public bool Generate <TContainer, TValue>(FieldInfo fieldInfo, ReflectedPropertyBag <TContainer> propertyBag)
        {
            if (!typeof(TContainer).IsValueType)
            {
                return(false);
            }

            if (!fieldInfo.FieldType.IsValueType)
            {
                return(false);
            }

            if (!UnsafeUtility.IsBlittable(fieldInfo.FieldType) && fieldInfo.FieldType != typeof(char))
            {
                return(false);
            }

            var propertyType = typeof(UnmanagedProperty <,>).MakeGenericType(typeof(TContainer), fieldInfo.FieldType);
            var property     = Activator.CreateInstance(propertyType,
                                                        fieldInfo.Name,
                                                        UnsafeUtility.GetFieldOffset(fieldInfo),
                                                        null != fieldInfo.GetCustomAttribute <ReadOnlyAttribute>(),
                                                        new PropertyAttributeCollection(fieldInfo.GetCustomAttributes().ToArray()));

            propertyBag.AddProperty <IProperty <TContainer, TValue>, TValue>((IProperty <TContainer, TValue>)property);
            return(true);
        }
Ejemplo n.º 2
0
 public FieldData(int offset, FieldInfo info)
 {
     this.offset      = offset + UnsafeUtility.GetFieldOffset(info);
     length           = UnsafeUtility.SizeOf(info.FieldType);
     type             = info.FieldType;
     isAssetReference = info.GetCustomAttribute <AssetReferenceAttribute>() != null;
 }
Ejemplo n.º 3
0
        static bool TryGetOffsetOfField(Type rootType, PropertyPath propertyPath, int startIndex, out int offset)
        {
            offset = 0;

            var currentType = rootType;

            for (var i = startIndex; i < propertyPath.PartsCount; i++)
            {
                if (!currentType.IsValueType)
                {
                    return(false);
                }

                var part = propertyPath[i];

                if (part.IsIndex || part.IsKey)
                {
                    throw new ArgumentException("TinyAnimation does not support array indexers or dictionary keys for bindings.");
                }

                var f = currentType.GetField(propertyPath[i].Name, BindingFlags.Instance | BindingFlags.Public);

                if (f == null)
                {
                    return(false);
                }

                offset     += UnsafeUtility.GetFieldOffset(f);
                currentType = f.FieldType;
            }

            return(true);
        }
Ejemplo n.º 4
0
 public InjectionData(FieldInfo field, Type genericType, bool isReadOnly)
 {
     this.IndexInComponentGroup = -1;
     this.FieldOffset           = UnsafeUtility.GetFieldOffset(field);
     Unity.Entities.ComponentType.AccessMode accessModeType = isReadOnly ? Unity.Entities.ComponentType.AccessMode.ReadOnly : Unity.Entities.ComponentType.AccessMode.ReadWrite;
     this.ComponentType = new Unity.Entities.ComponentType(genericType, accessModeType);
     this.IsReadOnly    = isReadOnly;
 }
Ejemplo n.º 5
0
        public int GetOffsetOfField(string fieldName)
        {
            var currentBase = GetCurrentOffset();
            var fieldInfo   = m_StructTypes[m_StructTypes.Count - 1].Type.GetField(fieldName);

            currentBase += UnsafeUtility.GetFieldOffset(fieldInfo);
            return(currentBase);
        }
        private static void Init()
        {
            var type  = typeof(ComponentTypeHandle <>);
            var field = type.GetField("m_Safety", BindingFlags.NonPublic |
                                      BindingFlags.Instance |
                                      BindingFlags.GetField);

            s_ArchetypeComponentSafetyVarPos = UnsafeUtility.GetFieldOffset(field);
        }
Ejemplo n.º 7
0
    static ListUtilities()
    {
        var type        = typeof(List <>);
        var itemsMember = type.GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic);
        var sizeMember  = type.GetField("_size", BindingFlags.Instance | BindingFlags.NonPublic);

        ItemsFieldOffset = UnsafeUtility.GetFieldOffset(itemsMember);
        SizeFieldOffset  = UnsafeUtility.GetFieldOffset(sizeMember);
    }
