Beispiel #1
0
        // 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);
        }
Beispiel #2
0
        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);
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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));
        }