AnimancerDataMigrator.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Text;
  7. using UnityEditor;
  8. using UnityEditor.SceneManagement;
  9. using UnityEngine;
  10. using static Animancer.AnimancerEvent.Sequence.Serializable;
  11. using Object = UnityEngine.Object;
  12. namespace Animancer.Editor
  13. {
  14. /// <summary>[Editor-Only]
  15. /// A system for migrating old asset data to the current version of Animancer.
  16. /// </summary>
  17. /// https://kybernetik.com.au/animancer/api/Animancer.Editor/AnimancerDataMigrator
  18. public class AnimancerDataMigrator
  19. {
  20. /************************************************************************************************************************/
  21. /// <summary>The directory where any newly created files will be saved.</summary>
  22. public const string
  23. MigratedDataDirectory = "Animancer Migrated Data";
  24. /************************************************************************************************************************/
  25. #region Entry Point
  26. /************************************************************************************************************************/
  27. [InitializeOnLoadMethod]
  28. private static void Initialize()
  29. {
  30. AnimancerReadMe.Editor.MigrateOldAssetData +=
  31. text => new AnimancerDataMigrator(text);
  32. }
  33. /************************************************************************************************************************/
  34. /// <summary>
  35. /// Shows dialogue windows explaining what the migration process will involve
  36. /// and confirming that the user wants to proceed before doing so.
  37. /// </summary>
  38. /// <remarks>
  39. /// <list type="number">
  40. /// <item>Save the scene if dirty.</item>
  41. /// <item>Make a backup.</item>
  42. /// </list>
  43. /// </remarks>
  44. public AnimancerDataMigrator(string functionName)
  45. {
  46. if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
  47. return;
  48. if (EditorApplication.isCompiling)
  49. {
  50. Debug.LogError(functionName + " failed: unable to migrate while scripts are compiling.");
  51. EditorApplication.Beep();
  52. return;
  53. }
  54. if (EditorUtility.scriptCompilationFailed)
  55. {
  56. Debug.LogError(functionName + " failed: all compile errors must be fixed first.");
  57. EditorApplication.Beep();
  58. return;
  59. }
  60. if (!AskForBackup(functionName))
  61. return;
  62. BeginMigration();
  63. }
  64. /************************************************************************************************************************/
  65. /// <summary>
  66. /// Explains what migration will involve
  67. /// and confirms that the user has backed up their project.
  68. /// </summary>
  69. private static bool AskForBackup(string functionName)
  70. {
  71. const string Message =
  72. "This process scans through your entire project" +
  73. " for assets containing data from an older version of Animancer" +
  74. " and attempts to convert that data to the format required by the current version." +
  75. "\n• This operation cannot be undone and may not be 100% reliable." +
  76. "\n• DO NOT proceed unless you have made a backup of your project" +
  77. " (Version Control such as Git is recommended)." +
  78. "\n• All files changed will be logged." +
  79. "\n• Please report any issues to " + Strings.DocsURLs.DeveloperEmail;
  80. var result = EditorUtility.DisplayDialogComplex(
  81. functionName,
  82. Message,
  83. "I have made a backup -> Migrate Data",
  84. "Cancel",
  85. "Open Upgrade Guide");
  86. switch (result)
  87. {
  88. case 0:
  89. return true;
  90. default:
  91. case 1:
  92. return false;
  93. case 2:
  94. Application.OpenURL(Strings.DocsURLs.UpgradeGuideURL);
  95. return false;
  96. }
  97. }
  98. /************************************************************************************************************************/
  99. private void BeginMigration()
  100. => MigrateData(GatherTargetFiles());
  101. /************************************************************************************************************************/
  102. private static List<string> GatherTargetFiles()
  103. {
  104. AssetDatabase.SaveAssets();
  105. var directory = Environment.CurrentDirectory;
  106. var targetFiles = new List<string>();
  107. GatherTargetFiles(Path.Combine(directory, "Assets"), targetFiles);
  108. GatherTargetFiles(Path.Combine(directory, "Packages"), targetFiles);
  109. return targetFiles;
  110. }
  111. private static void GatherTargetFiles(string directory, List<string> targetFiles)
  112. {
  113. var allFiles = Directory.GetFiles(directory, "*", SearchOption.AllDirectories);
  114. for (int i = 0; i < allFiles.Length; i++)
  115. {
  116. var file = allFiles[i];
  117. if (file.EndsWith(".asset", StringComparison.OrdinalIgnoreCase) ||
  118. file.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) ||
  119. file.EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
  120. targetFiles.Add(file);
  121. }
  122. }
  123. /************************************************************************************************************************/
  124. private void MigrateData(List<string> files)
  125. {
  126. try
  127. {
  128. Debug.Log("Data Migration Started");
  129. var modifiedFileCount = 0;
  130. var timer = SimpleTimer.Start();
  131. for (int i = 0; i < files.Count; i++)
  132. {
  133. var file = files[i];
  134. if (ShowProgressBar(i, files.Count, file))
  135. return;
  136. if (MigrateData(file))
  137. modifiedFileCount++;
  138. }
  139. Debug.Log(
  140. $"Data Migration Complete." +
  141. $" Modified {modifiedFileCount} files in {timer}." +
  142. $" Please check the modified files and report any issues to {Strings.DocsURLs.DeveloperEmail}");
  143. WarnAboutUnSharedChanges();
  144. AssetDatabase.Refresh();
  145. }
  146. finally
  147. {
  148. EditorUtility.ClearProgressBar();
  149. }
  150. }
  151. /************************************************************************************************************************/
  152. private static bool ShowProgressBar(int index, int count, string currentFile)
  153. => EditorUtility.DisplayCancelableProgressBar(
  154. "Migrating Asset Data",
  155. $"{index} / {count}: {currentFile}",
  156. index / (float)count);
  157. /************************************************************************************************************************/
  158. private bool MigrateData(string filePath)
  159. {
  160. try
  161. {
  162. var originalText = File.ReadAllText(filePath);
  163. var modifiedText = originalText;
  164. MigrateSerializedReferences(ref modifiedText);
  165. MigrateEvents(ref modifiedText);
  166. MigrateUnSharedTransitionAssets(filePath, ref modifiedText);
  167. MigrateTransitionAssets(ref modifiedText);
  168. if (!ReferenceEquals(originalText, modifiedText))
  169. {
  170. File.WriteAllText(filePath, modifiedText);
  171. filePath = GetRelativeFilePath(filePath);
  172. AssetDatabase.ImportAsset(filePath, ImportAssetOptions.ForceSynchronousImport);
  173. var asset = AssetDatabase.LoadAssetAtPath<Object>(filePath);
  174. Debug.Log($"Migrated: {filePath}", asset);
  175. return true;
  176. }
  177. }
  178. catch (Exception exception)
  179. {
  180. Debug.LogException(exception);
  181. }
  182. return false;
  183. }
  184. /************************************************************************************************************************/
  185. #endregion
  186. /************************************************************************************************************************/
  187. #region Serialized References
  188. /************************************************************************************************************************/
  189. /// <summary>[Animancer v8.0]
  190. /// Animancer assemblies renamed when moving to a package.
  191. /// </summary>
  192. private static void MigrateSerializedReferences(ref string fileText)
  193. {
  194. fileText = fileText.Replace(
  195. ", ns: Animancer, asm: Animancer}",
  196. ", ns: Animancer, asm: Kybernetik.Animancer}");
  197. }
  198. /************************************************************************************************************************/
  199. #endregion
  200. /************************************************************************************************************************/
  201. #region Events
  202. /************************************************************************************************************************/
  203. /// <summary>[Animancer v8.0]
  204. /// Event callbacks changed from <see cref="UnityEvent"/> to <see cref="IInvokable"/>.
  205. /// Event names changed from <see cref="string"/> to <see cref="StringAsset"/>.
  206. /// </summary>
  207. private static void MigrateEvents(ref string text)
  208. {
  209. const string Prefix = "_" + nameof(ITransitionWithEvents.Events) + ":";
  210. var index = 0;
  211. while ((index = text.IndexOf(Prefix, index)) >= 0)
  212. {
  213. var callbacks = IndexOfEventField(text, index, CallbacksField);
  214. if (callbacks >= 0)
  215. MigrateEventCallbacks(ref text, callbacks);
  216. var names = IndexOfEventField(text, index, NamesField);
  217. if (names >= 0)
  218. MigrateEventNames(ref text, names);
  219. index += Prefix.Length;
  220. }
  221. }
  222. /************************************************************************************************************************/
  223. /// <summary>Finds the indices of the serialized event fields and returns true if successful.</summary>
  224. private static int IndexOfEventField(
  225. string text,
  226. int start,
  227. string fieldName)
  228. {
  229. var baseIndentation = CountConsecutiveCharacters(text, start - 1, -1, ' ');
  230. while (true)
  231. {
  232. start = text.IndexOf('\n', start);
  233. if (start < 0)
  234. break;
  235. start++;
  236. var lineIndentation = CountConsecutiveCharacters(text, start, 1, ' ');
  237. if (lineIndentation <= baseIndentation)
  238. break;
  239. start += lineIndentation;
  240. if (text[start] != '_')
  241. continue;
  242. if (SubstringEquals(text, start, fieldName + ":"))
  243. return start;
  244. }
  245. return -1;
  246. }
  247. /************************************************************************************************************************/
  248. #region Event Names
  249. /************************************************************************************************************************/
  250. /// <summary>[Animancer v8.0]
  251. /// Event names changed from <see cref="string"/> to <see cref="StringAsset"/>.
  252. /// </summary>
  253. private static void MigrateEventNames(ref string text, int start)
  254. {
  255. start = text.IndexOf('\n', start);
  256. if (start < 0)
  257. return;
  258. start++;
  259. while (start < text.Length)
  260. {
  261. var end = text.IndexOf('\n', start + 1);
  262. if (start < 0)
  263. end = text.Length;
  264. start += CountConsecutiveCharacters(text, start, 1, ' ');
  265. var character = text[start];
  266. if (character != '-')
  267. return;
  268. start++;
  269. // If not already migrated.
  270. if (!SubstringEquals(text, start, " {fileID: "))
  271. {
  272. var name = text[start..end];
  273. name = name.Trim();
  274. start = text.IndexOf(name, start);
  275. MigrateEventName(ref text, start, name, out end);
  276. }
  277. start = end + 1;
  278. }
  279. }
  280. /************************************************************************************************************************/
  281. private static void MigrateEventName(ref string text, int start, string name, out int end)
  282. {
  283. GetOrCreateStringAsset(name, out var path, out var guid);
  284. var fileID = GetFileID(path);
  285. end = start + name.Length;
  286. var before = text[..start];
  287. var after = text[end..];
  288. var newReference = $"{{fileID: {fileID}, guid: {guid}, type: 2}}";
  289. end = start + newReference.Length;
  290. text = $"{before}{newReference}{after}";
  291. }
  292. /************************************************************************************************************************/
  293. private static StringAsset GetOrCreateStringAsset(StringReference name, out string path, out string guid)
  294. {
  295. var filter = $"{name} t:{nameof(StringAsset)}";
  296. var guids = AssetDatabase.FindAssets(filter);
  297. for (int i = 0; i < guids.Length; i++)
  298. {
  299. guid = guids[i];
  300. path = AssetDatabase.GUIDToAssetPath(guid);
  301. var asset = AssetDatabase.LoadAssetAtPath<StringAsset>(path);
  302. if (asset != null && asset.Name == name)
  303. return asset;
  304. }
  305. var key = ScriptableObject.CreateInstance<StringAsset>();
  306. key.name = name;
  307. AssetDatabase.CreateFolder("Assets", MigratedDataDirectory);
  308. path = Path.Combine("Assets", MigratedDataDirectory, name + ".asset");
  309. AssetDatabase.CreateAsset(key, path);
  310. guid = AssetDatabase.AssetPathToGUID(path);
  311. Debug.Log($"Created {nameof(StringAsset)} for event name: {name}", key);
  312. return key;
  313. }
  314. /************************************************************************************************************************/
  315. private static string GetFileID(string assetPath)
  316. {
  317. assetPath += ".meta";
  318. if (!File.Exists(assetPath))
  319. return null;
  320. var meta = File.ReadAllText(assetPath);
  321. const string Prefix = "mainObjectFileID: ";
  322. var start = meta.IndexOf(Prefix, StringComparison.Ordinal);
  323. if (start < 0)
  324. return null;
  325. start += Prefix.Length;
  326. var end = meta.IndexOf('\n', start);
  327. if (end < 0)
  328. end = meta.Length;
  329. var id = meta[start..end];
  330. id = id.Trim();
  331. return id;
  332. }
  333. /************************************************************************************************************************/
  334. #endregion
  335. /************************************************************************************************************************/
  336. #region Event Callbacks
  337. /************************************************************************************************************************/
  338. /// <summary>[Animancer v8.0]
  339. /// Event callbacks changed from <see cref="UnityEvent"/> to <see cref="IInvokable"/>.
  340. /// </summary>
  341. private static void MigrateEventCallbacks(ref string text, int start)
  342. {
  343. start = text.IndexOf('\n', start);
  344. if (start < 0)
  345. return;
  346. start++;
  347. while (start < text.Length)
  348. {
  349. var end = text.IndexOf('\n', start + 1);
  350. if (start < 0)
  351. end = text.Length;
  352. var indentation = CountConsecutiveCharacters(text, start, 1, ' ');
  353. start += indentation;
  354. var character = text[start];
  355. if (character != '-')
  356. return;
  357. start++;
  358. // If already migrated, skip over it.
  359. if (SubstringEquals(text, start, " rid:"))
  360. {
  361. start = end;
  362. }
  363. else// Otherwise, turn it into a serialized reference.
  364. {
  365. var callback = ReIndentFieldData(text, start, indentation, 6, out end);
  366. var id = RandomLong();
  367. var newText = StringBuilderPool.Instance.Acquire();
  368. newText.Append(text, 0, start - indentation - 1);
  369. newText.Append(' ', indentation);
  370. newText.Append("- rid: ");
  371. newText.Append(id);
  372. newText.Append('\n');
  373. start = newText.Length;
  374. newText.Append(text, end, text.Length - end);
  375. text = newText.ReleaseToString();
  376. AddSerializedReference(ref text, start, id, typeof(Animancer.UnityEvent), callback);
  377. }
  378. }
  379. }
  380. /************************************************************************************************************************/
  381. private static string ReIndentFieldData(
  382. string text,
  383. int start,
  384. int oldIndentation,
  385. int newIndentation,
  386. out int end)
  387. {
  388. var callback = StringBuilderPool.Instance.Acquire();
  389. end = start;
  390. while (start < text.Length)
  391. {
  392. var indentation = CountConsecutiveCharacters(text, start, 1, ' ');
  393. // Remove the dash from the start of the first line.
  394. if (callback.Length == 0)
  395. {
  396. start += indentation;
  397. indentation = newIndentation + 2;
  398. }
  399. else// Adjust the indentation of other lines.
  400. {
  401. if (indentation <= oldIndentation)
  402. {
  403. end = start;
  404. break;
  405. }
  406. start += indentation;
  407. indentation = indentation - oldIndentation + newIndentation;
  408. }
  409. end = text.IndexOf('\n', start);
  410. if (end < 0)
  411. end = text.Length;
  412. callback.Append(' ', indentation);
  413. callback.Append(text, start, end - start);
  414. callback.Append('\n');
  415. start = end + 1;
  416. }
  417. return callback.ReleaseToString();
  418. }
  419. /************************************************************************************************************************/
  420. #endregion
  421. /************************************************************************************************************************/
  422. #endregion
  423. /************************************************************************************************************************/
  424. #region UnShared Transition Assets
  425. /************************************************************************************************************************/
  426. private List<string> _UnSharedFilePaths;
  427. /************************************************************************************************************************/
  428. /// <summary>[Animancer v8.0]
  429. /// UnShared classes removed,
  430. /// data replaced with direct <see cref="TransitionAssetBase"/> references.
  431. /// </summary>
  432. private void MigrateUnSharedTransitionAssets(string filePath, ref string fileText)
  433. {
  434. var isChanged = false;
  435. var start = 0;
  436. while (start < fileText.Length)
  437. {
  438. const string
  439. AssetReferencePrefix = " _Asset: ",
  440. FileReferencePrefix = "{fileID: ",
  441. GuidPrefix = ", guid: ",
  442. TypePrefix = ", type: ";
  443. start = fileText.IndexOf(AssetReferencePrefix + FileReferencePrefix, start, StringComparison.Ordinal);
  444. if (start < 0)
  445. break;
  446. var end = fileText.IndexOf(
  447. '\n',
  448. start + AssetReferencePrefix.Length + FileReferencePrefix.Length);
  449. if (end < 0)
  450. end = fileText.Length;
  451. var guid = fileText.IndexOf(
  452. GuidPrefix,
  453. start + AssetReferencePrefix.Length,
  454. end - (start + AssetReferencePrefix.Length),
  455. StringComparison.Ordinal);
  456. if (guid < 0)
  457. break;
  458. var type = fileText.IndexOf(
  459. TypePrefix,
  460. guid + GuidPrefix.Length,
  461. end - (guid + GuidPrefix.Length),
  462. StringComparison.Ordinal);
  463. if (type < 0)
  464. break;
  465. var guidReference = fileText[(guid + GuidPrefix.Length)..type];
  466. var assetPath = AssetDatabase.GUIDToAssetPath(guidReference);
  467. if (string.IsNullOrEmpty(assetPath) ||
  468. AssetDatabase.LoadAssetAtPath<TransitionAssetBase>(assetPath) == null)
  469. {
  470. start++;
  471. continue;
  472. }
  473. var indent = CountConsecutiveCharacters(fileText, start, -1, ' ');
  474. var reference = fileText[(start + AssetReferencePrefix.Length)..end];
  475. fileText = $"{fileText[..(start - indent)]} {reference}{fileText[end..]}";
  476. if (!isChanged)
  477. {
  478. isChanged = true;
  479. _UnSharedFilePaths ??= new();
  480. _UnSharedFilePaths.Add(GetRelativeFilePath(filePath));
  481. }
  482. start += reference.Length;
  483. }
  484. }
  485. /************************************************************************************************************************/
  486. private void WarnAboutUnSharedChanges()
  487. {
  488. if (_UnSharedFilePaths == null)
  489. return;
  490. var text = StringBuilderPool.Instance.Acquire();
  491. AppendUnSharedFilePaths(text, int.MaxValue);
  492. var message = text.ToString();
  493. Debug.LogWarning(message + '\n');
  494. if (_UnSharedFilePaths.Count > 10)
  495. {
  496. text.Length = 0;
  497. AppendUnSharedFilePaths(text, 10);
  498. message = text.ToString();
  499. }
  500. EditorUtility.DisplayDialog(
  501. "UnShared Transition Assets",
  502. message + "\n\nThis warning will be logged.",
  503. "I will review the listed files");
  504. StringBuilderPool.Instance.Release(text);
  505. }
  506. private void AppendUnSharedFilePaths(StringBuilder text, int maxCount)
  507. {
  508. text.Append("Data resembling UnShared Transition Assets has been detected in the following files:");
  509. var count = Math.Min(maxCount, _UnSharedFilePaths.Count);
  510. for (int i = 0; i < count; i++)
  511. {
  512. text.Append("\n• ")
  513. .Append(_UnSharedFilePaths[i]);
  514. }
  515. if (count < _UnSharedFilePaths.Count)
  516. text.Append("\n• And ")
  517. .Append(_UnSharedFilePaths.Count - count)
  518. .Append(" more (see the log).");
  519. text.Append(
  520. "\n\nThe UnShared system has been removed so this data has been modified" +
  521. " to the format of a direct reference to a Transition Asset." +
  522. $"\n\nFor example, if you had a ClipTransitionAsset.UnShared field" +
  523. $" and you changed it to a direct ClipTransitionAsset" +
  524. $" then this modification should allow the field to keep its referenced asset." +
  525. $"\n\nThis may have falsely identified unrelated data which looks similar" +
  526. $" so please review the listed files to check your references.");
  527. }
  528. /************************************************************************************************************************/
  529. #endregion
  530. /************************************************************************************************************************/
  531. #region Transition Assets
  532. /************************************************************************************************************************/
  533. private const string AnimancerTransitionAssetGUID = "c5a8877f26e7a6a43aaf06fade1a064a";
  534. private static readonly string[] RemovedTransitionAssetGUIDs =
  535. {
  536. "65baa284d24adb24b90b39482364509c",// ClipTransitionAsset.
  537. "6cb514e38f7bd084383a7355a6273a33",// ControllerTransitionAsset.
  538. "15ed7dccdb910ec4896f03f7cc2ede35",// Float1ControllerTransitionAsset.
  539. "f283fbe305b90dc489b5dec987ecad09",// Float2ControllerTransitionAsset.
  540. "435c1d26374dbbb498fa45fe394dfe10",// Float3ControllerTransitionAsset.
  541. "a472a4806da7f9147a4a5cd7eee170df",// LinearMixerTransitionAsset.
  542. "9785324053a1e39449b4e579bfb71f76",// ManualMixerTransitionAsset.
  543. "8a2d01d4f425b3848938f199392c9afb",// MixerTransition2DAsset.
  544. "5415cf2115901c345af7680b044d4604",// PlayableAssetTransitionAsset.
  545. };
  546. /// <summary>[Animancer v8.0]
  547. /// Removed the classes derived from <see cref="TransitionAssetBase"/>
  548. /// for each individual transition type except <see cref="TransitionAsset"/>
  549. /// since its [<see cref="SerializeReference"/>] can hold any of them without changing the asset type.
  550. /// </summary>
  551. private void MigrateTransitionAssets(ref string fileText)
  552. {
  553. const string
  554. ScriptReferencePrefix = "m_Script: {fileID: 11500000, guid: ",
  555. ScriptReferenceSuffix = ", type: 3}";
  556. var index = 0;
  557. while (index < fileText.Length)
  558. {
  559. index = fileText.IndexOf(ScriptReferencePrefix, index);
  560. if (index < 0)
  561. return;
  562. index += ScriptReferencePrefix.Length;
  563. var guidLength = AnimancerTransitionAssetGUID.Length;
  564. var guidEnd = index + guidLength;
  565. if (guidEnd >= fileText.Length)
  566. return;
  567. for (int i = 0; i < RemovedTransitionAssetGUIDs.Length; i++)
  568. {
  569. if (string.CompareOrdinal(fileText, index, RemovedTransitionAssetGUIDs[i], 0, guidLength) == 0)
  570. {
  571. fileText = $"{fileText[..index]}{AnimancerTransitionAssetGUID}{fileText[guidEnd..]}";
  572. break;
  573. }
  574. }
  575. index = guidEnd + ScriptReferenceSuffix.Length;
  576. }
  577. }
  578. /************************************************************************************************************************/
  579. #endregion
  580. /************************************************************************************************************************/
  581. #region Utilities
  582. /************************************************************************************************************************/
  583. private static int CountConsecutiveCharacters(
  584. string text,
  585. int start,
  586. int direction,
  587. char character)
  588. {
  589. var count = 0;
  590. while (start >= 0 && start < text.Length)
  591. {
  592. if (text[start] == character)
  593. {
  594. count++;
  595. start += direction;
  596. }
  597. else
  598. {
  599. break;
  600. }
  601. }
  602. return count;
  603. }
  604. /************************************************************************************************************************/
  605. private static bool SubstringEquals(
  606. string text,
  607. int start,
  608. string substring)
  609. => string.CompareOrdinal(
  610. text,
  611. start,
  612. substring,
  613. 0,
  614. substring.Length) == 0;
  615. /************************************************************************************************************************/
  616. private static readonly System.Random
  617. Random = new();
  618. private static long RandomLong()
  619. => (uint)Random.Next() | ((long)Random.Next() << 32);
  620. /************************************************************************************************************************/
  621. private static void AddSerializedReference(
  622. ref string text,
  623. int index,
  624. long id,
  625. Type type,
  626. string reference)
  627. {
  628. const string ObjectHeader = "\n--- !u!";
  629. var start = text.LastIndexOf(ObjectHeader, index) + 1;
  630. var end = text.IndexOf(ObjectHeader, start) + 1;
  631. if (end <= 0)
  632. end = text.Length;
  633. var newText = StringBuilderPool.Instance.Acquire();
  634. newText.Append(text, 0, end);
  635. if (text[^1] != '\n')
  636. newText.Append('\n');
  637. const string ReferencesHeader = " references:\n";
  638. const string VersionHeader = " version: 2\n";
  639. // If the references block is missing, add it.
  640. var referencesIndex = text.LastIndexOf("\n" + ReferencesHeader, end, end - start);
  641. if (referencesIndex < 0)
  642. {
  643. newText.Append(ReferencesHeader + VersionHeader + " RefIds:\n");
  644. }
  645. else
  646. {
  647. // Or if it exists but has the wrong version, complain.
  648. if (!SubstringEquals(text, referencesIndex + ReferencesHeader.Length + 1, VersionHeader))
  649. Debug.LogWarning("Unknown serialized reference format. Expected version 2.");
  650. }
  651. newText.Append(" - rid: ");
  652. newText.Append(id);
  653. newText.Append("\n type: {class: ");
  654. newText.Append(type.Name);
  655. newText.Append(", ns: ");
  656. newText.Append(type.Namespace);
  657. newText.Append(", asm: ");
  658. newText.Append(type.Assembly.GetName().Name);
  659. newText.Append("}\n");
  660. newText.Append(" data:\n");
  661. newText.Append(reference);
  662. newText.Append(text, end, text.Length - end);
  663. text = newText.ReleaseToString();
  664. }
  665. /************************************************************************************************************************/
  666. private static string GetRelativeFilePath(string filePath)
  667. {
  668. var currentDirectory = Environment.CurrentDirectory;
  669. if (filePath.StartsWith(currentDirectory))
  670. filePath = filePath[(currentDirectory.Length + 1)..];
  671. return filePath;
  672. }
  673. /************************************************************************************************************************/
  674. #endregion
  675. /************************************************************************************************************************/
  676. #region Example Data
  677. /************************************************************************************************************************/
  678. // Old Transition Asset Example
  679. /************************************************************************************************************************/
  680. /*
  681. %YAML 1.1
  682. %TAG !u! tag:unity3d.com,2011:
  683. --- !u!114 &11400000
  684. MonoBehaviour:
  685. m_ObjectHideFlags: 0
  686. m_CorrespondingSourceObject: {fileID: 0}
  687. m_PrefabInstance: {fileID: 0}
  688. m_PrefabAsset: {fileID: 0}
  689. m_GameObject: {fileID: 0}
  690. m_Enabled: 1
  691. m_EditorHideFlags: 0
  692. m_Script: {fileID: 11500000, guid: 65baa284d24adb24b90b39482364509c, type: 3}
  693. m_Name: New Clip Transition Asset
  694. m_EditorClassIdentifier:
  695. _Transition:
  696. rid: 2481247569047191554
  697. references:
  698. version: 2
  699. RefIds:
  700. - rid: 2481247569047191554
  701. type: {class: ClipTransition, ns: Animancer, asm: Animancer}
  702. data:
  703. _FadeDuration: 0.25
  704. _Events:
  705. _NormalizedTimes:
  706. - 0.653912
  707. - NaN
  708. _Callbacks:
  709. - m_PersistentCalls:
  710. m_Calls:
  711. - m_Target: {fileID: 11400000}
  712. m_TargetAssemblyTypeName: UnityEngine.Object, UnityEngine
  713. m_MethodName: set_name
  714. m_Mode: 5
  715. m_Arguments:
  716. m_ObjectArgument: {fileID: 0}
  717. m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
  718. m_IntArgument: 0
  719. m_FloatArgument: 0
  720. m_StringArgument:
  721. m_BoolArgument: 0
  722. m_CallState: 2
  723. - m_Target: {fileID: 11400000}
  724. m_TargetAssemblyTypeName: UnityEngine.Object, UnityEngine
  725. m_MethodName: set_name
  726. m_Mode: 5
  727. m_Arguments:
  728. m_ObjectArgument: {fileID: 0}
  729. m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
  730. m_IntArgument: 0
  731. m_FloatArgument: 0
  732. m_StringArgument: SecondCall
  733. m_BoolArgument: 0
  734. m_CallState: 2
  735. - m_PersistentCalls:
  736. m_Calls:
  737. - m_Target: {fileID: 11400000}
  738. m_TargetAssemblyTypeName: UnityEngine.Object, UnityEngine
  739. m_MethodName: set_name
  740. m_Mode: 5
  741. m_Arguments:
  742. m_ObjectArgument: {fileID: 0}
  743. m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
  744. m_IntArgument: 0
  745. m_FloatArgument: 0
  746. m_StringArgument: Ended
  747. m_BoolArgument: 0
  748. m_CallState: 2
  749. _Names:
  750. - Fire
  751. _Clip: {fileID: 7400000, guid: c9ffd254c8ebfae4fbe97ac489238d8d, type: 2}
  752. _Speed: 1
  753. _NormalizedStartTime: 0
  754. */
  755. /************************************************************************************************************************/
  756. // New Transition Asset Example
  757. /************************************************************************************************************************/
  758. /*
  759. %YAML 1.1
  760. %TAG !u! tag:unity3d.com,2011:
  761. --- !u!114 &11400000
  762. MonoBehaviour:
  763. m_ObjectHideFlags: 0
  764. m_CorrespondingSourceObject: {fileID: 0}
  765. m_PrefabInstance: {fileID: 0}
  766. m_PrefabAsset: {fileID: 0}
  767. m_GameObject: {fileID: 0}
  768. m_Enabled: 1
  769. m_EditorHideFlags: 0
  770. m_Script: {fileID: 11500000, guid: 65baa284d24adb24b90b39482364509c, type: 3}
  771. m_Name: New Clip Transition Asset
  772. m_EditorClassIdentifier:
  773. _Transition:
  774. rid: 2481247569047191554
  775. references:
  776. version: 2
  777. RefIds:
  778. - rid: 2481247569047191554
  779. type: {class: ClipTransition, ns: Animancer, asm: Kybernetik.Animancer}
  780. data:
  781. _FadeDuration: 0.25
  782. _Speed: 1
  783. _Events:
  784. _NormalizedTimes:
  785. - 0.653912
  786. - NaN
  787. _Callbacks:
  788. - rid: 2481247573131657217
  789. - rid: 2481247573131657219
  790. _Names:
  791. - {fileID: 11400000, guid: f0b72c8b47f88b2449a2300915f08c72, type: 2}
  792. _Clip: {fileID: 7400000, guid: c9ffd254c8ebfae4fbe97ac489238d8d, type: 2}
  793. _NormalizedStartTime: 0
  794. - rid: 2481247573131657217
  795. type: {class: UnityEvent, ns: Animancer, asm: Kybernetik.Animancer}
  796. data:
  797. m_PersistentCalls:
  798. m_Calls:
  799. - m_Target: {fileID: 11400000}
  800. m_TargetAssemblyTypeName: UnityEngine.Object, UnityEngine
  801. m_MethodName: set_name
  802. m_Mode: 5
  803. m_Arguments:
  804. m_ObjectArgument: {fileID: 0}
  805. m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
  806. m_IntArgument: 0
  807. m_FloatArgument: 0
  808. m_StringArgument:
  809. m_BoolArgument: 0
  810. m_CallState: 2
  811. - m_Target: {fileID: 11400000}
  812. m_TargetAssemblyTypeName: UnityEngine.Object, UnityEngine
  813. m_MethodName: set_name
  814. m_Mode: 5
  815. m_Arguments:
  816. m_ObjectArgument: {fileID: 0}
  817. m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
  818. m_IntArgument: 0
  819. m_FloatArgument: 0
  820. m_StringArgument: SecondCall
  821. m_BoolArgument: 0
  822. m_CallState: 2
  823. - rid: 2481247573131657219
  824. type: {class: UnityEvent, ns: Animancer, asm: Kybernetik.Animancer}
  825. data:
  826. m_PersistentCalls:
  827. m_Calls:
  828. - m_Target: {fileID: 11400000}
  829. m_TargetAssemblyTypeName: UnityEngine.Object, UnityEngine
  830. m_MethodName: set_name
  831. m_Mode: 5
  832. m_Arguments:
  833. m_ObjectArgument: {fileID: 0}
  834. m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
  835. m_IntArgument: 0
  836. m_FloatArgument: 0
  837. m_StringArgument: Ended
  838. m_BoolArgument: 0
  839. m_CallState: 2
  840. */
  841. /************************************************************************************************************************/
  842. #endregion
  843. /************************************************************************************************************************/
  844. }
  845. }
  846. #endif