Ejemplo n.º 8
0
 public FieldOffsetInfo(int currentOffset, FieldInfo parent, FieldInfo fieldInfo)
 {
     this.root   = parent == null;
     fullName    = root ? fieldInfo.Name : $"{parent.Name}.{fieldInfo.Name}";
     name        = fieldInfo.Name;
     this.type   = fieldInfo.FieldType;
     this.offset = currentOffset + UnsafeUtility.GetFieldOffset(fieldInfo);
     length      = UnsafeUtility.SizeOf(fieldInfo.FieldType);
 }
Ejemplo n.º 9
0
        public InjectionData(FieldInfo field, Type genericType, bool isReadOnly)
        {
            IndexInComponentGroup = -1;
            FieldOffset           = UnsafeUtility.GetFieldOffset(field);

            var accessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite;

            ComponentType = new ComponentType(genericType, accessMode);
            IsReadOnly    = isReadOnly;
        }
Ejemplo n.º 10
0
        public void Build(BlobBuilder builder, IntPtr dataPtr)
        {
            var fields = Type.GetType(BlobDataType).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            for (var i = 0; i < Builders.Length; i++)
            {
                var offset = UnsafeUtility.GetFieldOffset(fields[i]);
                Builders[i].Build(builder, dataPtr + offset);
            }
        }
#pragma warning restore 649

            static KernelLayout Calculate()
            {
                var          type = typeof(Layout <TUserKernel, TKernelData, TKernelPortDefinition>);
                KernelLayout ret;

                ret.Combined     = SimpleType.Create <Layout <TUserKernel, TKernelData, TKernelPortDefinition> >();
                ret.DataOffset   = UnsafeUtility.GetFieldOffset(type.GetField("Data"));
                ret.KernelOffset = UnsafeUtility.GetFieldOffset(type.GetField("Kernel"));

                return(ret);
            }
        public static int GetFieldOffset(FieldInfo field)
        {
#if UNITY_COLLECTIONS
            return(UnsafeUtility.GetFieldOffset(field));
#else
            int GetFieldOffset(RuntimeFieldHandle h) =>
            Marshal.ReadInt32(h.Value + (4 + IntPtr.Size)) & 0xFFFFFF;

            return(GetFieldOffset(field.FieldHandle));
#endif
        }
Ejemplo n.º 13
0
        public override unsafe void Build(BlobBuilder builder, ref T data)
        {
            var dataPtr = new IntPtr(UnsafeUtility.AddressOf(ref data));
            var fields  = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            for (var i = 0; i < Builders.Length; i++)
            {
                var offset = UnsafeUtility.GetFieldOffset(fields[i]);
                Builders[i].Build(builder, dataPtr + offset);
            }
        }
Ejemplo n.º 14
0
        private unsafe InjectComponentGroupData(ComponentSystemBase system, FieldInfo groupField,
                                                InjectionData[] componentDataInjections, InjectionData[] bufferArrayInjections,
                                                InjectionData[] sharedComponentInjections,
                                                FieldInfo entityArrayInjection, FieldInfo indexFromEntityInjection, InjectionContext injectionContext,
                                                FieldInfo lengthInjection, FieldInfo componentGroupIndexField, ComponentType[] componentRequirements)
        {
            m_EntityGroup = system.GetComponentGroupInternal(componentRequirements);

            m_ComponentGroupIndex = Array.IndexOf(system.ComponentGroups, m_EntityGroup);

            m_ComponentDataInjections   = componentDataInjections;
            m_BufferArrayInjections     = bufferArrayInjections;
            m_SharedComponentInjections = sharedComponentInjections;
            m_InjectionContext          = injectionContext;

            PatchGetIndexInComponentGroup(m_ComponentDataInjections);
            PatchGetIndexInComponentGroup(m_BufferArrayInjections);
            PatchGetIndexInComponentGroup(m_SharedComponentInjections);

            injectionContext.PrepareEntries(m_EntityGroup);

            if (entityArrayInjection != null)
            {
                m_EntityArrayOffset = UnsafeUtility.GetFieldOffset(entityArrayInjection);
            }
            else
            {
                m_EntityArrayOffset = -1;
            }

            if (lengthInjection != null)
            {
                m_LengthOffset = UnsafeUtility.GetFieldOffset(lengthInjection);
            }
            else
            {
                m_LengthOffset = -1;
            }

            m_GroupFieldOffset = UnsafeUtility.GetFieldOffset(groupField);

            if (componentGroupIndexField != null)
            {
                ulong gchandle;
                var   pinnedSystemPtr = (byte *)UnsafeUtility.PinGCObjectAndGetAddress(system, out gchandle);
                var   groupIndexPtr   = pinnedSystemPtr + m_GroupFieldOffset + UnsafeUtility.GetFieldOffset(componentGroupIndexField);

                int groupIndex = m_ComponentGroupIndex;
                UnsafeUtility.CopyStructureToPtr(ref groupIndex, groupIndexPtr);

                UnsafeUtility.ReleaseGCObject(gchandle);
            }
        }
