A game about forced loneliness, made by TACStudios
at master 251 lines 13 kB view raw
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}