using System;
namespace UnityEditor.Rendering
{
/// Serialisation of BitArray, Utility class
public static partial class SerializedBitArrayUtilities
{
/// Construct a SerializedBitArrayAny of appropriate size
/// The SerializedProperty
/// An individual SerializedObject for each targetObject
/// A SerializedBitArrayAny
public static SerializedBitArrayAny ToSerializedBitArray(this SerializedProperty serializedProperty, SerializedObject[] targetSerializedObjects)
{
if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
throw new Exception("Cannot get SerializeBitArray's Capacity");
return new SerializedBitArrayAny(serializedProperty, targetSerializedObjects, capacity);
}
/// Construct a SerializedBitArrayAny of appropriate size
/// The SerializedProperty
/// A SerializedBitArrayAny
/// Note that this variant doesn't properly support editing multiple targets, especially if there
/// are several BitArrays on the target objects.
public static SerializedBitArrayAny ToSerializedBitArray(this SerializedProperty serializedProperty)
{
if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
throw new Exception("Cannot get SerializeBitArray's Capacity");
return new SerializedBitArrayAny(serializedProperty, capacity);
}
/// Try to construct a SerializedBitArray of appropriate size
/// The SerializedProperty
/// An individual SerializedObject for each targetObject
/// Out SerializedBitArray
/// True if construction was successful
public static bool TryGetSerializedBitArray(this SerializedProperty serializedProperty, SerializedObject[] targetSerializedObjects, out SerializedBitArrayAny serializedBitArray)
{
serializedBitArray = null;
if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
return false;
serializedBitArray = new SerializedBitArrayAny(serializedProperty, targetSerializedObjects, capacity);
return true;
}
/// Try to construct a SerializedBitArray of appropriate size
/// The SerializedProperty
/// Out SerializedBitArray
/// True if construction was successful
/// Note that this variant doesn't properly support editing multiple targets, especially if there
/// are several BitArrays on the target objects.
public static bool TryGetSerializedBitArray(this SerializedProperty serializedProperty, out SerializedBitArrayAny serializedBitArray)
{
serializedBitArray = null;
if (!TryGetCapacityFromTypeName(serializedProperty, out uint capacity))
return false;
serializedBitArray = new SerializedBitArrayAny(serializedProperty, capacity);
return true;
}
static bool TryGetCapacityFromTypeName(SerializedProperty serializedProperty, out uint capacity)
{
capacity = 0u;
const string baseTypeName = "BitArray";
string type = serializedProperty.type;
return type.StartsWith(baseTypeName)
&& uint.TryParse(type.Substring(baseTypeName.Length), out capacity);
}
}
/// interface to handle generic SerializedBitArray
public interface ISerializedBitArray
{
/// Capacity of the bitarray
uint capacity { get; }
/// Get the bit at given index
/// The index
/// Bit value
bool GetBitAt(uint bitIndex);
/// Set the bit at given index
/// The index
/// The value
void SetBitAt(uint bitIndex, bool value);
/// Does the bit at given index have multiple different values?
/// The index
/// True: Multiple different value
bool HasBitMultipleDifferentValue(uint bitIndex);
}
/// Base class for SerializedBitArrays
public sealed class SerializedBitArrayAny : ISerializedBitArray
{
//To only be used for bit isolation as internal operator only support 32 bit formats.
//Why:
// - 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)
// - 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)
// - 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.
// - For the bit isolation:
// - If we want to isolate and only work on 1 bit, we want to modify it, whatever the data was in other bits.
// - If the data on other bits was different per targets beffore writting on 1 bit, it should still be different per targets.
// - Internal method HasMultipleDifferentValuesBitwise and SetBitAtIndexForAllTargetsImmediate is only supported for 32bits formats.
//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.
SerializedProperty[] m_SerializedPropertyPerTargets;
/// Capacity of the bitarray
public uint capacity { get; }
internal SerializedBitArrayAny(SerializedProperty serializedProperty, SerializedObject[] targetSerializedObjects, uint capacity)
{
this.capacity = capacity;
m_SerializedPropertyPerTargets = new SerializedProperty[targetSerializedObjects.Length];
for (int i = 0; i < targetSerializedObjects.Length; i++)
{
m_SerializedPropertyPerTargets[i] = targetSerializedObjects[i].FindProperty(serializedProperty.propertyPath);
}
}
// Old constructor that doesn't properly work with multiple target objects.
internal SerializedBitArrayAny(SerializedProperty serializedProperty, uint capacity)
{
this.capacity = capacity;
m_SerializedPropertyPerTargets = new SerializedProperty[serializedProperty.serializedObject.targetObjects.Length];
for (int i = 0; i < serializedProperty.serializedObject.targetObjects.Length; i++)
{
m_SerializedPropertyPerTargets[i] = new SerializedObject(serializedProperty.serializedObject.targetObjects[i]).FindProperty(serializedProperty.propertyPath);
}
}
//To update if we need container over 256bits
ulong GetTargetValueUnverified(int targetIndex, int part)
=> part switch
{
0 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative(capacity <= 64 ? "data" : "data1").boxedValue),
1 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data2")?.boxedValue ?? 0ul),
2 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data3")?.boxedValue ?? 0ul),
3 => Unbox(m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data4")?.boxedValue ?? 0ul),
_ => 0ul
};
void SetTargetValueUnverified(int targetIndex, int part, object value)
{
switch (part)
{
case 0: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative(capacity <= 64 ? "data" : "data1").boxedValue = value; break;
case 1: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data2").boxedValue = value; break;
case 2: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data3").boxedValue = value; break;
case 3: m_SerializedPropertyPerTargets[targetIndex].FindPropertyRelative("data4").boxedValue = value; break;
};
}
//we cannot directly cast from boxed value to the ulong we want in C#, We first need to unbox in the true type
ulong Unbox(object boxedValue)
=> capacity switch
{
8 => (byte)boxedValue,
16 => (ushort)boxedValue,
32 => (uint)boxedValue,
64 => (ulong)boxedValue,
_ => (ulong)boxedValue //any higher is a composition of ulong
};
bool ExtractBitFrom64BitsPart(ulong part, uint bitIndexInPart)
=> (part & (1ul << (int) (bitIndexInPart % 64))) != 0;
void AssertInRange(uint bitIndex)
{
if (bitIndex >= capacity)
throw new IndexOutOfRangeException("Index out of bound in BitArray" + capacity);
}
/// Does the bit at given index have multiple different values?
/// The index of the 64bits bucket to check
/// Bitwise discrepancy over 64 bits. If 1 : multiple different value for this bit.
ulong HasBitMultipleDifferentValueBitwiseOver64Bits(int partIndex)
{
ulong diff = 0ul;
var firstValue = GetTargetValueUnverified(0, partIndex);
for (int i = 1; i < m_SerializedPropertyPerTargets.Length; ++i)
diff |= firstValue ^ GetTargetValueUnverified(i, partIndex);
return diff;
}
/// Does the bit at given index have multiple different values
/// The index
/// True: Multiple different value for the given bit index
public bool HasBitMultipleDifferentValue(uint bitIndex)
{
AssertInRange(bitIndex);
return ExtractBitFrom64BitsPart(HasBitMultipleDifferentValueBitwiseOver64Bits((int)bitIndex / 64), bitIndex % 64);
}
/// Get the bit at given index
/// The index
/// Bit value
public bool GetBitAt(uint bitIndex)
{
AssertInRange(bitIndex);
if (HasBitMultipleDifferentValue(bitIndex))
return default; //we cannot assess anything if different
//As no different value on this bit, lets use the one from first target
return ExtractBitFrom64BitsPart(GetTargetValueUnverified(0, (int)bitIndex / 64), bitIndex % 64);
}
/// Set the bit at given index
/// The index
/// The value
public void SetBitAt(uint bitIndex, bool value)
{
// Update the serialized object to make sure we have the latest values.
Update();
AssertInRange(bitIndex);
int part = (int)bitIndex / 64;
int indexInPart = (int)bitIndex % 64;
for (int i = 0; i < m_SerializedPropertyPerTargets.Length; ++i)
{
ulong targetValue = GetTargetValueUnverified(i, part);
if (value)
targetValue |= 1ul << indexInPart;
else
targetValue &= ~(1ul << indexInPart);
SetTargetValueUnverified(i, part, targetValue);
}
ResyncSerialization();
}
/// Sync again every serializedProperty
void ResyncSerialization()
{
ApplyModifiedProperties();
Update();
}
/// Sync the reflected value with target value change
public void Update()
{
foreach (var property in m_SerializedPropertyPerTargets)
property.serializedObject.Update();
}
/// Apply the reflected value onto targets
public void ApplyModifiedProperties()
{
foreach (var property in m_SerializedPropertyPerTargets)
property.serializedObject.ApplyModifiedProperties();
}
}
}