Ejemplo n.º 15
0
 public override InjectionContext.Entry CreateInjectionInfoFor(FieldInfo field, bool isReadOnly)
 {
     return(new InjectionContext.Entry
     {
         Hook = this,
         FieldInfo = field,
         IsReadOnly = isReadOnly,
         AccessMode = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite,
         IndexInComponentGroup = -1,
         FieldOffset = UnsafeUtility.GetFieldOffset(field),
         ComponentRequirements = new[] { typeof(Transform) }
     });
 }
        public unsafe void Buffer_AndBufferDescription_HaveSameLayout()
        {
            var typed   = typeof(Buffer <byte>);
            var untyped = typeof(BufferDescription);

            Assert.AreEqual(UnsafeUtility.SizeOf(typed), UnsafeUtility.SizeOf(untyped));

            var fields = typed.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);

            Assert.AreEqual(1, fields.Length);
            Assert.AreEqual(fields[0].FieldType, untyped);
            Assert.Zero(UnsafeUtility.GetFieldOffset(fields[0]));
        }
Ejemplo n.º 17
0
        public unsafe void TypeInfo_BlobAssetReferenceOffsets_AreSortedAndCorrect()
        {
            var typeInfo = TypeManager.GetTypeInfo <EcsTestDataBlobAssetRef2>();

            Assert.AreEqual(2, typeInfo.BlobAssetRefOffsetCount);
            var offsets = TypeManager.GetBlobAssetRefOffsets(typeInfo);
            int offsetA = offsets[0].Offset;
            int offsetB = offsets[1].Offset;

            Assert.Less(offsetA, offsetB, "BlobAssetOffsets offsets are assumed to be sorted.");
            Assert.AreEqual(UnsafeUtility.GetFieldOffset(typeof(EcsTestDataBlobAssetRef2).GetField(nameof(EcsTestDataBlobAssetRef2.value))), offsetA);
            Assert.AreEqual(UnsafeUtility.GetFieldOffset(typeof(EcsTestDataBlobAssetRef2).GetField(nameof(EcsTestDataBlobAssetRef2.value2))), offsetB);
        }
Ejemplo n.º 18
0
            public static Field Create(FieldInfo info, Type type, string name, bool selfNodeOnly)
            {
                var offset =
                    UnsafeUtility.GetFieldOffset(info);

                return(new Field
                {
                    info = info,
                    name = name,
                    type = type,
                    offset = offset,
                    selfNodeOnly = selfNodeOnly
                });
            }
Ejemplo n.º 19
0
        public unsafe void TypeInfo_EntityReferenceOffsets_AreSortedAndCorrect()
        {
            var typeInfo = TypeManager.GetTypeInfo <EcsTestDataEntity2>();

            Assert.IsTrue(TypeManager.HasEntityReferences(TypeManager.GetTypeIndex <EcsTestDataEntity2>()));
            Assert.AreEqual(2, typeInfo.EntityOffsetCount);
            var offsets = TypeManager.GetEntityOffsets(typeInfo);
            int offsetA = offsets[0].Offset;
            int offsetB = offsets[1].Offset;

            Assert.Less(offsetA, offsetB, "Entity offsets are assumed to be sorted.");
            Assert.AreEqual(UnsafeUtility.GetFieldOffset(typeof(EcsTestDataEntity2).GetField(nameof(EcsTestDataEntity2.value1))), offsetA);
            Assert.AreEqual(UnsafeUtility.GetFieldOffset(typeof(EcsTestDataEntity2).GetField(nameof(EcsTestDataEntity2.value2))), offsetB);
        }
