A game about forced loneliness, made by TACStudios
1using System;
2
3namespace UnityEditor.Rendering
4{
5 /// <summary>Serialisation of BitArray, Utility class</summary>
6 public static partial class SerializedBitArrayUtilities
7 {
8 /// <summary>Construct a SerializedBitArrayAny of appropriate size</summary>
9 /// <param name="serializedProperty">The SerializedProperty</param>
10 /// <param name="targetSerializedObjects">An individual SerializedObject for each targetObject</param>
11 /// <returns>A SerializedBitArrayAny</returns>
12 public static SerializedBitArrayAny ToSerializedBitArray(this SerializedProperty serializedProperty, SerializedObject[] targetSerializedObjects)
13 {
14 if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
15 throw new Exception("Cannot get SerializeBitArray's Capacity");
16 return new SerializedBitArrayAny(serializedProperty, targetSerializedObjects, capacity);
17 }
18
19 /// <summary>Construct a SerializedBitArrayAny of appropriate size</summary>
20 /// <param name="serializedProperty">The SerializedProperty</param>
21 /// <returns>A SerializedBitArrayAny</returns>
22 /// <remarks>Note that this variant doesn't properly support editing multiple targets, especially if there
23 /// are several BitArrays on the target objects.</remarks>
24 public static SerializedBitArrayAny ToSerializedBitArray(this SerializedProperty serializedProperty)
25 {
26 if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
27 throw new Exception("Cannot get SerializeBitArray's Capacity");
28 return new SerializedBitArrayAny(serializedProperty, capacity);
29 }
30
31 /// <summary>Try to construct a SerializedBitArray of appropriate size</summary>
32 /// <param name="serializedProperty">The SerializedProperty</param>
33 /// <param name="targetSerializedObjects">An individual SerializedObject for each targetObject</param>
34 /// <param name="serializedBitArray">Out SerializedBitArray</param>
35 /// <returns>True if construction was successful</returns>
36 public static bool TryGetSerializedBitArray(this SerializedProperty serializedProperty, SerializedObject[] targetSerializedObjects, out SerializedBitArrayAny serializedBitArray)
37 {
38 serializedBitArray = null;
39 if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
40 return false;
41 serializedBitArray = new SerializedBitArrayAny(serializedProperty, targetSerializedObjects, capacity);
42 return true;
43 }
44
45 /// <summary>Try to construct a SerializedBitArray of appropriate size</summary>
46 /// <param name="serializedProperty">The SerializedProperty</param>
47 /// <param name="serializedBitArray">Out SerializedBitArray</param>
48 /// <returns>True if construction was successful</returns>
49 /// <remarks>Note that this variant doesn't properly support editing multiple targets, especially if there
50 /// are several BitArrays on the target objects.</remarks>
51 public static bool TryGetSerializedBitArray(this SerializedProperty serializedProperty, out SerializedBitArrayAny serializedBitArray)
52 {
53 serializedBitArray = null;
54 if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
55 return false;
56 serializedBitArray = new SerializedBitArrayAny(serializedProperty, capacity);
57 return true;
58 }
59
60 static bool TryGetCapacityFromTypeName(SerializedProperty serializedProperty, out uint capacity)
61 {
62 capacity = 0u;
63 const string baseTypeName = "BitArray";
64 string type = serializedProperty.type;
65 return type.StartsWith(baseTypeName)
66 && uint.TryParse(type.Substring(baseTypeName.Length), out capacity);
67 }
68 }
69
70 /// <summary>interface to handle generic SerializedBitArray</summary>
71 public interface ISerializedBitArray
72 {
73 /// <summary>Capacity of the bitarray</summary>
74 uint capacity { get; }
75 /// <summary>Get the bit at given index</summary>
76 /// <param name="bitIndex">The index</param>
77 /// <returns>Bit value</returns>
78 bool GetBitAt(uint bitIndex);
79 /// <summary>Set the bit at given index</summary>
80 /// <param name="bitIndex">The index</param>
81 /// <param name="value">The value</param>
82 void SetBitAt(uint bitIndex, bool value);
83 /// <summary>Does the bit at given index have multiple different values?</summary>
84 /// <param name="bitIndex">The index</param>
85 /// <returns>True: Multiple different value</returns>
86 bool HasBitMultipleDifferentValue(uint bitIndex);
87 }
88
89 /// <summary>Base class for SerializedBitArrays</summary>
90 public sealed class SerializedBitArrayAny : ISerializedBitArray
91 {
92 //To only be used for bit isolation as internal operator only support 32 bit formats.
93 //Why:
94 // - We cannot access easily the root of the BitArray from the target (most of the time there is a non null serialization path to take)
95 // - The BitArray is a struct making any reflection process to access it difficult especially if we want to modify a bit and not just read (potential copy issue)
96 // - We are in Editor only here so we can use Unity serialization to do this access, using a bunch of SerializedProperty. We just need to keep them in sync.
97 // - For the bit isolation:
98 // - If we want to isolate and only work on 1 bit, we want to modify it, whatever the data was in other bits.
99 // - If the data on other bits was different per targets beffore writting on 1 bit, it should still be different per targets.
100 // - Internal method HasMultipleDifferentValuesBitwise and SetBitAtIndexForAllTargetsImmediate is only supported for 32bits formats.
101 //Todo: Ideally, if we move this BitArray to Unity, we can rewrite a little the HasMultipleDifferentValuesBitwise and SetBitAtIndexForAllTargetsImmediate to work on other format and thus we should not need this m_SerializedPropertyPerTargets anymore.
102 SerializedProperty[] m_SerializedPropertyPerTargets;
103
104 /// <summary>Capacity of the bitarray</summary>
105 public uint capacity { get; }
106
107 internal SerializedBitArrayAny(SerializedProperty serializedProperty, SerializedObject[] targetSerializedObjects, uint capacity)
108 {
109 this.capacity = capacity;
110 m_SerializedPropertyPerTargets = new SerializedProperty[targetSerializedObjects.Length];
111 for (int i = 0; i < targetSerializedObjects.Length; i++)
112 {
113 m_SerializedPropertyPerTargets[i] = targetSerializedObjects[i].FindProperty(serializedProperty.propertyPath);
114 }
115 }
116
117 // Old constructor that doesn't properly work with multiple target objects.
118 internal SerializedBitArrayAny(SerializedProperty serializedProperty, uint capacity)
119 {
120 this.capacity = capacity;
121 m_SerializedPropertyPerTargets = new SerializedProperty[serializedProperty.serializedObject.targetObjects.Length];
122 for (int i = 0; i < serializedProperty.serializedObject.targetObjects.Length; i++)
123 {
124 m_SerializedPropertyPerTargets[i] = new SerializedObject(serializedProperty.serializedObject.targetObjects[i]).FindProperty(serializedProperty.propertyPath);
125 }
126 }
127
128 //To update if we need container over 256bits
129 ulong GetTargetValueUnverified(int targetIndex, int part)
130 => part switch
131 {
132 0 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative(capacity <= 64 ? "data" : "data1").boxedValue),
133 1 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data2")?.boxedValue ?? 0ul),
134 2 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data3")?.boxedValue ?? 0ul),
135 3 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data4")?.boxedValue ?? 0ul),
136 _ => 0ul
137 };
138
139 void SetTargetValueUnverified(int targetIndex, int part, object value)
140 {
141 switch (part)
142 {
143 case 0: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative(capacity <= 64 ? "data" : "data1").boxedValue = value; break;
144 case 1: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data2").boxedValue = value; break;
145 case 2: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data3").boxedValue = value; break;
146 case 3: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data4").boxedValue = value; break;
147 };
148 }
149
150 //we cannot directly cast from boxed value to the ulong we want in C#, We first need to unbox in the true type
151 ulong Unbox(object boxedValue)
152 => capacity switch
153 {
154 8 => (byte)boxedValue,
155 16 => (ushort)boxedValue,
156 32 => (uint)boxedValue,
157 64 => (ulong)boxedValue,
158 _ => (ulong)boxedValue //any higher is a composition of ulong
159 };
160
161 bool ExtractBitFrom64BitsPart(ulong part, uint bitIndexInPart)
162 => (part & (1ul << (int) (bitIndexInPart % 64))) != 0;
163
164 void AssertInRange(uint bitIndex)
165 {
166 if (bitIndex >= capacity)
167 throw new IndexOutOfRangeException("Index out of bound in BitArray" + capacity);
168 }
169
170 /// <summary>Does the bit at given index have multiple different values?</summary>
171 /// <param name="partIndex">The index of the 64bits bucket to check</param>
172 /// <returns>Bitwise discrepancy over 64 bits. If 1 : multiple different value for this bit.</returns>
173 ulong HasBitMultipleDifferentValueBitwiseOver64Bits(int partIndex)
174 {
175 ulong diff = 0ul;
176 var firstValue = GetTargetValueUnverified(0, partIndex);
177 for (int i = 1; i < m_SerializedPropertyPerTargets.Length; ++i)
178 diff |= firstValue ^ GetTargetValueUnverified(i, partIndex);
179 return diff;
180 }
181
182 /// <summary>Does the bit at given index have multiple different values</summary>
183 /// <param name="bitIndex">The index</param>
184 /// <returns>True: Multiple different value for the given bit index</returns>
185 public bool HasBitMultipleDifferentValue(uint bitIndex)
186 {
187 AssertInRange(bitIndex);
188
189 return ExtractBitFrom64BitsPart(HasBitMultipleDifferentValueBitwiseOver64Bits((int)bitIndex / 64), bitIndex % 64);
190 }
191
192 /// <summary>Get the bit at given index</summary>
193 /// <param name="bitIndex">The index</param>
194 /// <returns>Bit value</returns>
195 public bool GetBitAt(uint bitIndex)
196 {
197 AssertInRange(bitIndex);
198 if (HasBitMultipleDifferentValue(bitIndex))
199 return default; //we cannot assess anything if different
200
201 //As no different value on this bit, lets use the one from first target
202 return ExtractBitFrom64BitsPart(GetTargetValueUnverified(0, (int)bitIndex / 64), bitIndex % 64);
203 }
204
205 /// <summary>Set the bit at given index</summary>
206 /// <param name="bitIndex">The index</param>
207 /// <param name="value">The value</param>
208 public void SetBitAt(uint bitIndex, bool value)
209 {
210 // Update the serialized object to make sure we have the latest values.
211 Update();
212
213 AssertInRange(bitIndex);
214
215 int part = (int)bitIndex / 64;
216 int indexInPart = (int)bitIndex % 64;
217 for (int i = 0; i < m_SerializedPropertyPerTargets.Length; ++i)
218 {
219 ulong targetValue = GetTargetValueUnverified(i, part);
220 if (value)
221 targetValue |= 1ul << indexInPart;
222 else
223 targetValue &= ~(1ul << indexInPart);
224 SetTargetValueUnverified(i, part, targetValue);
225 }
226
227 ResyncSerialization();
228 }
229
230 /// <summary>Sync again every serializedProperty</summary>
231 void ResyncSerialization()
232 {
233 ApplyModifiedProperties();
234 Update();
235 }
236
237 /// <summary>Sync the reflected value with target value change</summary>
238 public void Update()
239 {
240 foreach (var property in m_SerializedPropertyPerTargets)
241 property.serializedObject.Update();
242 }
243
244 /// <summary>Apply the reflected value onto targets</summary>
245 public void ApplyModifiedProperties()
246 {
247 foreach (var property in m_SerializedPropertyPerTargets)
248 property.serializedObject.ApplyModifiedProperties();
249 }
250 }
251}