您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1086 行
44 KiB
1086 行
44 KiB
using System;
|
|
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using Unity.Assertions;
|
|
using Unity.Burst;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using Unity.Jobs;
|
|
using UnityEngine.Profiling;
|
|
|
|
namespace Unity.Entities
|
|
{
|
|
internal struct ComponentTypeInArchetype
|
|
{
|
|
public readonly int TypeIndex;
|
|
public readonly int BufferCapacity;
|
|
|
|
public bool IsBuffer => BufferCapacity >= 0;
|
|
public bool IsSystemStateComponent => TypeManager.IsSystemStateComponent(TypeIndex);
|
|
public bool IsSystemStateSharedComponent => TypeManager.IsSystemStateSharedComponent(TypeIndex);
|
|
public bool IsSharedComponent => TypeManager.IsSharedComponent(TypeIndex);
|
|
public bool IsZeroSized => TypeManager.GetTypeInfo(TypeIndex).IsZeroSized;
|
|
|
|
public ComponentTypeInArchetype(ComponentType type)
|
|
{
|
|
TypeIndex = type.TypeIndex;
|
|
BufferCapacity = type.BufferCapacity;
|
|
}
|
|
|
|
public static bool operator == (ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs)
|
|
{
|
|
return lhs.TypeIndex == rhs.TypeIndex && lhs.BufferCapacity == rhs.BufferCapacity;
|
|
}
|
|
|
|
public static bool operator != (ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs)
|
|
{
|
|
return lhs.TypeIndex != rhs.TypeIndex || lhs.BufferCapacity != rhs.BufferCapacity;
|
|
}
|
|
|
|
public static bool operator < (ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs)
|
|
{
|
|
return lhs.TypeIndex != rhs.TypeIndex
|
|
? lhs.TypeIndex < rhs.TypeIndex
|
|
: lhs.BufferCapacity < rhs.BufferCapacity;
|
|
}
|
|
|
|
public static bool operator > (ComponentTypeInArchetype lhs, ComponentTypeInArchetype rhs)
|
|
{
|
|
return lhs.TypeIndex != rhs.TypeIndex
|
|
? lhs.TypeIndex > rhs.TypeIndex
|
|
: lhs.BufferCapacity > rhs.BufferCapacity;
|
|
}
|
|
|
|
public static unsafe bool CompareArray(ComponentTypeInArchetype* type1, int typeCount1,
|
|
ComponentTypeInArchetype* type2, int typeCount2)
|
|
{
|
|
if (typeCount1 != typeCount2)
|
|
return false;
|
|
for (var i = 0; i < typeCount1; ++i)
|
|
if (type1[i] != type2[i])
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
public ComponentType ToComponentType()
|
|
{
|
|
ComponentType type;
|
|
type.BufferCapacity = BufferCapacity;
|
|
type.TypeIndex = TypeIndex;
|
|
type.AccessModeType = ComponentType.AccessMode.ReadWrite;
|
|
return type;
|
|
}
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
public override string ToString()
|
|
{
|
|
return ToComponentType().ToString();
|
|
}
|
|
#endif
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (obj is ComponentTypeInArchetype) return (ComponentTypeInArchetype) obj == this;
|
|
|
|
return false;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return (TypeIndex * 5819) ^ BufferCapacity;
|
|
}
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
internal unsafe struct Chunk
|
|
{
|
|
// NOTE: Order of the UnsafeLinkedListNode is required to be this in order
|
|
// to allow for casting & grabbing Chunk* from nodes...
|
|
public UnsafeLinkedListNode ChunkListNode; // 16 | 8
|
|
public UnsafeLinkedListNode ChunkListWithEmptySlotsNode; // 32 | 16
|
|
|
|
public Archetype* Archetype; // 40 | 20
|
|
public int* SharedComponentValueArray; // 48 | 24
|
|
|
|
// This is meant as read-only.
|
|
// ArchetypeManager.SetChunkCount should be used to change the count.
|
|
public int Count; // 52 | 28
|
|
public int Capacity; // 56 | 32
|
|
|
|
public int ManagedArrayIndex; // 60 | 36
|
|
|
|
public int Padding0; // 64 | 40
|
|
public uint* ChangeVersion; // 72 | 44
|
|
public void* Padding2; // 80 | 48
|
|
|
|
|
|
// Component data buffer
|
|
public fixed byte Buffer[4];
|
|
|
|
|
|
public const int kChunkSize = 16 * 1024;
|
|
public const int kMaximumEntitiesPerChunk = kChunkSize / 8;
|
|
|
|
public static int GetChunkBufferSize(int numComponents, int numSharedComponents)
|
|
{
|
|
var bufferSize = kChunkSize -
|
|
(sizeof(Chunk) - 4 + numSharedComponents * sizeof(int) + numComponents * sizeof(uint));
|
|
return bufferSize;
|
|
}
|
|
|
|
public static int GetSharedComponentOffset(int numSharedComponents)
|
|
{
|
|
return kChunkSize - numSharedComponents * sizeof(int);
|
|
}
|
|
|
|
public static int GetChangedComponentOffset(int numComponents, int numSharedComponents)
|
|
{
|
|
return GetSharedComponentOffset(numSharedComponents) - numComponents * sizeof(uint);
|
|
}
|
|
|
|
public bool MatchesFilter(MatchingArchetypes* match, ref ComponentGroupFilter filter)
|
|
{
|
|
if ((filter.Type & FilterType.SharedComponent) != 0)
|
|
{
|
|
var sharedComponentsInChunk = SharedComponentValueArray;
|
|
var filteredCount = filter.Shared.Count;
|
|
|
|
fixed (int* indexInComponentGroupPtr = filter.Shared.IndexInComponentGroup, sharedComponentIndexPtr =
|
|
filter.Shared.SharedComponentIndex)
|
|
{
|
|
for (var i = 0; i < filteredCount; ++i)
|
|
{
|
|
var indexInComponentGroup = indexInComponentGroupPtr[i];
|
|
var sharedComponentIndex = sharedComponentIndexPtr[i];
|
|
var componentIndexInArcheType = match->IndexInArchetype[indexInComponentGroup];
|
|
var componentIndexInChunk = match->Archetype->SharedComponentOffset[componentIndexInArcheType];
|
|
if (sharedComponentsInChunk[componentIndexInChunk] != sharedComponentIndex)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if ((filter.Type & FilterType.Changed) != 0)
|
|
{
|
|
var changedCount = filter.Changed.Count;
|
|
|
|
var requiredVersion = filter.RequiredChangeVersion;
|
|
fixed (int* indexInComponentGroupPtr = filter.Changed.IndexInComponentGroup)
|
|
{
|
|
for (var i = 0; i < changedCount; ++i)
|
|
{
|
|
var indexInArchetype = match->IndexInArchetype[indexInComponentGroupPtr[i]];
|
|
|
|
var changeVersion = ChangeVersion[indexInArchetype];
|
|
if (ChangeVersionUtility.DidChange(changeVersion, requiredVersion))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public int GetSharedComponentIndex(MatchingArchetypes* match, int indexInComponentGroup)
|
|
{
|
|
var sharedComponentsInChunk = SharedComponentValueArray;
|
|
|
|
var componentIndexInArcheType = match->IndexInArchetype[indexInComponentGroup];
|
|
var componentIndexInChunk = match->Archetype->SharedComponentOffset[componentIndexInArcheType];
|
|
return sharedComponentsInChunk[componentIndexInChunk];
|
|
}
|
|
}
|
|
|
|
internal unsafe struct Archetype
|
|
{
|
|
public UnsafeLinkedListNode ChunkList;
|
|
public UnsafeLinkedListNode ChunkListWithEmptySlots;
|
|
public ChunkListMap FreeChunksBySharedComponents;
|
|
|
|
public int EntityCount;
|
|
public int ChunkCapacity;
|
|
public int ChunkCount;
|
|
|
|
public ComponentTypeInArchetype* Types;
|
|
public int TypesCount;
|
|
|
|
// Index matches archetype types
|
|
public int* Offsets;
|
|
public int* SizeOfs;
|
|
|
|
// TypesCount indices into Types/Offsets/SizeOfs in the order that the
|
|
// components are laid out in memory.
|
|
public int* TypeMemoryOrder;
|
|
|
|
public int* ManagedArrayOffset;
|
|
public int NumManagedArrays;
|
|
|
|
public int* SharedComponentOffset;
|
|
public int NumSharedComponents;
|
|
|
|
public Archetype* PrevArchetype;
|
|
public Archetype* InstantiableArchetype;
|
|
|
|
public EntityRemapUtility.EntityPatchInfo* ScalarEntityPatches;
|
|
public int ScalarEntityPatchCount;
|
|
|
|
public EntityRemapUtility.BufferEntityPatchInfo* BufferEntityPatches;
|
|
public int BufferEntityPatchCount;
|
|
|
|
public bool SystemStateCleanupComplete;
|
|
public bool SystemStateCleanupNeeded;
|
|
public bool Disabled;
|
|
public bool Prefab;
|
|
}
|
|
|
|
internal unsafe class ArchetypeManager : IDisposable
|
|
{
|
|
private readonly UnsafeLinkedListNode* m_EmptyChunkPool;
|
|
|
|
private readonly SharedComponentDataManager m_SharedComponentManager;
|
|
private ChunkAllocator m_ArchetypeChunkAllocator;
|
|
|
|
internal Archetype* m_LastArchetype;
|
|
private ManagedArrayStorage[] m_ManagedArrays = new ManagedArrayStorage[1];
|
|
private NativeMultiHashMap<uint, IntPtr> m_TypeLookup;
|
|
// lastChunkWithSharedComponentsAllocatedInto is used to speed up allocations from the same archetype/
|
|
// shared component combination, if this chunk matches we can avoid searching through all chunks belonging
|
|
// to the archetype
|
|
private Chunk* lastChunkWithSharedComponentsAllocatedInto;
|
|
|
|
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
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
// Move all chunks to become pooled chunks
|
|
while (m_LastArchetype != null)
|
|
{
|
|
while (!m_LastArchetype->ChunkList.IsEmpty)
|
|
{
|
|
var chunk = (Chunk*)m_LastArchetype->ChunkList.Begin;
|
|
SetChunkCount(chunk, 0);
|
|
}
|
|
|
|
m_LastArchetype->FreeChunksBySharedComponents.Dispose();
|
|
m_LastArchetype = m_LastArchetype->PrevArchetype;
|
|
}
|
|
|
|
// And all pooled chunks
|
|
while (!m_EmptyChunkPool->IsEmpty)
|
|
{
|
|
var chunk = m_EmptyChunkPool->Begin;
|
|
chunk->Remove();
|
|
UnsafeUtility.Free(chunk, Allocator.Persistent);
|
|
}
|
|
|
|
m_ManagedArrays = null;
|
|
m_TypeLookup.Dispose();
|
|
m_ArchetypeChunkAllocator.Dispose();
|
|
}
|
|
|
|
private void DeallocateManagedArrayStorage(int index)
|
|
{
|
|
Assert.IsTrue(m_ManagedArrays[index].ManagedArray != null);
|
|
m_ManagedArrays[index].ManagedArray = null;
|
|
}
|
|
|
|
private int AllocateManagedArrayStorage(int length)
|
|
{
|
|
for (var i = 0; i < m_ManagedArrays.Length; i++)
|
|
if (m_ManagedArrays[i].ManagedArray == null)
|
|
{
|
|
m_ManagedArrays[i].ManagedArray = new object[length];
|
|
return i;
|
|
}
|
|
|
|
var oldLength = m_ManagedArrays.Length;
|
|
Array.Resize(ref m_ManagedArrays, m_ManagedArrays.Length * 2);
|
|
|
|
m_ManagedArrays[oldLength].ManagedArray = new object[length];
|
|
|
|
return oldLength;
|
|
}
|
|
|
|
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
|
public static void AssertArchetypeComponents(ComponentTypeInArchetype* types, int count)
|
|
{
|
|
if (count < 1)
|
|
throw new ArgumentException($"Invalid component count");
|
|
if (types[0].TypeIndex == 0)
|
|
throw new ArgumentException($"Component type may not be null");
|
|
if (types[0].TypeIndex != TypeManager.GetTypeIndex<Entity>())
|
|
throw new ArgumentException($"The Entity ID must always be the first component");
|
|
|
|
for (var i = 1; i < count; i++)
|
|
{
|
|
if (types[i - 1].TypeIndex == types[i].TypeIndex)
|
|
throw new ArgumentException(
|
|
$"It is not allowed to have two components of the same type on the same entity. ({types[i - 1]} and {types[i]})");
|
|
}
|
|
}
|
|
|
|
public Archetype* GetExistingArchetype(ComponentTypeInArchetype* types, int count)
|
|
{
|
|
IntPtr typePtr;
|
|
NativeMultiHashMapIterator<uint> it;
|
|
|
|
if (!m_TypeLookup.TryGetFirstValue(GetHash(types, count), out typePtr, out it))
|
|
return null;
|
|
|
|
do
|
|
{
|
|
var type = (Archetype*) typePtr;
|
|
if (ComponentTypeInArchetype.CompareArray(type->Types, type->TypesCount, types, count))
|
|
return type;
|
|
} while (m_TypeLookup.TryGetNextValue(out typePtr, ref it));
|
|
|
|
return null;
|
|
}
|
|
|
|
private static uint GetHash(ComponentTypeInArchetype* types, int count)
|
|
{
|
|
var hash = HashUtility.Fletcher32((ushort*) types,
|
|
count * sizeof(ComponentTypeInArchetype) / sizeof(ushort));
|
|
return hash;
|
|
}
|
|
|
|
public Archetype* GetOrCreateArchetype(ComponentTypeInArchetype* types, int count,
|
|
EntityGroupManager groupManager)
|
|
{
|
|
var srcArchetype = GetOrCreateArchetypeInternal(types, count, groupManager);
|
|
|
|
var removedTypes = 0;
|
|
var prefabTypeIndex = TypeManager.GetTypeIndex<Prefab>();
|
|
for (var t = 0; t < srcArchetype->TypesCount; ++t)
|
|
{
|
|
var type = srcArchetype->Types[t];
|
|
var skip = type.IsSystemStateComponent || type.IsSystemStateSharedComponent || (type.TypeIndex == prefabTypeIndex);
|
|
if (skip)
|
|
++removedTypes;
|
|
else
|
|
types[t - removedTypes] = srcArchetype->Types[t];
|
|
}
|
|
|
|
srcArchetype->InstantiableArchetype = srcArchetype;
|
|
if (removedTypes > 0)
|
|
{
|
|
var instantiableArchetype = GetOrCreateArchetypeInternal(types, count-removedTypes, groupManager);
|
|
|
|
srcArchetype->InstantiableArchetype = instantiableArchetype;
|
|
instantiableArchetype->InstantiableArchetype = instantiableArchetype;
|
|
}
|
|
|
|
return srcArchetype;
|
|
}
|
|
|
|
private Archetype* GetOrCreateArchetypeInternal(ComponentTypeInArchetype* types, int count,
|
|
EntityGroupManager groupManager)
|
|
{
|
|
var type = GetExistingArchetype(types, count);
|
|
if (type != null)
|
|
return type;
|
|
|
|
AssertArchetypeComponents(types, count);
|
|
|
|
// This is a new archetype, allocate it and add it to the hash map
|
|
type = (Archetype*) m_ArchetypeChunkAllocator.Allocate(sizeof(Archetype), 8);
|
|
type->TypesCount = count;
|
|
type->Types =
|
|
(ComponentTypeInArchetype*) m_ArchetypeChunkAllocator.Construct(
|
|
sizeof(ComponentTypeInArchetype) * count, 4, types);
|
|
type->EntityCount = 0;
|
|
type->ChunkCount = 0;
|
|
|
|
type->NumSharedComponents = 0;
|
|
type->SharedComponentOffset = null;
|
|
|
|
var disabledTypeIndex = TypeManager.GetTypeIndex<Disabled>();
|
|
var prefabTypeIndex = TypeManager.GetTypeIndex<Prefab>();
|
|
type->Disabled = false;
|
|
type->Prefab = false;
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
if (TypeManager.GetTypeInfo(types[i].TypeIndex).Category == TypeManager.TypeCategory.ISharedComponentData)
|
|
++type->NumSharedComponents;
|
|
if (types[i].TypeIndex == disabledTypeIndex)
|
|
type->Disabled = true;
|
|
if (types[i].TypeIndex == prefabTypeIndex)
|
|
type->Prefab = true;
|
|
}
|
|
|
|
// Compute how many IComponentData types store Entities and need to be patched.
|
|
// Types can have more than one entity, which means that this count is not necessarily
|
|
// the same as the type count.
|
|
int scalarEntityPatchCount = 0;
|
|
int bufferEntityPatchCount = 0;
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var ct = TypeManager.GetTypeInfo(types[i].TypeIndex);
|
|
var entityOffsets = ct.EntityOffsets;
|
|
if (entityOffsets == null)
|
|
continue;
|
|
|
|
if (ct.BufferCapacity >= 0)
|
|
{
|
|
bufferEntityPatchCount += entityOffsets.Length;
|
|
}
|
|
else
|
|
{
|
|
scalarEntityPatchCount += entityOffsets.Length;
|
|
|
|
}
|
|
}
|
|
|
|
var chunkDataSize = Chunk.GetChunkBufferSize(type->TypesCount, type->NumSharedComponents);
|
|
|
|
// FIXME: proper alignment
|
|
type->Offsets = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4);
|
|
type->SizeOfs = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4);
|
|
type->TypeMemoryOrder = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4);
|
|
type->ScalarEntityPatches = (EntityRemapUtility.EntityPatchInfo*) m_ArchetypeChunkAllocator.Allocate(sizeof(EntityRemapUtility.EntityPatchInfo) * scalarEntityPatchCount, 4);
|
|
type->ScalarEntityPatchCount = scalarEntityPatchCount;
|
|
type->BufferEntityPatches = (EntityRemapUtility.BufferEntityPatchInfo*) m_ArchetypeChunkAllocator.Allocate(sizeof(EntityRemapUtility.BufferEntityPatchInfo) * bufferEntityPatchCount, 4);
|
|
type->BufferEntityPatchCount = bufferEntityPatchCount;
|
|
|
|
var bytesPerInstance = 0;
|
|
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var cType = TypeManager.GetTypeInfo(types[i].TypeIndex);
|
|
var sizeOf = cType.SizeInChunk; // Note that this includes internal capacity and header overhead for buffers.
|
|
type->SizeOfs[i] = sizeOf;
|
|
|
|
bytesPerInstance += sizeOf;
|
|
}
|
|
|
|
type->ChunkCapacity = chunkDataSize / bytesPerInstance;
|
|
|
|
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
|
if (bytesPerInstance > chunkDataSize)
|
|
throw new ArgumentException(
|
|
$"Entity archetype component data is too large. The maximum component data is {chunkDataSize} but the component data is {bytesPerInstance}");
|
|
|
|
Assert.IsTrue(Chunk.kMaximumEntitiesPerChunk >= type->ChunkCapacity);
|
|
#endif
|
|
|
|
// For serialization a stable ordering of the components in the
|
|
// chunk is desired. The type index is not stable, since it depends
|
|
// on the order in which types are added to the TypeManager.
|
|
// A permutation of the types ordered by a TypeManager-generated
|
|
// memory ordering is used instead.
|
|
var memoryOrderings = new NativeArray<UInt64>(count, Allocator.Temp);
|
|
for (int i = 0; i < count; ++i)
|
|
memoryOrderings[i] = TypeManager.GetTypeInfo(types[i].TypeIndex).MemoryOrdering;
|
|
for (int i = 0; i < count; ++i)
|
|
{
|
|
int index = i;
|
|
while (index > 1 && memoryOrderings[i] < memoryOrderings[type->TypeMemoryOrder[index - 1]])
|
|
{
|
|
type->TypeMemoryOrder[index] = type->TypeMemoryOrder[index - 1];
|
|
--index;
|
|
}
|
|
type->TypeMemoryOrder[index] = i;
|
|
}
|
|
memoryOrderings.Dispose();
|
|
|
|
var usedBytes = 0;
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var index = type->TypeMemoryOrder[i];
|
|
var sizeOf = type->SizeOfs[index];
|
|
|
|
type->Offsets[index] = usedBytes;
|
|
|
|
usedBytes += sizeOf * type->ChunkCapacity;
|
|
}
|
|
|
|
type->NumManagedArrays = 0;
|
|
type->ManagedArrayOffset = null;
|
|
|
|
for (var i = 0; i < count; ++i)
|
|
if (TypeManager.GetTypeInfo(types[i].TypeIndex).Category == TypeManager.TypeCategory.Class)
|
|
++type->NumManagedArrays;
|
|
|
|
if (type->NumManagedArrays > 0)
|
|
{
|
|
type->ManagedArrayOffset = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4);
|
|
var mi = 0;
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var cType = TypeManager.GetTypeInfo(types[i].TypeIndex);
|
|
if (cType.Category == TypeManager.TypeCategory.Class)
|
|
type->ManagedArrayOffset[i] = mi++;
|
|
else
|
|
type->ManagedArrayOffset[i] = -1;
|
|
}
|
|
}
|
|
|
|
if (type->NumSharedComponents > 0)
|
|
{
|
|
type->SharedComponentOffset = (int*) m_ArchetypeChunkAllocator.Allocate(sizeof(int) * count, 4);
|
|
var mi = 0;
|
|
for (var i = 0; i < count; ++i)
|
|
{
|
|
var cType = TypeManager.GetTypeInfo(types[i].TypeIndex);
|
|
if (cType.Category == TypeManager.TypeCategory.ISharedComponentData)
|
|
type->SharedComponentOffset[i] = mi++;
|
|
else
|
|
type->SharedComponentOffset[i] = -1;
|
|
}
|
|
}
|
|
|
|
// Fill in arrays of scalar and buffer entity patches
|
|
var scalarPatchInfo = type->ScalarEntityPatches;
|
|
var bufferPatchInfo = type->BufferEntityPatches;
|
|
for (var i = 0; i != count; i++)
|
|
{
|
|
var ct = TypeManager.GetTypeInfo(types[i].TypeIndex);
|
|
var offsets = ct.EntityOffsets;
|
|
if (ct.BufferCapacity >= 0)
|
|
{
|
|
bufferPatchInfo = EntityRemapUtility.AppendBufferEntityPatches(bufferPatchInfo, offsets, type->Offsets[i], type->SizeOfs[i], ct.ElementSize);
|
|
}
|
|
else
|
|
{
|
|
scalarPatchInfo = EntityRemapUtility.AppendEntityPatches(scalarPatchInfo, offsets, type->Offsets[i], type->SizeOfs[i]);
|
|
}
|
|
}
|
|
type->ScalarEntityPatchCount = scalarEntityPatchCount;
|
|
type->BufferEntityPatchCount = bufferEntityPatchCount;
|
|
|
|
// Update the list of all created archetypes
|
|
type->PrevArchetype = m_LastArchetype;
|
|
m_LastArchetype = type;
|
|
|
|
UnsafeLinkedListNode.InitializeList(&type->ChunkList);
|
|
UnsafeLinkedListNode.InitializeList(&type->ChunkListWithEmptySlots);
|
|
type->FreeChunksBySharedComponents.Init(8);
|
|
|
|
m_TypeLookup.Add(GetHash(types, count), (IntPtr) type);
|
|
|
|
type->SystemStateCleanupComplete = ArchetypeSystemStateCleanupComplete(type);
|
|
type->SystemStateCleanupNeeded = ArchetypeSystemStateCleanupNeeded(type);
|
|
|
|
groupManager.OnArchetypeAdded(type);
|
|
|
|
return type;
|
|
}
|
|
|
|
private bool ArchetypeSystemStateCleanupComplete(Archetype* archetype)
|
|
{
|
|
if (archetype->TypesCount == 2 && archetype->Types[1].TypeIndex == TypeManager.GetTypeIndex<CleanupEntity>()) return true;
|
|
return false;
|
|
}
|
|
|
|
private bool ArchetypeSystemStateCleanupNeeded(Archetype* archetype)
|
|
{
|
|
for (var t = 1; t < archetype->TypesCount; ++t)
|
|
{
|
|
var type = archetype->Types[t];
|
|
if (type.IsSystemStateComponent || type.IsSystemStateSharedComponent)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static Chunk* GetChunkFromEmptySlotNode(UnsafeLinkedListNode* node)
|
|
{
|
|
return (Chunk*) (node - 1);
|
|
}
|
|
|
|
public void AddExistingChunk(Chunk* chunk)
|
|
{
|
|
var archetype = chunk->Archetype;
|
|
archetype->ChunkList.Add(&chunk->ChunkListNode);
|
|
archetype->ChunkCount += 1;
|
|
archetype->EntityCount += chunk->Count;
|
|
for (var i = 0; i < archetype->NumSharedComponents; ++i)
|
|
m_SharedComponentManager.AddReference(chunk->SharedComponentValueArray[i]);
|
|
|
|
if (chunk->Count < chunk->Capacity)
|
|
{
|
|
if (archetype->NumSharedComponents == 0)
|
|
{
|
|
archetype->ChunkListWithEmptySlots.Add(&chunk->ChunkListWithEmptySlotsNode);
|
|
}
|
|
else
|
|
{
|
|
archetype->FreeChunksBySharedComponents.Add(chunk);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ConstructChunk(Archetype* archetype, Chunk* chunk, int* sharedComponentDataIndices)
|
|
{
|
|
chunk->Archetype = archetype;
|
|
|
|
chunk->Count = 0;
|
|
chunk->Capacity = archetype->ChunkCapacity;
|
|
chunk->ChunkListNode = new UnsafeLinkedListNode();
|
|
chunk->ChunkListWithEmptySlotsNode = new UnsafeLinkedListNode();
|
|
|
|
var numSharedComponents = archetype->NumSharedComponents;
|
|
var numTypes = archetype->TypesCount;
|
|
var sharedComponentOffset = Chunk.GetSharedComponentOffset(numSharedComponents);
|
|
var changeVersionOffset = Chunk.GetChangedComponentOffset(numTypes, numSharedComponents);
|
|
|
|
chunk->SharedComponentValueArray = (int*) ((byte*) chunk + sharedComponentOffset);
|
|
chunk->ChangeVersion = (uint*) ((byte*) chunk + changeVersionOffset);
|
|
|
|
archetype->ChunkList.Add(&chunk->ChunkListNode);
|
|
archetype->ChunkCount += 1;
|
|
|
|
Assert.IsTrue(!archetype->ChunkList.IsEmpty);
|
|
Assert.IsTrue(chunk == (Chunk*) archetype->ChunkList.Back);
|
|
|
|
if (numSharedComponents == 0)
|
|
{
|
|
archetype->ChunkListWithEmptySlots.Add(&chunk->ChunkListWithEmptySlotsNode);
|
|
Assert.IsTrue(chunk == GetChunkFromEmptySlotNode(archetype->ChunkListWithEmptySlots.Back));
|
|
Assert.IsTrue(!archetype->ChunkListWithEmptySlots.IsEmpty);
|
|
}
|
|
else
|
|
{
|
|
var sharedComponentValueArray = chunk->SharedComponentValueArray;
|
|
UnsafeUtility.MemCpy(sharedComponentValueArray, sharedComponentDataIndices, archetype->NumSharedComponents*sizeof(int));
|
|
|
|
for (var i = 0; i < archetype->NumSharedComponents; ++i)
|
|
{
|
|
var sharedComponentIndex = sharedComponentValueArray[i];
|
|
m_SharedComponentManager.AddReference(sharedComponentIndex);
|
|
}
|
|
|
|
archetype->FreeChunksBySharedComponents.Add(chunk);
|
|
Assert.IsTrue(archetype->FreeChunksBySharedComponents.GetChunkWithEmptySlots(sharedComponentDataIndices, archetype->NumSharedComponents) != null);
|
|
}
|
|
|
|
if (archetype->NumManagedArrays > 0)
|
|
chunk->ManagedArrayIndex = AllocateManagedArrayStorage(archetype->NumManagedArrays * chunk->Capacity);
|
|
else
|
|
chunk->ManagedArrayIndex = -1;
|
|
|
|
for (var i = 0; i < archetype->TypesCount; i++)
|
|
chunk->ChangeVersion[i] = 0;
|
|
}
|
|
|
|
private static bool ChunkHasSharedComponents(Chunk* chunk, int* sharedComponentDataIndices)
|
|
{
|
|
var sharedComponentValueArray = chunk->SharedComponentValueArray;
|
|
var numSharedComponents = chunk->Archetype->NumSharedComponents;
|
|
return UnsafeUtility.MemCmp(sharedComponentDataIndices, sharedComponentValueArray, numSharedComponents * sizeof(int)) == 0;
|
|
}
|
|
|
|
public Chunk* GetChunkWithEmptySlots(Archetype* archetype, int* sharedComponentDataIndices)
|
|
{
|
|
if (archetype->NumSharedComponents == 0)
|
|
{
|
|
if (!archetype->ChunkListWithEmptySlots.IsEmpty)
|
|
{
|
|
var chunk = GetChunkFromEmptySlotNode(archetype->ChunkListWithEmptySlots.Begin);
|
|
Assert.AreNotEqual(chunk->Count, chunk->Capacity);
|
|
return chunk;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var chunk = archetype->FreeChunksBySharedComponents.GetChunkWithEmptySlots(sharedComponentDataIndices,
|
|
archetype->NumSharedComponents);
|
|
if (chunk != null)
|
|
{
|
|
return chunk;
|
|
}
|
|
}
|
|
|
|
// Try existing archetype chunks
|
|
if (!archetype->ChunkListWithEmptySlots.IsEmpty)
|
|
{
|
|
if (lastChunkWithSharedComponentsAllocatedInto != null &&
|
|
lastChunkWithSharedComponentsAllocatedInto->Archetype == archetype &&
|
|
lastChunkWithSharedComponentsAllocatedInto->Count < lastChunkWithSharedComponentsAllocatedInto->Capacity)
|
|
{
|
|
if (ChunkHasSharedComponents(lastChunkWithSharedComponentsAllocatedInto, sharedComponentDataIndices))
|
|
{
|
|
return lastChunkWithSharedComponentsAllocatedInto;
|
|
}
|
|
}
|
|
|
|
if (archetype->NumSharedComponents == 0)
|
|
{
|
|
var chunk = GetChunkFromEmptySlotNode(archetype->ChunkListWithEmptySlots.Begin);
|
|
Assert.AreNotEqual(chunk->Count, chunk->Capacity);
|
|
return chunk;
|
|
}
|
|
}
|
|
|
|
Chunk* newChunk;
|
|
// Try empty chunk pool
|
|
if (m_EmptyChunkPool->IsEmpty)
|
|
{
|
|
// Allocate new chunk
|
|
newChunk = (Chunk*)UnsafeUtility.Malloc(Chunk.kChunkSize, 64, Allocator.Persistent);
|
|
}
|
|
else
|
|
{
|
|
newChunk = (Chunk*) m_EmptyChunkPool->Begin;
|
|
newChunk->ChunkListNode.Remove();
|
|
}
|
|
|
|
ConstructChunk(archetype, newChunk, sharedComponentDataIndices);
|
|
|
|
if (archetype->NumSharedComponents > 0)
|
|
{
|
|
lastChunkWithSharedComponentsAllocatedInto = newChunk;
|
|
}
|
|
return newChunk;
|
|
}
|
|
|
|
public int AllocateIntoChunk(Chunk* chunk)
|
|
{
|
|
int outIndex;
|
|
var res = AllocateIntoChunk(chunk, 1, out outIndex);
|
|
Assert.AreEqual(1, res);
|
|
return outIndex;
|
|
}
|
|
|
|
public int AllocateIntoChunk(Chunk* chunk, int count, out int outIndex)
|
|
{
|
|
var allocatedCount = Math.Min(chunk->Capacity - chunk->Count, count);
|
|
outIndex = chunk->Count;
|
|
SetChunkCount(chunk, chunk->Count + allocatedCount);
|
|
chunk->Archetype->EntityCount += allocatedCount;
|
|
return allocatedCount;
|
|
}
|
|
|
|
public void SetChunkCount(Chunk* chunk, int newCount)
|
|
{
|
|
Assert.AreNotEqual(newCount, chunk->Count);
|
|
|
|
var capacity = chunk->Capacity;
|
|
|
|
// Chunk released to empty chunk pool
|
|
if (newCount == 0)
|
|
{
|
|
// Remove references to shared components
|
|
if (chunk->Archetype->NumSharedComponents > 0)
|
|
{
|
|
var sharedComponentValueArray = chunk->SharedComponentValueArray;
|
|
|
|
for (var i = 0; i < chunk->Archetype->NumSharedComponents; ++i)
|
|
m_SharedComponentManager.RemoveReference(sharedComponentValueArray[i]);
|
|
}
|
|
|
|
if (chunk->ManagedArrayIndex != -1)
|
|
{
|
|
DeallocateManagedArrayStorage(chunk->ManagedArrayIndex);
|
|
chunk->ManagedArrayIndex = -1;
|
|
}
|
|
|
|
chunk->Archetype->ChunkCount -= 1;
|
|
chunk->Archetype = null;
|
|
chunk->ChunkListNode.Remove();
|
|
chunk->ChunkListWithEmptySlotsNode.Remove();
|
|
|
|
m_EmptyChunkPool->Add(&chunk->ChunkListNode);
|
|
}
|
|
// Chunk is now full
|
|
else if (newCount == capacity)
|
|
{
|
|
chunk->ChunkListWithEmptySlotsNode.Remove();
|
|
}
|
|
// Chunk is no longer full
|
|
else if (chunk->Count == capacity)
|
|
{
|
|
Assert.IsTrue(newCount < chunk->Count);
|
|
if (chunk->Archetype->NumSharedComponents == 0)
|
|
chunk->Archetype->ChunkListWithEmptySlots.Add(&chunk->ChunkListWithEmptySlotsNode);
|
|
else
|
|
chunk->Archetype->FreeChunksBySharedComponents.Add(chunk);
|
|
|
|
}
|
|
|
|
chunk->Count = newCount;
|
|
}
|
|
|
|
public object GetManagedObject(Chunk* chunk, ComponentType type, int index)
|
|
{
|
|
var typeOfs = ChunkDataUtility.GetIndexInTypeArray(chunk->Archetype, type.TypeIndex);
|
|
if (typeOfs < 0 || chunk->Archetype->ManagedArrayOffset[typeOfs] < 0)
|
|
throw new InvalidOperationException("Trying to get managed object for non existing component");
|
|
return GetManagedObject(chunk, typeOfs, index);
|
|
}
|
|
|
|
internal object GetManagedObject(Chunk* chunk, int type, int index)
|
|
{
|
|
var managedStart = chunk->Archetype->ManagedArrayOffset[type] * chunk->Capacity;
|
|
return m_ManagedArrays[chunk->ManagedArrayIndex].ManagedArray[index + managedStart];
|
|
}
|
|
|
|
public object[] GetManagedObjectRange(Chunk* chunk, int type, out int rangeStart, out int rangeLength)
|
|
{
|
|
rangeStart = chunk->Archetype->ManagedArrayOffset[type] * chunk->Capacity;
|
|
rangeLength = chunk->Count;
|
|
return m_ManagedArrays[chunk->ManagedArrayIndex].ManagedArray;
|
|
}
|
|
|
|
public void SetManagedObject(Chunk* chunk, int type, int index, object val)
|
|
{
|
|
var managedStart = chunk->Archetype->ManagedArrayOffset[type] * chunk->Capacity;
|
|
m_ManagedArrays[chunk->ManagedArrayIndex].ManagedArray[index + managedStart] = val;
|
|
}
|
|
|
|
public void SetManagedObject(Chunk* chunk, ComponentType type, int index, object val)
|
|
{
|
|
var typeOfs = ChunkDataUtility.GetIndexInTypeArray(chunk->Archetype, type.TypeIndex);
|
|
if (typeOfs < 0 || chunk->Archetype->ManagedArrayOffset[typeOfs] < 0)
|
|
throw new InvalidOperationException("Trying to set managed object for non existing component");
|
|
SetManagedObject(chunk, typeOfs, index, val);
|
|
}
|
|
|
|
public int CountEntities()
|
|
{
|
|
int entityCount = 0;
|
|
var archetype = m_LastArchetype;
|
|
while (archetype != null)
|
|
{
|
|
entityCount += archetype->EntityCount;
|
|
archetype = archetype->PrevArchetype;
|
|
}
|
|
|
|
return entityCount;
|
|
}
|
|
|
|
[BurstCompile]
|
|
struct MoveChunksJob : IJob
|
|
{
|
|
[NativeDisableUnsafePtrRestriction]
|
|
public EntityDataManager* srcEntityDataManager;
|
|
[NativeDisableUnsafePtrRestriction]
|
|
public EntityDataManager* dstEntityDataManager;
|
|
public NativeArray<EntityRemapUtility.EntityRemapInfo> entityRemapping;
|
|
|
|
public void Execute()
|
|
{
|
|
dstEntityDataManager->AllocateEntitiesForRemapping(srcEntityDataManager, ref entityRemapping);
|
|
srcEntityDataManager->FreeAllEntities();
|
|
}
|
|
}
|
|
|
|
struct RemapChunk
|
|
{
|
|
public Chunk* chunk;
|
|
public Archetype* dstArchetype;
|
|
}
|
|
|
|
[BurstCompile]
|
|
struct RemapChunksJob : IJobParallelFor
|
|
{
|
|
[ReadOnly] public NativeArray<EntityRemapUtility.EntityRemapInfo> entityRemapping;
|
|
[DeallocateOnJobCompletion] [ReadOnly] public NativeArray<RemapChunk> remapChunks;
|
|
[ReadOnly] public NativeArray<int> remapShared;
|
|
|
|
[NativeDisableUnsafePtrRestriction]
|
|
public EntityDataManager* dstEntityDataManager;
|
|
|
|
public void Execute(int index)
|
|
{
|
|
Chunk* chunk = remapChunks[index].chunk;
|
|
Archetype* dstArchetype = remapChunks[index].dstArchetype;
|
|
|
|
dstEntityDataManager->RemapChunk(dstArchetype, chunk, 0, chunk->Count, ref entityRemapping);
|
|
EntityRemapUtility.PatchEntities(dstArchetype->ScalarEntityPatches + 1, dstArchetype->ScalarEntityPatchCount - 1, dstArchetype->BufferEntityPatches, dstArchetype->BufferEntityPatchCount, chunk->Buffer, chunk->Count, ref entityRemapping);
|
|
chunk->Archetype = dstArchetype;
|
|
|
|
for (int i = 0; i < dstArchetype->NumSharedComponents; ++i)
|
|
{
|
|
var componentIndex = chunk->SharedComponentValueArray[i];
|
|
componentIndex = remapShared[componentIndex];
|
|
chunk->SharedComponentValueArray[i] = componentIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct RemapArchetype
|
|
{
|
|
public Archetype* srcArchetype;
|
|
public Archetype* dstArchetype;
|
|
}
|
|
|
|
[BurstCompile]
|
|
struct RemapArchetypesJob : IJobParallelFor
|
|
{
|
|
[DeallocateOnJobCompletion] [ReadOnly] public NativeArray<RemapArchetype> remapArchetypes;
|
|
|
|
// This must be run after chunks have been remapped since FreeChunksBySharedComponents needs the shared component
|
|
// indices in the chunks to be remapped
|
|
public void Execute(int index)
|
|
{
|
|
var srcArchetype = remapArchetypes[index].srcArchetype;
|
|
var dstArchetype = remapArchetypes[index].dstArchetype;
|
|
|
|
UnsafeLinkedListNode.InsertListBefore(dstArchetype->ChunkList.End, &srcArchetype->ChunkList);
|
|
|
|
if (srcArchetype->NumSharedComponents == 0)
|
|
{
|
|
if (!srcArchetype->ChunkListWithEmptySlots.IsEmpty)
|
|
UnsafeLinkedListNode.InsertListBefore(dstArchetype->ChunkListWithEmptySlots.End,
|
|
&srcArchetype->ChunkListWithEmptySlots);
|
|
}
|
|
else
|
|
{
|
|
remapArchetypes[index].dstArchetype->FreeChunksBySharedComponents.AppendFrom(&remapArchetypes[index].srcArchetype->FreeChunksBySharedComponents);
|
|
}
|
|
|
|
dstArchetype->EntityCount += srcArchetype->EntityCount;
|
|
dstArchetype->ChunkCount += srcArchetype->ChunkCount;
|
|
srcArchetype->EntityCount = 0;
|
|
srcArchetype->ChunkCount = 0;
|
|
}
|
|
}
|
|
|
|
public static void MoveChunks(EntityManager srcEntities, ArchetypeManager dstArchetypeManager,
|
|
EntityGroupManager dstGroupManager, EntityDataManager* dstEntityDataManager, SharedComponentDataManager dstSharedComponents, ComponentTypeInArchetype* componentTypeInArchetypeArray)
|
|
{
|
|
var srcArchetypeManager = srcEntities.ArchetypeManager;
|
|
var srcEntityDataManager = srcEntities.Entities;
|
|
var srcSharedComponents = srcEntities.m_SharedComponentManager;
|
|
|
|
var entityRemapping = new NativeArray<EntityRemapUtility.EntityRemapInfo>(srcEntityDataManager->Capacity, Allocator.TempJob);
|
|
|
|
var moveChunksJob = new MoveChunksJob
|
|
{
|
|
srcEntityDataManager = srcEntityDataManager,
|
|
dstEntityDataManager = dstEntityDataManager,
|
|
entityRemapping = entityRemapping
|
|
}.Schedule();
|
|
JobHandle.ScheduleBatchedJobs();
|
|
|
|
var samplerShared = CustomSampler.Create("MoveAllSharedComponents");
|
|
samplerShared.Begin();
|
|
var remapShared = dstSharedComponents.MoveAllSharedComponents(srcSharedComponents, Allocator.TempJob);
|
|
samplerShared.End();
|
|
|
|
Archetype* srcArchetype;
|
|
|
|
int chunkCount = 0;
|
|
int archetypeCount = 0;
|
|
srcArchetype = srcArchetypeManager.m_LastArchetype;
|
|
while (srcArchetype != null)
|
|
{
|
|
archetypeCount++;
|
|
chunkCount += srcArchetype->ChunkCount;
|
|
srcArchetype = srcArchetype->PrevArchetype;
|
|
}
|
|
|
|
var remapChunks = new NativeArray<RemapChunk>(chunkCount, Allocator.TempJob);
|
|
var remapArchetypes = new NativeArray<RemapArchetype>(archetypeCount, Allocator.TempJob);
|
|
|
|
int chunkIndex = 0;
|
|
int archetypeIndex = 0;
|
|
srcArchetype = srcArchetypeManager.m_LastArchetype;
|
|
while (srcArchetype != null)
|
|
{
|
|
if (srcArchetype->ChunkCount != 0)
|
|
{
|
|
if (srcArchetype->NumManagedArrays != 0)
|
|
throw new ArgumentException("MoveEntitiesFrom is not supported with managed arrays");
|
|
|
|
// Make copy. GetOrCreateArchetype writes to type array.
|
|
UnsafeUtility.MemCpy(componentTypeInArchetypeArray, srcArchetype->Types, UnsafeUtility.SizeOf<ComponentTypeInArchetype>() * srcArchetype->TypesCount);
|
|
|
|
var dstArchetype = dstArchetypeManager.GetOrCreateArchetype(componentTypeInArchetypeArray, srcArchetype->TypesCount, dstGroupManager);
|
|
|
|
remapArchetypes[archetypeIndex] = new RemapArchetype {srcArchetype = srcArchetype, dstArchetype = dstArchetype};
|
|
|
|
for (var c = srcArchetype->ChunkList.Begin; c != srcArchetype->ChunkList.End; c = c->Next)
|
|
{
|
|
remapChunks[chunkIndex] = new RemapChunk { chunk = (Chunk*)c, dstArchetype = dstArchetype };
|
|
chunkIndex++;
|
|
}
|
|
|
|
archetypeIndex++;
|
|
|
|
dstEntityDataManager->IncrementComponentTypeOrderVersion(dstArchetype);
|
|
}
|
|
|
|
srcArchetype = srcArchetype->PrevArchetype;
|
|
}
|
|
|
|
var remapChunksJob = new RemapChunksJob
|
|
{
|
|
dstEntityDataManager = dstEntityDataManager,
|
|
remapChunks = remapChunks,
|
|
remapShared = remapShared,
|
|
entityRemapping = entityRemapping
|
|
}.Schedule(remapChunks.Length, 1, moveChunksJob);
|
|
|
|
var remapArchetypesJob = new RemapArchetypesJob
|
|
{
|
|
remapArchetypes = remapArchetypes
|
|
}.Schedule(archetypeIndex, 1, remapChunksJob);
|
|
|
|
remapArchetypesJob.Complete();
|
|
|
|
entityRemapping.Dispose();
|
|
remapShared.Dispose();
|
|
}
|
|
|
|
public int CheckInternalConsistency()
|
|
{
|
|
var archetype = m_LastArchetype;
|
|
var totalCount = 0;
|
|
while (archetype != null)
|
|
{
|
|
var countInArchetype = 0;
|
|
var chunkCount = 0;
|
|
for (var c = archetype->ChunkList.Begin; c != archetype->ChunkList.End; c = c->Next)
|
|
{
|
|
var chunk = (Chunk*) c;
|
|
Assert.IsTrue(chunk->Archetype == archetype);
|
|
Assert.IsTrue(chunk->Capacity >= chunk->Count);
|
|
Assert.AreEqual(chunk->ChunkListWithEmptySlotsNode.IsInList, chunk->Capacity != chunk->Count);
|
|
|
|
countInArchetype += chunk->Count;
|
|
chunkCount++;
|
|
}
|
|
|
|
Assert.AreEqual(countInArchetype, archetype->EntityCount);
|
|
Assert.AreEqual(chunkCount, archetype->ChunkCount);
|
|
|
|
totalCount += countInArchetype;
|
|
archetype = archetype->PrevArchetype;
|
|
}
|
|
|
|
return totalCount;
|
|
}
|
|
|
|
internal SharedComponentDataManager GetSharedComponentDataManager()
|
|
{
|
|
return m_SharedComponentManager;
|
|
}
|
|
|
|
private struct ManagedArrayStorage
|
|
{
|
|
public object[] ManagedArray;
|
|
}
|
|
}
|
|
}
|