Ejemplo n.º 20
0
        public ArchetypeManager(SharedComponentDataManager sharedComponentManager)
        {
            m_SharedComponentManager = sharedComponentManager;
            m_TypeLookup             = new NativeMultiHashMap <uint, IntPtr>(256, Allocator.Persistent);

            m_EmptyChunkPool = (UnsafeLinkedListNode *)m_ArchetypeChunkAllocator.Allocate(sizeof(UnsafeLinkedListNode), UnsafeUtility.AlignOf <UnsafeLinkedListNode>());
            UnsafeLinkedListNode.InitializeList(m_EmptyChunkPool);

#if UNITY_ASSERTIONS
            // Buffer should be 16 byte aligned to ensure component data layout itself can gurantee being aligned
            var offset = UnsafeUtility.GetFieldOffset(typeof(Chunk).GetField("Buffer"));
            Assert.IsTrue(offset % 16 == 0, "Chunk buffer must be 16 byte aligned");
#endif
        }
Ejemplo n.º 21
0
        InjectComponentGroupData(ComponentSystemBase system, FieldInfo groupField,
                                 InjectionData[] componentDataInjections, InjectionData[] fixedArrayInjections, InjectionData[] sharedComponentInjections,
                                 FieldInfo entityArrayInjection, FieldInfo indexFromEntityInjection, InjectionContext injectionContext,
                                 FieldInfo lengthInjection, ComponentType[] componentRequirements)
        {
            var requiredComponentTypes = componentRequirements;

            m_EntityGroup = system.GetComponentGroup(requiredComponentTypes);

            m_ComponentDataInjections   = componentDataInjections;
            m_FixedArrayInjections      = fixedArrayInjections;
            m_SharedComponentInjections = sharedComponentInjections;
            m_InjectionContext          = injectionContext;

            PatchGetIndexInComponentGroup(m_ComponentDataInjections);
            PatchGetIndexInComponentGroup(m_FixedArrayInjections);
            PatchGetIndexInComponentGroup(m_SharedComponentInjections);

            injectionContext.PrepareEntries(m_EntityGroup);

            if (entityArrayInjection != null)
            {
                m_EntityArrayOffset = UnsafeUtility.GetFieldOffset(entityArrayInjection);
            }
            else
            {
                m_EntityArrayOffset = -1;
            }

            if (indexFromEntityInjection != null)
            {
                m_IndexFromEntityOffset = UnsafeUtility.GetFieldOffset(indexFromEntityInjection);
            }
            else
            {
                m_IndexFromEntityOffset = -1;
            }

            if (lengthInjection != null)
            {
                m_LengthOffset = UnsafeUtility.GetFieldOffset(lengthInjection);
            }
            else
            {
                m_LengthOffset = -1;
            }

            m_GroupFieldOffset = UnsafeUtility.GetFieldOffset(groupField);
        }
Ejemplo n.º 22
0
        public int GetCurrentOffset()
        {
            var offset = 0;

            var type = m_StructTypes[0].Type;

            for (var i = 1; i < m_StructTypes.Count; ++i)
            {
                var info      = m_StructTypes[i];
                var fieldInfo = type.GetField(info.Name);
                offset += UnsafeUtility.GetFieldOffset(fieldInfo);
                type    = info.Type;
            }
            return(offset);
        }
Ejemplo n.º 23
0
        // Fills the int array with offsets of specified type's fields, starting from specified index and updating the index after each write into array.
        internal unsafe static void FillFieldOffsetsArray(Type typeContainingFields, List <KeyValuePair <string, bool> > fieldNamesAndAccess, int *fieldOffsets, ref int fieldIndex)
        {
            foreach (KeyValuePair <string, bool> fieldInfo in fieldNamesAndAccess)
            {
                if (fieldInfo.Value)
                {
                    fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeContainingFields.GetField(fieldInfo.Key));
                }
                else
                {
                    fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeContainingFields.GetField(fieldInfo.Key, BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
                }

                fieldIndex++;
            }
        }
