// As properties iterates over containers, if the container doesn't default construct it's fields but we // have serialized out field data for those containers check for that data (absence of our null sentinel) // and then return a default constructed container to be filled by said data unsafe bool InitializeNullIfFieldNotNull <TValue>(ref TValue value) { bool isActuallyNull = true; var oldOffset = _PrimitiveReader.Buffer->Offset; var sentinel = _PrimitiveReader.Buffer->ReadNext <uint>(); if (sentinel != kMagicNull) { isActuallyNull = false; if (sentinel == kMagicPolymorphic) { var typeName = new PolymorphicTypeName(); PropertyContainer.Visit(ref typeName, this); var concreteType = Type.GetType(typeName.Name); value = (TValue)Activator.CreateInstance(concreteType); } else { // We didn't read a sentinel value so reset the offset to what it was before _PrimitiveReader.Buffer->Offset = oldOffset; value = Activator.CreateInstance <TValue>(); } } return(isActuallyNull); }
protected override VisitStatus BeginCollection <TProperty, TContainer, TValue> (TProperty property, ref TContainer container, ref TValue value, ref ChangeTracker changeTracker) { _ProcessingCollectionStack.Push(true); if (null == value) { Buffer.Add(kMagicNull); return(VisitStatus.Override); } // Write out size of list required for deserializing later var count = property.GetCount(ref container); Buffer.Add(count); // If the collection contains polymorphic types, write out all the concrete names to the buffer // as we will reconstruct the list elements upon deserialization _before_ reading in the element's // actual values (current restriction in Properties), so we need to know the element types up front // Since a collection could be a List<SomeBaseClass> and each element a concrete type, we need to check // all the elements to see if recording the element names is required. var elementType = typeof(TValue); if (elementType.IsArray) { elementType = elementType.GetElementType(); } else if (elementType.IsGenericType) { elementType = elementType.GetGenericArguments()[0]; } else { throw new ArgumentException($"Collection type {typeof(TValue)} is not supported."); } bool shouldGetTypeNames = false; var list = (IList)value; for (int i = 0; i < count; ++i) { var element = list[i]; if (element == null) { continue; } var concreteType = element.GetType(); if (concreteType != elementType) { shouldGetTypeNames = true; break; } } if (shouldGetTypeNames) { // Push a sentinel to know when deserializing if we should read the list element names or not Buffer.Add(kMagicPolymorphic); for (int i = 0; i < count; ++i) { var element = list[i]; if (element == null) { Buffer.Add(kMagicNull); } else { var concreteType = element.GetType(); var typeName = new PolymorphicTypeName() { Name = concreteType.AssemblyQualifiedName }; PropertyContainer.Visit(ref typeName, this); } } } return(VisitStatus.Handled); }
unsafe protected override VisitStatus BeginCollection <TProperty, TContainer, TValue> (TProperty property, ref TContainer container, ref TValue value, ref ChangeTracker changeTracker) { _ProcessingCollectionStack.Push(true); if (CheckSentinel(_PrimitiveReader, kMagicNull)) { return(VisitStatus.Override); } // Unity.Properties doesn't really support class types so we need to workaround // this issue for now by prefilling our list with instances which will be later replaced. // Properties assumes a value type (non-null) value will already be there to write // and if not will try to create a default value (which will be null for class types) _PrimitiveReader.Buffer->ReadNext(out int count); var polyList = count != 0 && CheckSentinel(_PrimitiveReader, kMagicPolymorphic); var type = typeof(TValue); if (type.IsArray) { var tValue = type.GetElementType(); Array array; array = Array.CreateInstance(tValue, count); if (polyList) { for (int i = 0; i < count; ++i) { // annoyingly we need to check if an element in our polymorphic list was null (in which case we // couldn't resolve the actual concrete type if (CheckSentinel(_PrimitiveReader, kMagicNull)) { array.SetValue(null, i); } else { var typeName = new PolymorphicTypeName(); PropertyContainer.Visit(ref typeName, this); var concreteType = Type.GetType(typeName.Name); if (!concreteType.IsValueType && !concreteType.GetConstructors().Any(c => c.GetParameters().Count() == 0)) { throw new ArgumentException( $"All class component types must be default constructable. '{concreteType.FullName}' is missing a default constructor."); } array.SetValue(Activator.CreateInstance(concreteType), i); } } } else { for (int i = 0; i < count; ++i) { // Strings are immutable so we need to give a value when creating them if (typeof(string) == tValue) { array.SetValue(Activator.CreateInstance(tValue, "".ToCharArray()), i); } else if (typeof(UnityEngine.Object).IsAssignableFrom(tValue)) { // do nothing } else { if (!tValue.IsValueType && !tValue.GetConstructors().Any(c => c.GetParameters().Count() == 0)) { throw new ArgumentException( $"All class component types must be default constructable. '{tValue.FullName}' is missing a default constructor."); } array.SetValue(Activator.CreateInstance(tValue), i); } } } value = (TValue)(object)array; } else if (type.IsGenericType) { var tValue = type.GetGenericArguments()[0]; System.Collections.IList list; if (null == value) { list = (System.Collections.IList)Activator.CreateInstance(typeof(List <>).MakeGenericType(tValue)); } else { list = value as System.Collections.IList; } if (polyList) { for (int i = 0; i < count; ++i) { // annoyingly we need to check if an element in our polymorphic list was null (in which case we // couldn't resolve the actual concrete type if (CheckSentinel(_PrimitiveReader, kMagicNull)) { list.Add(null); } else { var typeName = new PolymorphicTypeName(); PropertyContainer.Visit(ref typeName, this); var concreteType = Type.GetType(typeName.Name); if (!concreteType.IsValueType && !concreteType.GetConstructors().Any(c => c.GetParameters().Count() == 0)) { throw new ArgumentException( $"All class component types must be default constructable. '{concreteType.FullName}' is missing a default constructor."); } list.Add(Activator.CreateInstance(concreteType)); } } } else { for (int i = 0; i < count; ++i) { // Strings are immutable so we need to give a value when creating them if (typeof(string) == tValue) { list.Add(Activator.CreateInstance(tValue, "".ToCharArray())); } else if (typeof(UnityEngine.Object).IsAssignableFrom(tValue)) { list.Add(null); } else { if (!tValue.IsValueType && !tValue.GetConstructors().Any(c => c.GetParameters().Count() == 0)) { throw new ArgumentException( $"All class component types must be default constructable. '{tValue.FullName}' is missing a default constructor."); } list.Add(Activator.CreateInstance(tValue)); } } } value = (TValue)list; } property.SetValue(ref container, value); property.SetCount(ref container, count); return(VisitStatus.Handled); }
protected override VisitStatus BeginContainer <TProperty, TContainer, TValue> (TProperty property, ref TContainer container, ref TValue value, ref ChangeTracker changeTracker) { if (_ProcessingCollectionStack.Count > 0) { _ProcessingElementStack.Push(true); } if (typeof(System.Collections.IDictionary).IsAssignableFrom(typeof(TValue))) { var dict = value as System.Collections.IDictionary; var keys = dict.Keys; var values = dict.Values; var tKey = typeof(TValue).GetGenericArguments()[0]; var tValue = typeof(TValue).GetGenericArguments()[1]; // Workaround to support Dictionaries since Unity.Properties doesn't contain a ReflectedDictionaryProperty nor is it currently setup // to easily be extended to support Key-Value container types. As such we treat dictionaries as two list (keys and values) by // making our own container type to visit which we populate with the two lists we care about var openMethod = typeof(PropertiesBinaryWriter).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(m => m.Name == "VisitDictionary"); var closedMethod = openMethod.MakeGenericMethod(tKey, tValue); closedMethod.Invoke(this, new[] { keys, values }); EndContainer(property, ref container, ref value, ref changeTracker); return(VisitStatus.Override); } #if !NET_DOTS else if (typeof(UnityEngine.Object).IsAssignableFrom(typeof(TValue))) { AppendObject(value as UnityEngine.Object); EndContainer(property, ref container, ref value, ref changeTracker); return(VisitStatus.Override); } #endif else if (value == null) { Buffer.Add(kMagicNull); return(VisitStatus.Override); } // Is the runtime type different than the field type, then we are dealing with a polymorphic type // and we need to persist enough information to reconstruct the correct type when deserializing var valueType = value.GetType(); if (typeof(TValue) != valueType) { // If we are iterating over a list, don't write out the element type since we did that already // when constructing the list if (_ProcessingCollectionStack.Count == 0 || (_ProcessingCollectionStack.Count > 0 && _ProcessingElementStack.Count > 1)) { Buffer.Add(kMagicPolymorphic); var typeName = new PolymorphicTypeName() { Name = valueType.AssemblyQualifiedName }; PropertyContainer.Visit(ref typeName, this); } var openMethod = typeof(PropertiesBinaryWriter).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Single(m => m.Name == "VisitPolymorphicType"); var closedMethod = openMethod.MakeGenericMethod(valueType); closedMethod.Invoke(this, new object[] { value }); // Override will skip EndContainer, and Handled will start visiting fields we already visited. Sigh... EndContainer(property, ref container, ref value, ref changeTracker); return(VisitStatus.Override); } return(base.BeginContainer(property, ref container, ref value, ref changeTracker)); }