Serialization.cs 60 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308
  1. // Serialization // Copyright 2023-2024 Kybernetik //
  2. #if UNITY_EDITOR
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. using System.Text;
  8. using UnityEditor;
  9. using UnityEngine;
  10. using Object = UnityEngine.Object;
  11. // Shared File Last Modified: 2023-08-16
  12. namespace Animancer.Editor
  13. // namespace InspectorGadgets.Editor
  14. // namespace UltEvents.Editor
  15. {
  16. /// <summary>[Editor-Only] The possible states for a function in a <see cref="GenericMenu"/>.</summary>
  17. public enum MenuFunctionState
  18. {
  19. /************************************************************************************************************************/
  20. /// <summary>Displayed normally.</summary>
  21. Normal,
  22. /// <summary>Has a check mark next to it to show that it is selected.</summary>
  23. Selected,
  24. /// <summary>Greyed out and unusable.</summary>
  25. Disabled,
  26. /************************************************************************************************************************/
  27. }
  28. /// <summary>[Editor-Only] Various serialization utilities.</summary>
  29. public static partial class Serialization
  30. {
  31. /************************************************************************************************************************/
  32. #region Public Static API
  33. /************************************************************************************************************************/
  34. /// <summary>The text used in a <see cref="SerializedProperty.propertyPath"/> to denote array elements.</summary>
  35. public const string
  36. ArrayDataPrefix = ".Array.data[",
  37. ArrayDataSuffix = "]";
  38. /// <summary>Bindings for Public and Non-Public Instance members.</summary>
  39. public const BindingFlags
  40. InstanceBindings = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
  41. /************************************************************************************************************************/
  42. /// <summary>Returns a user friendly version of the <see cref="SerializedProperty.propertyPath"/>.</summary>
  43. public static string GetFriendlyPath(this SerializedProperty property)
  44. => property.propertyPath.Replace(ArrayDataPrefix, "[");
  45. /************************************************************************************************************************/
  46. #region Get Value
  47. /************************************************************************************************************************/
  48. /// <summary>Gets the value of the specified <see cref="SerializedProperty"/>.</summary>
  49. public static object GetValue(this SerializedProperty property, object targetObject)
  50. {
  51. if (property.hasMultipleDifferentValues &&
  52. property.serializedObject.targetObject != targetObject as Object)
  53. {
  54. property = new SerializedObject(targetObject as Object).FindProperty(property.propertyPath);
  55. }
  56. return property.propertyType switch
  57. {
  58. SerializedPropertyType.Boolean => property.boolValue,
  59. SerializedPropertyType.Float => property.floatValue,
  60. SerializedPropertyType.String => property.stringValue,
  61. SerializedPropertyType.Integer or
  62. SerializedPropertyType.Character or
  63. SerializedPropertyType.LayerMask or
  64. SerializedPropertyType.ArraySize => property.intValue,
  65. SerializedPropertyType.Vector2 => property.vector2Value,
  66. SerializedPropertyType.Vector3 => property.vector3Value,
  67. SerializedPropertyType.Vector4 => property.vector4Value,
  68. SerializedPropertyType.Quaternion => property.quaternionValue,
  69. SerializedPropertyType.Color => property.colorValue,
  70. SerializedPropertyType.AnimationCurve => property.animationCurveValue,
  71. SerializedPropertyType.Rect => property.rectValue,
  72. SerializedPropertyType.Bounds => property.boundsValue,
  73. SerializedPropertyType.Vector2Int => property.vector2IntValue,
  74. SerializedPropertyType.Vector3Int => property.vector3IntValue,
  75. SerializedPropertyType.RectInt => property.rectIntValue,
  76. SerializedPropertyType.BoundsInt => property.boundsIntValue,
  77. SerializedPropertyType.ObjectReference => property.objectReferenceValue,
  78. SerializedPropertyType.ExposedReference => property.exposedReferenceValue,
  79. SerializedPropertyType.FixedBufferSize => property.fixedBufferSize,
  80. SerializedPropertyType.Gradient => property.GetGradientValue(),
  81. SerializedPropertyType.ManagedReference => property.managedReferenceValue,
  82. // Enum would be complex because enumValueIndex can't be cast directly.
  83. _ => GetAccessor(property)?.GetValue(targetObject),
  84. };
  85. }
  86. /************************************************************************************************************************/
  87. /// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
  88. public static object GetValue(this SerializedProperty property)
  89. => GetValue(property, property.serializedObject.targetObject);
  90. /// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
  91. public static T GetValue<T>(this SerializedProperty property)
  92. => (T)GetValue(property);
  93. /// <summary>Gets the value of the <see cref="SerializedProperty"/>.</summary>
  94. public static void GetValue<T>(this SerializedProperty property, out T value)
  95. => value = (T)GetValue(property);
  96. /************************************************************************************************************************/
  97. /// <summary>Gets the value of the <see cref="SerializedProperty"/> for each of its target objects.</summary>
  98. public static T[] GetValues<T>(this SerializedProperty property)
  99. {
  100. try
  101. {
  102. var targetObjects = property.serializedObject.targetObjects;
  103. var values = new T[targetObjects.Length];
  104. for (int i = 0; i < values.Length; i++)
  105. {
  106. values[i] = (T)GetValue(property, targetObjects[i]);
  107. }
  108. return values;
  109. }
  110. catch
  111. {
  112. return null;
  113. }
  114. }
  115. /************************************************************************************************************************/
  116. /// <summary>Is the value of the `property` the same as the default serialized value for its type?</summary>
  117. public static bool IsDefaultValueByType(SerializedProperty property)
  118. {
  119. if (property.hasMultipleDifferentValues)
  120. return false;
  121. switch (property.propertyType)
  122. {
  123. case SerializedPropertyType.Boolean: return property.boolValue == default;
  124. case SerializedPropertyType.Float: return property.floatValue == default;
  125. case SerializedPropertyType.String: return property.stringValue == "";
  126. case SerializedPropertyType.Integer:
  127. case SerializedPropertyType.Character:
  128. case SerializedPropertyType.LayerMask:
  129. case SerializedPropertyType.ArraySize:
  130. return property.intValue == default;
  131. case SerializedPropertyType.Vector2: return property.vector2Value.Equals(default);
  132. case SerializedPropertyType.Vector3: return property.vector3Value.Equals(default);
  133. case SerializedPropertyType.Vector4: return property.vector4Value.Equals(default);
  134. case SerializedPropertyType.Quaternion: return property.quaternionValue.Equals(default);
  135. case SerializedPropertyType.Color: return property.colorValue.Equals(default);
  136. case SerializedPropertyType.AnimationCurve: return property.animationCurveValue == default;
  137. case SerializedPropertyType.Rect: return property.rectValue == default;
  138. case SerializedPropertyType.Bounds: return property.boundsValue == default;
  139. case SerializedPropertyType.Vector2Int: return property.vector2IntValue == default;
  140. case SerializedPropertyType.Vector3Int: return property.vector3IntValue == default;
  141. case SerializedPropertyType.RectInt: return property.rectIntValue.Equals(default);
  142. case SerializedPropertyType.BoundsInt: return property.boundsIntValue == default;
  143. case SerializedPropertyType.ObjectReference: return property.objectReferenceValue == default;
  144. case SerializedPropertyType.ExposedReference: return property.exposedReferenceValue == default;
  145. case SerializedPropertyType.ManagedReference: return property.managedReferenceValue == default;
  146. case SerializedPropertyType.FixedBufferSize: return property.fixedBufferSize == default;
  147. case SerializedPropertyType.Enum: return property.enumValueIndex == default;
  148. case SerializedPropertyType.Gradient:
  149. case SerializedPropertyType.Generic:
  150. default:
  151. if (property.isArray)
  152. return property.arraySize == default;
  153. var depth = property.depth;
  154. property = property.Copy();
  155. var enterChildren = true;
  156. while (property.Next(enterChildren) && property.depth > depth)
  157. {
  158. enterChildren = false;
  159. if (!IsDefaultValueByType(property))
  160. return false;
  161. }
  162. return true;
  163. }
  164. }
  165. /************************************************************************************************************************/
  166. #endregion
  167. /************************************************************************************************************************/
  168. #region Set Value
  169. /************************************************************************************************************************/
  170. /// <summary>Sets the value of the specified <see cref="SerializedProperty"/>.</summary>
  171. public static void SetValue(this SerializedProperty property, object targetObject, object value)
  172. {
  173. switch (property.propertyType)
  174. {
  175. case SerializedPropertyType.Boolean: property.boolValue = (bool)value; break;
  176. case SerializedPropertyType.Float: property.floatValue = (float)value; break;
  177. case SerializedPropertyType.String: property.stringValue = (string)value; break;
  178. case SerializedPropertyType.Integer:
  179. case SerializedPropertyType.Character:
  180. case SerializedPropertyType.LayerMask:
  181. case SerializedPropertyType.ArraySize:
  182. property.intValue = (int)value; break;
  183. case SerializedPropertyType.Vector2: property.vector2Value = (Vector2)value; break;
  184. case SerializedPropertyType.Vector3: property.vector3Value = (Vector3)value; break;
  185. case SerializedPropertyType.Vector4: property.vector4Value = (Vector4)value; break;
  186. case SerializedPropertyType.Quaternion: property.quaternionValue = (Quaternion)value; break;
  187. case SerializedPropertyType.Color: property.colorValue = (Color)value; break;
  188. case SerializedPropertyType.AnimationCurve: property.animationCurveValue = (AnimationCurve)value; break;
  189. case SerializedPropertyType.Rect: property.rectValue = (Rect)value; break;
  190. case SerializedPropertyType.Bounds: property.boundsValue = (Bounds)value; break;
  191. case SerializedPropertyType.Vector2Int: property.vector2IntValue = (Vector2Int)value; break;
  192. case SerializedPropertyType.Vector3Int: property.vector3IntValue = (Vector3Int)value; break;
  193. case SerializedPropertyType.RectInt: property.rectIntValue = (RectInt)value; break;
  194. case SerializedPropertyType.BoundsInt: property.boundsIntValue = (BoundsInt)value; break;
  195. case SerializedPropertyType.ObjectReference: property.objectReferenceValue = (Object)value; break;
  196. case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = (Object)value; break;
  197. case SerializedPropertyType.ManagedReference: property.managedReferenceValue = value; break;
  198. case SerializedPropertyType.FixedBufferSize:
  199. throw new InvalidOperationException($"{nameof(SetValue)} failed:" +
  200. $" {nameof(SerializedProperty)}.{nameof(SerializedProperty.fixedBufferSize)} is read-only.");
  201. case SerializedPropertyType.Gradient: property.SetGradientValue((Gradient)value); break;
  202. case SerializedPropertyType.Enum:// Would be complex because enumValueIndex can't be cast directly.
  203. case SerializedPropertyType.Generic:
  204. default:
  205. var accessor = GetAccessor(property);
  206. accessor?.SetValue(targetObject, value);
  207. break;
  208. }
  209. }
  210. /************************************************************************************************************************/
  211. /// <summary>Sets the value of the <see cref="SerializedProperty"/>.</summary>
  212. public static void SetValue(this SerializedProperty property, object value)
  213. {
  214. switch (property.propertyType)
  215. {
  216. case SerializedPropertyType.Boolean: property.boolValue = (bool)value; break;
  217. case SerializedPropertyType.Float: property.floatValue = (float)value; break;
  218. case SerializedPropertyType.Integer: property.intValue = (int)value; break;
  219. case SerializedPropertyType.String: property.stringValue = (string)value; break;
  220. case SerializedPropertyType.Vector2: property.vector2Value = (Vector2)value; break;
  221. case SerializedPropertyType.Vector3: property.vector3Value = (Vector3)value; break;
  222. case SerializedPropertyType.Vector4: property.vector4Value = (Vector4)value; break;
  223. case SerializedPropertyType.Quaternion: property.quaternionValue = (Quaternion)value; break;
  224. case SerializedPropertyType.Color: property.colorValue = (Color)value; break;
  225. case SerializedPropertyType.AnimationCurve: property.animationCurveValue = (AnimationCurve)value; break;
  226. case SerializedPropertyType.Rect: property.rectValue = (Rect)value; break;
  227. case SerializedPropertyType.Bounds: property.boundsValue = (Bounds)value; break;
  228. case SerializedPropertyType.Vector2Int: property.vector2IntValue = (Vector2Int)value; break;
  229. case SerializedPropertyType.Vector3Int: property.vector3IntValue = (Vector3Int)value; break;
  230. case SerializedPropertyType.RectInt: property.rectIntValue = (RectInt)value; break;
  231. case SerializedPropertyType.BoundsInt: property.boundsIntValue = (BoundsInt)value; break;
  232. case SerializedPropertyType.ObjectReference: property.objectReferenceValue = (Object)value; break;
  233. case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = (Object)value; break;
  234. case SerializedPropertyType.ArraySize: property.intValue = (int)value; break;
  235. case SerializedPropertyType.FixedBufferSize:
  236. throw new InvalidOperationException($"{nameof(SetValue)} failed:" +
  237. $" {nameof(SerializedProperty)}.{nameof(SerializedProperty.fixedBufferSize)} is read-only.");
  238. case SerializedPropertyType.Generic:
  239. case SerializedPropertyType.Enum:
  240. case SerializedPropertyType.LayerMask:
  241. case SerializedPropertyType.Gradient:
  242. case SerializedPropertyType.Character:
  243. default:
  244. var accessor = GetAccessor(property);
  245. if (accessor != null)
  246. {
  247. var targets = property.serializedObject.targetObjects;
  248. for (int i = 0; i < targets.Length; i++)
  249. {
  250. accessor.SetValue(targets[i], value);
  251. }
  252. }
  253. break;
  254. }
  255. }
  256. /************************************************************************************************************************/
  257. /// <summary>
  258. /// Resets the value of the <see cref="SerializedProperty"/> to the default value of its type and all its field
  259. /// types, ignoring values set by constructors or field initializers.
  260. /// </summary>
  261. /// <remarks>
  262. /// If you want to run constructors and field initializers, you can call
  263. /// <see cref="PropertyAccessor.ResetValue"/> instead.
  264. /// </remarks>
  265. public static void ResetValue(SerializedProperty property)
  266. {
  267. switch (property.propertyType)
  268. {
  269. case SerializedPropertyType.Boolean: property.boolValue = default; break;
  270. case SerializedPropertyType.Float: property.floatValue = default; break;
  271. case SerializedPropertyType.String: property.stringValue = ""; break;
  272. case SerializedPropertyType.Integer:
  273. case SerializedPropertyType.Character:
  274. case SerializedPropertyType.LayerMask:
  275. case SerializedPropertyType.ArraySize:
  276. property.intValue = default;
  277. break;
  278. case SerializedPropertyType.Vector2: property.vector2Value = default; break;
  279. case SerializedPropertyType.Vector3: property.vector3Value = default; break;
  280. case SerializedPropertyType.Vector4: property.vector4Value = default; break;
  281. case SerializedPropertyType.Quaternion: property.quaternionValue = default; break;
  282. case SerializedPropertyType.Color: property.colorValue = default; break;
  283. case SerializedPropertyType.AnimationCurve: property.animationCurveValue = default; break;
  284. case SerializedPropertyType.Rect: property.rectValue = default; break;
  285. case SerializedPropertyType.Bounds: property.boundsValue = default; break;
  286. case SerializedPropertyType.Vector2Int: property.vector2IntValue = default; break;
  287. case SerializedPropertyType.Vector3Int: property.vector3IntValue = default; break;
  288. case SerializedPropertyType.RectInt: property.rectIntValue = default; break;
  289. case SerializedPropertyType.BoundsInt: property.boundsIntValue = default; break;
  290. case SerializedPropertyType.ObjectReference: property.objectReferenceValue = default; break;
  291. case SerializedPropertyType.ExposedReference: property.exposedReferenceValue = default; break;
  292. case SerializedPropertyType.Enum: property.enumValueIndex = default; break;
  293. case SerializedPropertyType.Gradient:
  294. case SerializedPropertyType.FixedBufferSize:
  295. case SerializedPropertyType.Generic:
  296. default:
  297. if (property.isArray)
  298. {
  299. property.arraySize = default;
  300. break;
  301. }
  302. var depth = property.depth;
  303. property = property.Copy();
  304. var enterChildren = true;
  305. while (property.Next(enterChildren) && property.depth > depth)
  306. {
  307. enterChildren = false;
  308. ResetValue(property);
  309. }
  310. break;
  311. }
  312. }
  313. /************************************************************************************************************************/
  314. /// <summary>Copies the value of `from` into `to` (including all nested properties).</summary>
  315. public static float CopyValueFrom(this SerializedProperty to, SerializedProperty from)
  316. {
  317. from = from.Copy();
  318. var fromPath = from.propertyPath;
  319. var pathPrefixLength = fromPath.Length + 1;
  320. var depth = from.depth;
  321. var copyCount = 0;
  322. var totalCount = 0;
  323. StringBuilder issues = null;
  324. do
  325. {
  326. while (from.propertyType == SerializedPropertyType.Generic)
  327. if (!from.Next(true))
  328. goto LogResults;
  329. SerializedProperty toRelative;
  330. var relativePath = from.propertyPath;
  331. if (relativePath.Length <= pathPrefixLength)
  332. {
  333. toRelative = to;
  334. }
  335. else
  336. {
  337. relativePath = relativePath[pathPrefixLength..];
  338. toRelative = to.FindPropertyRelative(relativePath);
  339. }
  340. if (!from.hasMultipleDifferentValues &&
  341. toRelative != null &&
  342. toRelative.propertyType == from.propertyType &&
  343. toRelative.type == from.type)
  344. {
  345. // GetValue and SetValue currently access the underlying field for enums, but we need the stored value.
  346. if (toRelative.propertyType == SerializedPropertyType.Enum)
  347. toRelative.enumValueIndex = from.enumValueIndex;
  348. else
  349. toRelative.SetValue(from.GetValue());
  350. copyCount++;
  351. }
  352. else
  353. {
  354. issues ??= new();
  355. issues.AppendLine()
  356. .Append(" - ");
  357. if (from.hasMultipleDifferentValues)
  358. {
  359. issues
  360. .Append("The selected objects have different values for '")
  361. .Append(relativePath)
  362. .Append("'.");
  363. }
  364. else if (toRelative == null)
  365. {
  366. issues
  367. .Append("No property '")
  368. .Append(relativePath)
  369. .Append("' exists relative to '")
  370. .Append(to.propertyPath)
  371. .Append("'.");
  372. }
  373. else if (toRelative.propertyType != from.propertyType)
  374. {
  375. issues
  376. .Append("The type of '")
  377. .Append(toRelative.propertyPath)
  378. .Append("' was '")
  379. .Append(toRelative.propertyType)
  380. .Append("' but should be '")
  381. .Append(from.propertyType)
  382. .Append("'.");
  383. }
  384. else if (toRelative.type != from.type)
  385. {
  386. issues
  387. .Append("The type of '")
  388. .Append(toRelative.propertyPath)
  389. .Append("' was '")
  390. .Append(toRelative.type)
  391. .Append("' but should be '")
  392. .Append(from.type)
  393. .Append("'.");
  394. }
  395. else// This should never happen.
  396. {
  397. issues
  398. .Append(" - Unknown issue with '")
  399. .Append(relativePath)
  400. .Append("'.");
  401. }
  402. }
  403. totalCount++;
  404. }
  405. while (from.Next(false) && from.depth > depth);
  406. LogResults:
  407. if (copyCount < totalCount)
  408. Debug.Log($"Copied {copyCount} / {totalCount} values from '{fromPath}' to '{to.propertyPath}': {issues}");
  409. return (float)copyCount / totalCount;
  410. }
  411. /************************************************************************************************************************/
  412. #endregion
  413. /************************************************************************************************************************/
  414. #region Gradients
  415. /************************************************************************************************************************/
  416. private static PropertyInfo _GradientValue;
  417. /// <summary><c>SerializedProperty.gradientValue</c> is internal.</summary>
  418. private static PropertyInfo GradientValue
  419. {
  420. get
  421. {
  422. if (_GradientValue == null)
  423. _GradientValue = typeof(SerializedProperty).GetProperty("gradientValue", InstanceBindings);
  424. return _GradientValue;
  425. }
  426. }
  427. /// <summary>Gets the <see cref="Gradient"/> value from a <see cref="SerializedPropertyType.Gradient"/>.</summary>
  428. public static Gradient GetGradientValue(this SerializedProperty property)
  429. => (Gradient)GradientValue.GetValue(property, null);
  430. /// <summary>Sets the <see cref="Gradient"/> value on a <see cref="SerializedPropertyType.Gradient"/>.</summary>
  431. public static void SetGradientValue(this SerializedProperty property, Gradient value)
  432. => GradientValue.SetValue(property, value, null);
  433. /************************************************************************************************************************/
  434. #endregion
  435. /************************************************************************************************************************/
  436. /// <summary>Indicates whether both properties refer to the same underlying field.</summary>
  437. public static bool AreSameProperty(SerializedProperty a, SerializedProperty b)
  438. {
  439. if (a == b)
  440. return true;
  441. if (a == null)
  442. return b == null;
  443. if (b == null)
  444. return false;
  445. if (a.propertyPath != b.propertyPath)
  446. return false;
  447. var aTargets = a.serializedObject.targetObjects;
  448. var bTargets = b.serializedObject.targetObjects;
  449. if (aTargets.Length != bTargets.Length)
  450. return false;
  451. for (int i = 0; i < aTargets.Length; i++)
  452. {
  453. if (aTargets[i] != bTargets[i])
  454. return false;
  455. }
  456. return true;
  457. }
  458. /************************************************************************************************************************/
  459. /// <summary>
  460. /// Executes the `action` once with a new <see cref="SerializedProperty"/> for each of the
  461. /// <see cref="SerializedObject.targetObjects"/>. Or if there is only one target, it uses the `property`.
  462. /// </summary>
  463. public static void ForEachTarget(this SerializedProperty property, Action<SerializedProperty> function,
  464. string undoName = "Inspector")
  465. {
  466. var targets = property.serializedObject.targetObjects;
  467. if (undoName != null)
  468. Undo.RecordObjects(targets, undoName);
  469. if (targets.Length == 1)
  470. {
  471. function(property);
  472. property.serializedObject.ApplyModifiedProperties();
  473. }
  474. else
  475. {
  476. var path = property.propertyPath;
  477. for (int i = 0; i < targets.Length; i++)
  478. {
  479. using var serializedObject = new SerializedObject(targets[i]);
  480. property = serializedObject.FindProperty(path);
  481. function(property);
  482. property.serializedObject.ApplyModifiedProperties();
  483. }
  484. }
  485. }
  486. /************************************************************************************************************************/
  487. /// <summary>
  488. /// Adds a menu item to execute the specified `function` for each of the `property`s target objects.
  489. /// </summary>
  490. public static void AddFunction(this GenericMenu menu, string label, MenuFunctionState state, GenericMenu.MenuFunction function)
  491. {
  492. if (state != MenuFunctionState.Disabled)
  493. {
  494. menu.AddItem(new(label), state == MenuFunctionState.Selected, function);
  495. }
  496. else
  497. {
  498. menu.AddDisabledItem(new(label));
  499. }
  500. }
  501. /// <summary>
  502. /// Adds a menu item to execute the specified `function` for each of the `property`s target objects.
  503. /// </summary>
  504. public static void AddFunction(this GenericMenu menu, string label, bool enabled, GenericMenu.MenuFunction function)
  505. => AddFunction(menu, label, enabled ? MenuFunctionState.Normal : MenuFunctionState.Disabled, function);
  506. /************************************************************************************************************************/
  507. /// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
  508. public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label,
  509. MenuFunctionState state, Action<SerializedProperty> function)
  510. {
  511. if (state != MenuFunctionState.Disabled && GUI.enabled)
  512. {
  513. menu.AddItem(new(label), state == MenuFunctionState.Selected, () =>
  514. {
  515. ForEachTarget(property, function);
  516. GUIUtility.keyboardControl = 0;
  517. GUIUtility.hotControl = 0;
  518. EditorGUIUtility.editingTextField = false;
  519. });
  520. }
  521. else
  522. {
  523. menu.AddDisabledItem(new(label));
  524. }
  525. }
  526. /// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
  527. public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label, bool enabled,
  528. Action<SerializedProperty> function)
  529. => AddPropertyModifierFunction(menu, property, label, enabled ? MenuFunctionState.Normal : MenuFunctionState.Disabled, function);
  530. /// <summary>Adds a menu item to execute the specified `function` for each of the `property`s target objects.</summary>
  531. public static void AddPropertyModifierFunction(this GenericMenu menu, SerializedProperty property, string label,
  532. Action<SerializedProperty> function)
  533. => AddPropertyModifierFunction(menu, property, label, MenuFunctionState.Normal, function);
  534. /************************************************************************************************************************/
  535. /// <summary>
  536. /// Calls the specified `method` for each of the underlying values of the `property` (in case it represents
  537. /// multiple selected objects) and records an undo step for any modifications made.
  538. /// </summary>
  539. public static void ModifyValues<T>(this SerializedProperty property, Action<T> method, string undoName = "Inspector")
  540. {
  541. RecordUndo(property, undoName);
  542. var values = GetValues<T>(property);
  543. for (int i = 0; i < values.Length; i++)
  544. method(values[i]);
  545. OnPropertyChanged(property);
  546. }
  547. /************************************************************************************************************************/
  548. /// <summary>
  549. /// Records the state of the specified `property` so it can be undone.
  550. /// </summary>
  551. public static void RecordUndo(this SerializedProperty property, string undoName = "Inspector")
  552. => Undo.RecordObjects(property.serializedObject.targetObjects, undoName);
  553. /************************************************************************************************************************/
  554. /// <summary>
  555. /// Updates the specified `property` and marks its target objects as dirty so any changes to a prefab will be saved.
  556. /// </summary>
  557. public static void OnPropertyChanged(this SerializedProperty property)
  558. {
  559. var targets = property.serializedObject.targetObjects;
  560. // If this change is made to a prefab, this makes sure that any instances in the scene will be updated.
  561. for (int i = 0; i < targets.Length; i++)
  562. {
  563. EditorUtility.SetDirty(targets[i]);
  564. }
  565. property.serializedObject.Update();
  566. }
  567. /************************************************************************************************************************/
  568. /// <summary>
  569. /// Returns the <see cref="SerializedPropertyType"/> that represents fields of the specified `type`.
  570. /// </summary>
  571. public static SerializedPropertyType GetPropertyType(Type type)
  572. {
  573. // Primitives.
  574. if (type == typeof(bool))
  575. return SerializedPropertyType.Boolean;
  576. if (type == typeof(int))
  577. return SerializedPropertyType.Integer;
  578. if (type == typeof(float))
  579. return SerializedPropertyType.Float;
  580. if (type == typeof(string))
  581. return SerializedPropertyType.String;
  582. if (type == typeof(LayerMask))
  583. return SerializedPropertyType.LayerMask;
  584. // Vectors.
  585. if (type == typeof(Vector2))
  586. return SerializedPropertyType.Vector2;
  587. if (type == typeof(Vector3))
  588. return SerializedPropertyType.Vector3;
  589. if (type == typeof(Vector4))
  590. return SerializedPropertyType.Vector4;
  591. if (type == typeof(Quaternion))
  592. return SerializedPropertyType.Quaternion;
  593. // Other.
  594. if (type == typeof(Color) || type == typeof(Color32))
  595. return SerializedPropertyType.Color;
  596. if (type == typeof(Gradient))
  597. return SerializedPropertyType.Gradient;
  598. if (type == typeof(Rect))
  599. return SerializedPropertyType.Rect;
  600. if (type == typeof(Bounds))
  601. return SerializedPropertyType.Bounds;
  602. if (type == typeof(AnimationCurve))
  603. return SerializedPropertyType.AnimationCurve;
  604. // Int Variants.
  605. if (type == typeof(Vector2Int))
  606. return SerializedPropertyType.Vector2Int;
  607. if (type == typeof(Vector3Int))
  608. return SerializedPropertyType.Vector3Int;
  609. if (type == typeof(RectInt))
  610. return SerializedPropertyType.RectInt;
  611. if (type == typeof(BoundsInt))
  612. return SerializedPropertyType.BoundsInt;
  613. // Special.
  614. if (typeof(Object).IsAssignableFrom(type))
  615. return SerializedPropertyType.ObjectReference;
  616. if (type.IsEnum)
  617. return SerializedPropertyType.Enum;
  618. return SerializedPropertyType.Generic;
  619. }
  620. /************************************************************************************************************************/
  621. /// <summary>Removes the specified array element from the `property`.</summary>
  622. /// <remarks>
  623. /// If the element is not at its default value, the first call to
  624. /// <see cref="SerializedProperty.DeleteArrayElementAtIndex"/> will only reset it, so this method will
  625. /// call it again if necessary to ensure that it actually gets removed.
  626. /// </remarks>
  627. public static void RemoveArrayElement(SerializedProperty property, int index)
  628. {
  629. var count = property.arraySize;
  630. if ((uint)index >= count)
  631. return;
  632. property.DeleteArrayElementAtIndex(index);
  633. if (property.arraySize == count)
  634. property.DeleteArrayElementAtIndex(index);
  635. }
  636. /************************************************************************************************************************/
  637. #endregion
  638. /************************************************************************************************************************/
  639. #region Accessor Pool
  640. /************************************************************************************************************************/
  641. private static readonly Dictionary<Type, Dictionary<string, PropertyAccessor>>
  642. TypeToPathToAccessor = new();
  643. /************************************************************************************************************************/
  644. /// <summary>
  645. /// Returns a <see cref="PropertyAccessor"/> that can be used to access the details of the specified `property`.
  646. /// </summary>
  647. public static PropertyAccessor GetAccessor(this SerializedProperty property)
  648. {
  649. var type = property.serializedObject.targetObject.GetType();
  650. return GetAccessor(property, property.propertyPath, ref type);
  651. }
  652. /************************************************************************************************************************/
  653. /// <summary>
  654. /// Returns a <see cref="PropertyAccessor"/> for a <see cref="SerializedProperty"/> with the specified `propertyPath`
  655. /// on the specified `type` of object.
  656. /// </summary>
  657. private static PropertyAccessor GetAccessor(SerializedProperty property, string propertyPath, ref Type type)
  658. {
  659. if (!TypeToPathToAccessor.TryGetValue(type, out var pathToAccessor))
  660. TypeToPathToAccessor.Add(type, pathToAccessor = new());
  661. if (!pathToAccessor.TryGetValue(propertyPath, out var accessor))
  662. {
  663. var nameStartIndex = propertyPath.LastIndexOf('.');
  664. string elementName;
  665. PropertyAccessor parent;
  666. // Array.
  667. if (nameStartIndex > 6 &&
  668. nameStartIndex < propertyPath.Length - 7 &&
  669. string.Compare(propertyPath, nameStartIndex - 6, ArrayDataPrefix, 0, 12) == 0)
  670. {
  671. var index = int.Parse(propertyPath.Substring(nameStartIndex + 6, propertyPath.Length - nameStartIndex - 7));
  672. var nameEndIndex = nameStartIndex - 6;
  673. nameStartIndex = propertyPath.LastIndexOf('.', nameEndIndex - 1);
  674. elementName = propertyPath.Substring(nameStartIndex + 1, nameEndIndex - nameStartIndex - 1);
  675. FieldInfo field;
  676. if (nameStartIndex >= 0)
  677. {
  678. parent = GetAccessor(property, propertyPath[..nameStartIndex], ref type);
  679. field = GetField(parent, property, type, elementName);
  680. }
  681. else
  682. {
  683. parent = null;
  684. field = GetField(type, elementName);
  685. }
  686. accessor = new CollectionPropertyAccessor(parent, elementName, field, index);
  687. }
  688. else// Single.
  689. {
  690. if (nameStartIndex >= 0)
  691. {
  692. elementName = propertyPath[(nameStartIndex + 1)..];
  693. parent = GetAccessor(property, propertyPath[..nameStartIndex], ref type);
  694. }
  695. else
  696. {
  697. elementName = propertyPath;
  698. parent = null;
  699. }
  700. var field = GetField(parent, property, type, elementName);
  701. accessor = new PropertyAccessor(parent, elementName, field);
  702. }
  703. pathToAccessor.Add(propertyPath, accessor);
  704. }
  705. if (accessor != null)
  706. {
  707. var field = accessor.GetField(property);
  708. if (field != null)
  709. {
  710. type = field.FieldType;
  711. }
  712. else
  713. {
  714. var value = accessor.GetValue(property);
  715. type = value?.GetType();
  716. }
  717. }
  718. return accessor;
  719. }
  720. /************************************************************************************************************************/
  721. /// <summary>Returns a field with the specified `name` in the `declaringType` or any of its base types.</summary>
  722. /// <remarks>Uses the <see cref="InstanceBindings"/>.</remarks>
  723. public static FieldInfo GetField(PropertyAccessor accessor, SerializedProperty property, Type declaringType, string name)
  724. {
  725. declaringType = accessor?.GetFieldElementType(property) ?? declaringType;
  726. return GetField(declaringType, name);
  727. }
  728. /// <summary>Returns a field with the specified `name` in the `declaringType` or any of its base types.</summary>
  729. /// <remarks>Uses the <see cref="InstanceBindings"/>.</remarks>
  730. public static FieldInfo GetField(Type declaringType, string name)
  731. {
  732. while (declaringType != null)
  733. {
  734. var field = declaringType.GetField(name, InstanceBindings);
  735. if (field != null)
  736. return field;
  737. declaringType = declaringType.BaseType;
  738. }
  739. return null;
  740. }
  741. /************************************************************************************************************************/
  742. #endregion
  743. /************************************************************************************************************************/
  744. #region PropertyAccessor
  745. /************************************************************************************************************************/
  746. /// <summary>[Editor-Only]
  747. /// A wrapper for accessing the underlying values and fields of a <see cref="SerializedProperty"/>.
  748. /// </summary>
  749. public class PropertyAccessor
  750. {
  751. /************************************************************************************************************************/
  752. /// <summary>The accessor for the field which this accessor is nested inside.</summary>
  753. public readonly PropertyAccessor Parent;
  754. /// <summary>The name of the field wrapped by this accessor.</summary>
  755. public readonly string Name;
  756. /// <summary>The field wrapped by this accessor.</summary>
  757. protected readonly FieldInfo Field;
  758. /// <summary>
  759. /// The type of the wrapped <see cref="Field"/>.
  760. /// Or if it's a collection, this is the type of items in the collection.
  761. /// </summary>
  762. protected readonly Type FieldElementType;
  763. /// <summary>
  764. /// Does the <see cref="Field"/> in this accessor or any <see cref="Parent"/> have a
  765. /// <see cref="SerializeReference"/> attribute?
  766. /// </summary>
  767. public readonly bool IsDynamic;
  768. /************************************************************************************************************************/
  769. /// <summary>[Internal] Creates a new <see cref="PropertyAccessor"/>.</summary>
  770. internal PropertyAccessor(PropertyAccessor parent, string name, FieldInfo field)
  771. : this(parent, name, field, field?.FieldType)
  772. { }
  773. /// <summary>Creates a new <see cref="PropertyAccessor"/>.</summary>
  774. protected PropertyAccessor(PropertyAccessor parent, string name, FieldInfo field, Type fieldElementType)
  775. {
  776. Parent = parent;
  777. Name = name;
  778. if ((parent != null && parent.IsDynamic) ||
  779. field == null ||
  780. field.IsDefined(typeof(SerializeReference), false))
  781. {
  782. IsDynamic = true;
  783. return;
  784. }
  785. Field = field;
  786. FieldElementType = fieldElementType;
  787. }
  788. /************************************************************************************************************************/
  789. /// <summary>Returns the <see cref="Field"/> if there is one or tries to get it from the object's type.</summary>
  790. ///
  791. /// <remarks>
  792. /// If this accessor has a <see cref="Parent"/>, the `obj` must be associated with the root
  793. /// <see cref="SerializedProperty"/> and this method will change it to reference the parent field's value.
  794. /// </remarks>
  795. ///
  796. /// <example><code>
  797. /// [Serializable]
  798. /// public class InnerClass
  799. /// {
  800. /// public float value;
  801. /// }
  802. ///
  803. /// [Serializable]
  804. /// public class RootClass
  805. /// {
  806. /// public InnerClass inner;
  807. /// }
  808. ///
  809. /// public class MyBehaviour : MonoBehaviour
  810. /// {
  811. /// public RootClass root;
  812. /// }
  813. ///
  814. /// [UnityEditor.CustomEditor(typeof(MyBehaviour))]
  815. /// public class MyEditor : UnityEditor.Editor
  816. /// {
  817. /// private void OnEnable()
  818. /// {
  819. /// var serializedObject = new SerializedObject(target);
  820. /// var rootProperty = serializedObject.FindProperty("root");
  821. /// var innerProperty = rootProperty.FindPropertyRelative("inner");
  822. /// var valueProperty = innerProperty.FindPropertyRelative("value");
  823. ///
  824. /// var accessor = valueProperty.GetAccessor();
  825. ///
  826. /// object obj = target;
  827. /// var valueField = accessor.GetField(ref obj);
  828. /// // valueField is a FieldInfo referring to InnerClass.value.
  829. /// // obj now holds the ((MyBehaviour)target).root.inner.
  830. /// }
  831. /// }
  832. /// </code></example>
  833. ///
  834. public FieldInfo GetField(ref object obj)
  835. {
  836. if (Parent != null)
  837. obj = Parent.GetValue(obj);
  838. if (Field != null)
  839. return Field;
  840. if (obj is null)
  841. return null;
  842. return Serialization.GetField(obj.GetType(), Name);
  843. }
  844. /// <summary>
  845. /// Returns the <see cref="Field"/> if there is one, otherwise calls <see cref="GetField(ref object)"/>.
  846. /// </summary>
  847. public FieldInfo GetField(object obj)
  848. => Field ?? GetField(ref obj);
  849. /// <summary>
  850. /// Calls <see cref="GetField(object)"/> with the <see cref="SerializedObject.targetObject"/>.
  851. /// </summary>
  852. public FieldInfo GetField(SerializedObject serializedObject)
  853. => serializedObject != null ? GetField(serializedObject.targetObject) : null;
  854. /// <summary>
  855. /// Calls <see cref="GetField(SerializedObject)"/> with the
  856. /// <see cref="SerializedProperty.serializedObject"/>.
  857. /// </summary>
  858. public FieldInfo GetField(SerializedProperty serializedProperty)
  859. => serializedProperty != null ? GetField(serializedProperty.serializedObject) : null;
  860. /************************************************************************************************************************/
  861. /// <summary>
  862. /// Returns the <see cref="FieldElementType"/> if there is one, otherwise calls <see cref="GetField(ref object)"/>
  863. /// and returns its <see cref="FieldInfo.FieldType"/>.
  864. /// </summary>
  865. public virtual Type GetFieldElementType(object obj)
  866. => FieldElementType ?? GetField(ref obj)?.FieldType;
  867. /// <summary>
  868. /// Calls <see cref="GetFieldElementType(object)"/> with the
  869. /// <see cref="SerializedObject.targetObject"/>.
  870. /// </summary>
  871. public Type GetFieldElementType(SerializedObject serializedObject)
  872. => serializedObject != null ? GetFieldElementType(serializedObject.targetObject) : null;
  873. /// <summary>
  874. /// Calls <see cref="GetFieldElementType(SerializedObject)"/> with the
  875. /// <see cref="SerializedProperty.serializedObject"/>.
  876. /// </summary>
  877. public Type GetFieldElementType(SerializedProperty serializedProperty)
  878. => serializedProperty != null ? GetFieldElementType(serializedProperty.serializedObject) : null;
  879. /************************************************************************************************************************/
  880. /// <summary>
  881. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
  882. /// the value of the <see cref="Field"/>.
  883. /// </summary>
  884. public virtual object GetValue(object obj)
  885. {
  886. var field = GetField(ref obj);
  887. if (field is null ||
  888. (obj is null && !field.IsStatic))
  889. return null;
  890. return field.GetValue(obj);
  891. }
  892. /// <summary>
  893. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
  894. /// the value of the <see cref="Field"/>.
  895. /// </summary>
  896. public object GetValue(SerializedObject serializedObject)
  897. => serializedObject != null ? GetValue(serializedObject.targetObject) : null;
  898. /// <summary>
  899. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to get and return
  900. /// the value of the <see cref="Field"/>.
  901. /// </summary>
  902. public object GetValue(SerializedProperty serializedProperty)
  903. => serializedProperty != null ? GetValue(serializedProperty.serializedObject.targetObject) : null;
  904. /************************************************************************************************************************/
  905. /// <summary>
  906. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
  907. /// of the <see cref="Field"/>.
  908. /// </summary>
  909. public virtual void SetValue(object obj, object value)
  910. {
  911. var field = GetField(ref obj);
  912. if (field is null ||
  913. obj is null)
  914. return;
  915. field.SetValue(obj, value);
  916. }
  917. /// <summary>
  918. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
  919. /// of the <see cref="Field"/>.
  920. /// </summary>
  921. public void SetValue(SerializedObject serializedObject, object value)
  922. {
  923. if (serializedObject != null)
  924. SetValue(serializedObject.targetObject, value);
  925. }
  926. /// <summary>
  927. /// Gets the value of the from the <see cref="Parent"/> (if there is one), then uses it to set the value
  928. /// of the <see cref="Field"/>.
  929. /// </summary>
  930. public void SetValue(SerializedProperty serializedProperty, object value)
  931. {
  932. if (serializedProperty != null)
  933. SetValue(serializedProperty.serializedObject, value);
  934. }
  935. /************************************************************************************************************************/
  936. /// <summary>
  937. /// Resets the value of the <see cref="SerializedProperty"/> to the default value of its type by executing
  938. /// its constructor and field initializers.
  939. /// </summary>
  940. /// <remarks>
  941. /// If you don't want to run constructors and field initializers, you can call
  942. /// <see cref="Serialization.ResetValue"/> instead.
  943. /// </remarks>
  944. /// <example><code>
  945. /// SerializedProperty property;
  946. /// property.GetAccessor().ResetValue(property);
  947. /// </code></example>
  948. public void ResetValue(SerializedProperty property, string undoName = "Inspector")
  949. {
  950. property.RecordUndo(undoName);
  951. property.serializedObject.ApplyModifiedProperties();
  952. var type = GetValue(property)?.GetType();
  953. var value = type != null ? Activator.CreateInstance(type) : null;
  954. SetValue(property, value);
  955. property.serializedObject.Update();
  956. }
  957. /************************************************************************************************************************/
  958. /// <summary>Returns a description of this accessor's path.</summary>
  959. public override string ToString()
  960. {
  961. if (Parent != null)
  962. return $"{Parent}.{Name}";
  963. else
  964. return Name;
  965. }
  966. /************************************************************************************************************************/
  967. /// <summary>Returns this accessor's <see cref="SerializedProperty.propertyPath"/>.</summary>
  968. public virtual string GetPath()
  969. {
  970. if (Parent != null)
  971. return $"{Parent.GetPath()}.{Name}";
  972. else
  973. return Name;
  974. }
  975. /************************************************************************************************************************/
  976. }
  977. /************************************************************************************************************************/
  978. #endregion
  979. /************************************************************************************************************************/
  980. #region CollectionPropertyAccessor
  981. /************************************************************************************************************************/
  982. /// <summary>[Editor-Only] A <see cref="PropertyAccessor"/> for a specific element index in a collection.</summary>
  983. public class CollectionPropertyAccessor : PropertyAccessor
  984. {
  985. /************************************************************************************************************************/
  986. /// <summary>The index of the array element this accessor targets.</summary>
  987. public readonly int ElementIndex;
  988. /************************************************************************************************************************/
  989. /// <summary>[Internal] Creates a new <see cref="CollectionPropertyAccessor"/>.</summary>
  990. internal CollectionPropertyAccessor(PropertyAccessor parent, string name, FieldInfo field, int elementIndex)
  991. : base(parent, name, field, GetElementType(field?.FieldType))
  992. {
  993. ElementIndex = elementIndex;
  994. }
  995. /************************************************************************************************************************/
  996. /// <inheritdoc/>
  997. public override Type GetFieldElementType(object obj)
  998. => FieldElementType ?? GetElementType(GetField(ref obj)?.FieldType);
  999. /************************************************************************************************************************/
  1000. /// <summary>Returns the type of elements in the array.</summary>
  1001. public static Type GetElementType(Type fieldType)
  1002. {
  1003. if (fieldType == null)
  1004. return null;
  1005. if (fieldType.IsArray)
  1006. return fieldType.GetElementType();
  1007. if (fieldType.IsGenericType)
  1008. return fieldType.GetGenericArguments()[0];
  1009. Debug.LogWarning($"{nameof(Serialization)}.{nameof(CollectionPropertyAccessor)}:" +
  1010. $" unable to determine element type for {fieldType}");
  1011. return fieldType;
  1012. }
  1013. /************************************************************************************************************************/
  1014. /// <summary>Returns the collection object targeted by this accessor.</summary>
  1015. public object GetCollection(object obj)
  1016. => base.GetValue(obj);
  1017. /// <inheritdoc/>
  1018. public override object GetValue(object obj)
  1019. {
  1020. var collection = base.GetValue(obj);
  1021. if (collection == null)
  1022. return null;
  1023. if (collection is IList list)
  1024. return ElementIndex < list.Count ? list[ElementIndex] : null;
  1025. var enumerator = ((IEnumerable)collection).GetEnumerator();
  1026. for (int i = 0; i < ElementIndex; i++)
  1027. if (!enumerator.MoveNext())
  1028. return null;
  1029. return enumerator.Current;
  1030. }
  1031. /************************************************************************************************************************/
  1032. /// <summary>Sets the collection object targeted by this accessor.</summary>
  1033. public void SetCollection(object obj, object value)
  1034. => base.SetValue(obj, value);
  1035. /// <inheritdoc/>
  1036. public override void SetValue(object obj, object value)
  1037. {
  1038. var collection = base.GetValue(obj);
  1039. if (collection == null)
  1040. return;
  1041. if (collection is IList list)
  1042. {
  1043. if (ElementIndex < list.Count)
  1044. list[ElementIndex] = value;
  1045. return;
  1046. }
  1047. throw new InvalidOperationException(
  1048. $"{nameof(SetValue)} failed: field doesn't implement {nameof(IList)}.");
  1049. }
  1050. /************************************************************************************************************************/
  1051. /// <summary>Returns a description of this accessor's path.</summary>
  1052. public override string ToString()
  1053. => $"{base.ToString()}[{ElementIndex}]";
  1054. /************************************************************************************************************************/
  1055. /// <summary>Returns the <see cref="SerializedProperty.propertyPath"/> of the array containing the target.</summary>
  1056. public string GetCollectionPath()
  1057. => base.GetPath();
  1058. /// <summary>Returns this accessor's <see cref="SerializedProperty.propertyPath"/>.</summary>
  1059. public override string GetPath()
  1060. => $"{base.GetPath()}{ArrayDataPrefix}{ElementIndex}{ArrayDataSuffix}";
  1061. /************************************************************************************************************************/
  1062. }
  1063. /************************************************************************************************************************/
  1064. #endregion
  1065. /************************************************************************************************************************/
  1066. }
  1067. }
  1068. #endif