Ejemplo n.º 24
0
 private static void CollectInheritableFields(Type type, byte config, int offset, List <UISchema.InheritableField> fields)
 {
     if (type == typeof(UILength))
     {
         fields.Add(new UISchema.InheritableField
         {
             config = config, offset = offset, length = UnsafeUtility.SizeOf <UILength>()
         });
     }
     else
     {
         foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
         {
             CollectInheritableFields(field.FieldType, config, offset + UnsafeUtility.GetFieldOffset(field), fields);
         }
     }
 }
Ejemplo n.º 25
0
        public override InjectionContext.Entry CreateInjectionInfoFor(FieldInfo field, bool isReadOnly)
        {
            var componentType = field.FieldType.GetGenericArguments()[0];
            var accessMode    = isReadOnly ? ComponentType.AccessMode.ReadOnly : ComponentType.AccessMode.ReadWrite;

            return(new InjectionContext.Entry
            {
                Hook = this,
                FieldInfo = field,
                IsReadOnly = false /* isReadOnly */,
                AccessMode = accessMode,
                IndexInComponentGroup = -1,
                FieldOffset = UnsafeUtility.GetFieldOffset(field),
                ComponentType = new ComponentType(componentType, accessMode),
                ComponentRequirements = componentType == typeof(Transform)
                    ? new[] { typeof(Transform), componentType }
                    : new [] { componentType }
            });
        }
Ejemplo n.º 26
0
        private static void FindFields(List <FieldInfo> result, Type type, int parentOffset)
        {
            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            foreach (var field in fields)
            {
                int offset = parentOffset + UnsafeUtility.GetFieldOffset(field);

                if (field.FieldType.IsPrimitive || field.FieldType.IsPointer)
                {
                    int sizeOf = -1;

                    if (field.FieldType.IsPointer)
                    {
                        sizeOf = UnsafeUtility.SizeOf <IntPtr>();
                    }
                    else
                    {
                        sizeOf = UnsafeUtility.SizeOf(field.FieldType);
                    }

                    if ((sizeOf & (sizeOf - 1)) != 0)
                    {
                        throw new ArgumentException($"Field {type}.{field} is of size {sizeOf} which is not a power of two");
                    }

                    if (sizeOf != ElementSize)
                    {
                        throw new ArgumentException($"Field {type}.{field} is of size {sizeOf}; currently only types of size {ElementSize} bytes are allowed");
                    }

                    result.Add(new FieldInfo {
                        Offset = offset                        /*, Size = sizeOf */
                    });
                }
                else
                {
                    FindFields(result, field.FieldType, offset);
                }
            }
        }
Ejemplo n.º 27
0
        // Fills the specified array (given as pointer and length) with offsets of this struct's fields that need to be in sync with plugin struct fields.
        public unsafe static int FillFieldOffsetsArray(int startFrom, int *fieldOffsets, int fieldOffsetsLength)
        {
            const int myFieldCount = 3;

            if (startFrom + myFieldCount > fieldOffsetsLength)
            {
                throw new ArgumentException($"{nameof(fieldOffsets)} (length {fieldOffsetsLength}) is not big enough for all {myFieldCount} fields of this struct (starting from {startFrom})!");
            }

            int fieldIndex = startFrom;

            fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeof(HpIntArray).GetField("Data"));
            fieldIndex++;
            fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeof(HpIntArray).GetField("m_Size", BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
            fieldIndex++;
            fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeof(HpIntArray).GetField("m_Flags", BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
            fieldIndex++;

            Assert.AreEqual(myFieldCount, fieldIndex - startFrom);
            return(fieldIndex);
        }
Ejemplo n.º 28
0
        private static void CreateLayoutRecurse(Type type, int baseOffset, List <Layout> layouts, ref int begin,
                                                ref int end)
        {
            var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            foreach (var field in fields)
            {
                var offset = baseOffset + UnsafeUtility.GetFieldOffset(field);

                if (field.FieldType.IsPrimitive || field.FieldType.IsPointer || field.FieldType.IsClass)
                {
                    var sizeOf = -1;
                    if (field.FieldType.IsPointer || field.FieldType.IsClass)
                    {
                        sizeOf = UnsafeUtility.SizeOf <PointerSize>();
                    }
                    else
                    {
                        sizeOf = UnsafeUtility.SizeOf(field.FieldType);
                    }

                    if (end != offset)
                    {
                        layouts.Add(new Layout {
                            offset = begin, count = end - begin, Aligned4 = false
                        });
                        begin = offset;
                        end   = offset + sizeOf;
                    }
                    else
                    {
                        end += sizeOf;
                    }
                }
                else
                {
                    CreateLayoutRecurse(field.FieldType, offset, layouts, ref begin, ref end);
                }
            }
        }
Ejemplo n.º 29
0
        // There's more fields of in-place array here, but we don't care about them
#pragma warning restore CS0649, CS0169

        // Fills the specified array (given as pointer and length) with offsets of this struct's fields that need to be in sync with plugin struct fields.
        public unsafe static int FillFieldOffsetsArray(int startFrom, int *fieldOffsets, int fieldOffsetsLength)
        {
            // List of (field name, isPublic) pairs
            List <KeyValuePair <string, bool> > fieldNamesAndAccess = new List <KeyValuePair <string, bool> >()
            {
                new KeyValuePair <string, bool>("m_Allocator", false),
                new KeyValuePair <string, bool>("m_NumTotalElements", false),
                new KeyValuePair <string, bool>("m_PartiallyFreed", false),
                new KeyValuePair <string, bool>("m_IsLocked", false)
            };

            // We need to account for m_Data* separately
            int myFieldCount = fieldNamesAndAccess.Count + 1;

            if (startFrom + myFieldCount > fieldOffsetsLength)
            {
                throw new ArgumentException($"{nameof(fieldOffsets)} (length {fieldOffsetsLength}) is not big enough for all {myFieldCount} fields of this struct (starting from {startFrom})!");
            }

            int fieldIndex = startFrom;

            UnsafeEx.FillFieldOffsetsArray(typeof(HpBlockStream), fieldNamesAndAccess, fieldOffsets, ref fieldIndex);

            if (BuildPlatformUtil.is32Bit())
            {
                // 32bit
                fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeof(HpBlockStream).GetField("m_Data32Bit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
            }
            else
            {
                // 64bit
                fieldOffsets[fieldIndex] = UnsafeUtility.GetFieldOffset(typeof(HpBlockStream).GetField("m_Data64Bit", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance));
            }

            fieldIndex++;

            Assert.AreEqual(myFieldCount, fieldIndex - startFrom);
            return(fieldIndex);
        }
Ejemplo n.º 30
0
        private void Decompose(FieldInfo fieldInfo, string prefix, int baseOffset, int offset, Dictionary <string, FieldData> data, char separator = '.')
        {
            string path = prefix;

            if (fieldInfo.GetCustomAttribute <EmbedAttribute>() == null)
            {
                path = string.IsNullOrEmpty(path) ? fieldInfo.Name : $"{path}{separator}{fieldInfo.Name}";
                if (data.ContainsKey(path))
                {
                    throw new Exception("Path already exists");
                }
                data[path] = new FieldData(baseOffset + offset, fieldInfo);
            }
            if (fieldInfo.FieldType != null && Type.GetTypeCode(fieldInfo.FieldType) == TypeCode.Object && fieldInfo.FieldType.GetCustomAttribute <TerminalAttribute>() == null && (!fieldInfo.FieldType.IsGenericType || !typeof(FunctionPointer <>).IsAssignableFrom(fieldInfo.FieldType.GetGenericTypeDefinition())))
            {
                foreach (var innerFieldInfo in fieldInfo.FieldType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance))
                {
                    if (innerFieldInfo.GetCustomAttribute <HideInDecompositionAttribute>() == null)
                    {
                        Decompose(innerFieldInfo, path, baseOffset, UnsafeUtility.GetFieldOffset(fieldInfo), data, separator);
                    }
                }
            }
        }