From 88125fc1288a6ad0e4a180da89eeae876fb80566 Mon Sep 17 00:00:00 2001 From: sunqinzhe Date: Wed, 31 Dec 2025 18:45:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84NetDatabase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scenes/SampleScene.unity | 45 +++++ Assets/Scripts/Server/ServerMain.cs | 10 +- Assets/Scripts/TestEntityDataBlock.cs | 3 +- .../Runtime/NativeArrayExtensions.cs | 31 +++ .../Runtime/NativeArrayExtensions.cs.meta | 3 + .../Runtime/NativeDataContainer.cs | 55 ++++++ .../Runtime/NativeDataContainer.cs.meta | 3 + .../Runtime/UnsafeDataContainer.cs | 102 ++++++++++ .../Runtime/UnsafeDataContainer.cs.meta | 3 + .../Runtime/nimin.lowlevel.asmdef | 20 +- .../Runtime/FrameHistoryItem.cs | 44 +++++ .../Runtime/FrameHistoryItem.cs.meta | 3 + .../Runtime/NetDatabase.Snapshot.cs | 15 ++ .../Runtime/NetDatabase.Snapshot.cs.meta | 3 + .../com.nimin.network/Runtime/NetDatabase.cs | 187 ++++++++++++++++++ .../Runtime/NetDatabase.cs.meta | 3 + .../com.nimin.network/Runtime/Test.meta | 3 + .../Runtime/Test/TestNetDatabase.cs | 74 +++++++ .../Runtime/Test/TestNetDatabase.cs.meta | 3 + .../Runtime/TypeMetaDatabase.cs | 144 ++++++++++++++ .../Runtime/TypeMetaDatabase.cs.meta | 3 + .../Runtime/nimin.network.asmdef | 9 +- .../Runtime/EntityCollection.cs | 5 +- .../Runtime/EntityDataStruct.Block.cs | 11 +- .../Runtime/EntityDataStruct.cs | 3 + .../Runtime/EntityDatabase.cs | 19 +- .../Runtime/EntityGenerator.cs | 4 +- UnityECS.sln.DotSettings.user | 2 + 28 files changed, 771 insertions(+), 39 deletions(-) create mode 100644 LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs create mode 100644 LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs.meta create mode 100644 LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs create mode 100644 LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs.meta create mode 100644 LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs create mode 100644 LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs.meta create mode 100644 LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs create mode 100644 LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs.meta create mode 100644 LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs create mode 100644 LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs.meta create mode 100644 LocalPackages/com.nimin.network/Runtime/NetDatabase.cs create mode 100644 LocalPackages/com.nimin.network/Runtime/NetDatabase.cs.meta create mode 100644 LocalPackages/com.nimin.network/Runtime/Test.meta create mode 100644 LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs create mode 100644 LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs.meta create mode 100644 LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs create mode 100644 LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs.meta diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 78ecd64..41242d1 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -354,6 +354,50 @@ MonoBehaviour: m_EditorClassIdentifier: address: 127.0.0.1 driverAsset: {fileID: 11400000, guid: a8a5562854ecf4cc1a49700e8bd93c19, type: 2} +--- !u!1 &1633746920 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1633746922} + - component: {fileID: 1633746921} + m_Layer: 0 + m_Name: Tests + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1633746921 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633746920} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dae5c80c53134e4689ce1b03af81f96e, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &1633746922 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633746920} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1936170114 GameObject: m_ObjectHideFlags: 0 @@ -408,3 +452,4 @@ SceneRoots: - {fileID: 705507995} - {fileID: 1936170116} - {fileID: 1613085078} + - {fileID: 1633746922} diff --git a/Assets/Scripts/Server/ServerMain.cs b/Assets/Scripts/Server/ServerMain.cs index 8124c35..895aea9 100644 --- a/Assets/Scripts/Server/ServerMain.cs +++ b/Assets/Scripts/Server/ServerMain.cs @@ -33,14 +33,12 @@ namespace ECSTest.Server disconnectIndexList = new NativeList(16, Allocator.Persistent); var cannonMeta = EntityDataBlockMeta.Create(); - world.cannonDatabase = - new EntityDatabase(EntityType.Cannon, - new EntityDataBlockChain(cannonMeta)); + var cannonDataBlockChain = new EntityDataBlockChain(cannonMeta, Allocator.Persistent); + world.cannonDatabase = cannonDataBlockChain.CreateEntityDatabase(EntityType.Cannon, 16); var bulletMeta = EntityDataBlockMeta.Create(); - world.bulletDatabase = - new EntityDatabase(EntityType.Bullet, - new EntityDataBlockChain(bulletMeta)); + var bulletDataBlockChain = new EntityDataBlockChain(bulletMeta, Allocator.Persistent); + world.bulletDatabase = bulletDataBlockChain.CreateEntityDatabase(EntityType.Bullet, 16); } private void OnDisable() diff --git a/Assets/Scripts/TestEntityDataBlock.cs b/Assets/Scripts/TestEntityDataBlock.cs index 0689d63..a979580 100644 --- a/Assets/Scripts/TestEntityDataBlock.cs +++ b/Assets/Scripts/TestEntityDataBlock.cs @@ -1,4 +1,5 @@ using GameCore.TinyECS; +using Unity.Collections; using UnityEngine; namespace ECSTest @@ -8,7 +9,7 @@ namespace ECSTest public static void Run() { var meta = EntityDataBlockMeta.Create(); - using var blockChain = new EntityDataBlockChain(meta, 1000); + using var blockChain = new EntityDataBlockChain(meta, Allocator.Persistent, 1000); blockChain.Add(); ref var stateData = ref blockChain.GetDataRef(0); diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs b/LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs new file mode 100644 index 0000000..65a25be --- /dev/null +++ b/LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs @@ -0,0 +1,31 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace GameCore.LowLevel +{ + public static class NativeArrayExtensions + { + public static ref T GetRef(this NativeArray array, int index) + where T : struct + { + if (index < 0 || index >= array.Length) + throw new ArgumentOutOfRangeException(nameof(index)); + unsafe + { + return ref UnsafeUtility.ArrayElementAsRef(array.GetUnsafePtr(), index); + } + } + + public static ref readonly T GetReadOnlyRef(this NativeArray array, int index) + where T : struct + { + if (index < 0 || index >= array.Length) + throw new ArgumentOutOfRangeException(nameof(index)); + unsafe + { + return ref UnsafeUtility.ArrayElementAsRef(array.GetUnsafeReadOnlyPtr(), index); + } + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs.meta b/LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs.meta new file mode 100644 index 0000000..38e0e78 --- /dev/null +++ b/LocalPackages/com.nimin.lowlevel/Runtime/NativeArrayExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9b8d3e5900ff43ab8d285485371094ca +timeCreated: 1767086551 \ No newline at end of file diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs b/LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs new file mode 100644 index 0000000..2df49dc --- /dev/null +++ b/LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs @@ -0,0 +1,55 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace GameCore.LowLevel +{ + public unsafe struct NativeDataContainer : IDisposable where T : unmanaged + { + [NativeDisableUnsafePtrRestriction] + internal UnsafeDataContainer* m_UnsafeDataPtr; + internal AllocatorManager.AllocatorHandle m_Allocator; + + public bool IsCreated => m_UnsafeDataPtr != null; + + public NativeDataContainer(AllocatorManager.AllocatorHandle allocator) + { + m_UnsafeDataPtr = AllocatorManager.Allocate(allocator); + *m_UnsafeDataPtr = new UnsafeDataContainer(UnsafeUtility.SizeOf(), allocator); + + m_Allocator = allocator; + } + + public void Dispose() + { + if (!IsCreated) + { + return; + } + + m_UnsafeDataPtr->Dispose(); + AllocatorManager.Free(m_Allocator, m_UnsafeDataPtr); + m_UnsafeDataPtr = null; + } + + public bool Free(int index) + { + return m_UnsafeDataPtr->Free(index); + } + + public int Alloc(UnsafeDataContainer.AllocOptions options = UnsafeDataContainer.AllocOptions.None) + { + return m_UnsafeDataPtr->Alloc(options); + } + + public ref T ElementAt(int index) + { + return ref m_UnsafeDataPtr->ElementAt(index); + } + + public void Clear() + { + m_UnsafeDataPtr->Clear(); + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs.meta b/LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs.meta new file mode 100644 index 0000000..9eb4cb0 --- /dev/null +++ b/LocalPackages/com.nimin.lowlevel/Runtime/NativeDataContainer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8c8c1ce9e9104a6ab8e96adc313588fe +timeCreated: 1767171335 \ No newline at end of file diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs b/LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs new file mode 100644 index 0000000..0012686 --- /dev/null +++ b/LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs @@ -0,0 +1,102 @@ +using System; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; + +namespace GameCore.LowLevel +{ + public unsafe struct UnsafeDataContainer : IDisposable + { + private struct FreeNode + { + public int next; + public int _padding; + } + + public enum AllocOptions + { + None = 0, + ClearNew = 1, + ClearAll = 2, + } + + public readonly int elementSize; + private UnsafeList m_Buffer; + private readonly int m_NodeNumPerElement; + + public bool IsCreated => m_Buffer.IsCreated; + + public UnsafeDataContainer(int elementSize, AllocatorManager.AllocatorHandle allocator) + { + if (elementSize <= 0) + throw new ArgumentException(nameof(elementSize)); + + this.elementSize = elementSize; + m_NodeNumPerElement = ComputeNodeNumPerElement(elementSize); + + m_Buffer = new UnsafeList(0, allocator); + m_Buffer.Resize(m_NodeNumPerElement, NativeArrayOptions.ClearMemory); + } + + public void Dispose() + { + m_Buffer.Dispose(); + } + + public bool Free(int index) + { + ref var node = ref m_Buffer.ElementAt(index * m_NodeNumPerElement); + if (node.next >= 0) + return false; + + ref var head = ref m_Buffer.ElementAt(0); + node.next = head.next; + head.next = index; + return true; + } + + public int Alloc(AllocOptions options = AllocOptions.None) + { + ref var head = ref m_Buffer.ElementAt(0); + var index = head.next; + if (index == 0) + { + index = m_Buffer.Length / m_NodeNumPerElement; + m_Buffer.Resize(m_Buffer.Length + m_NodeNumPerElement, options == AllocOptions.None ? NativeArrayOptions.UninitializedMemory : NativeArrayOptions.ClearMemory); + m_Buffer.ElementAt(index * m_NodeNumPerElement).next = -1; + return index; + } + + ref var node = ref m_Buffer.ElementAt(index * m_NodeNumPerElement); + if (options == AllocOptions.ClearAll) + { + var ptr = m_Buffer.Ptr + index * m_NodeNumPerElement; + UnsafeUtility.MemClear(ptr, m_NodeNumPerElement * UnsafeUtility.SizeOf()); + } + + head.next = node.next; + node.next = -1; + return index; + } + + public ref T ElementAt(int index) where T : unmanaged + { + ref var node = ref m_Buffer.ElementAt(index * m_NodeNumPerElement); + if (node.next >= 0) + throw new IndexOutOfRangeException(); + + return ref UnsafeUtility.As(ref m_Buffer.ElementAt(index * m_NodeNumPerElement + 1)); + } + + public void Clear() + { + m_Buffer.Resize(m_NodeNumPerElement); + m_Buffer.ElementAt(0).next = 0; + } + + internal static int ComputeNodeNumPerElement(int elementSize) + { + return (elementSize + UnsafeUtility.SizeOf() - 1) / UnsafeUtility.SizeOf() + 1; + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs.meta b/LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs.meta new file mode 100644 index 0000000..630beac --- /dev/null +++ b/LocalPackages/com.nimin.lowlevel/Runtime/UnsafeDataContainer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 345278cb8e5e4c4cbd58ead608d3d098 +timeCreated: 1766977488 \ No newline at end of file diff --git a/LocalPackages/com.nimin.lowlevel/Runtime/nimin.lowlevel.asmdef b/LocalPackages/com.nimin.lowlevel/Runtime/nimin.lowlevel.asmdef index 2ea2e9f..9481d6f 100644 --- a/LocalPackages/com.nimin.lowlevel/Runtime/nimin.lowlevel.asmdef +++ b/LocalPackages/com.nimin.lowlevel/Runtime/nimin.lowlevel.asmdef @@ -5,25 +5,9 @@ "Unity.Collections", "Unity.Mathematics" ], - "includePlatforms": [ - "Android", - "Editor", - "iOS", - "LinuxStandalone64", - "Lumin", - "macOSStandalone", - "PS4", - "Stadia", - "Switch", - "tvOS", - "WSA", - "WebGL", - "WindowsStandalone32", - "WindowsStandalone64", - "XboxOne" - ], + "includePlatforms": [], "excludePlatforms": [], - "allowUnsafeCode": false, + "allowUnsafeCode": true, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, diff --git a/LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs b/LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs new file mode 100644 index 0000000..3afdb06 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs @@ -0,0 +1,44 @@ +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace GameCore.Network +{ + public readonly struct FrameHistoryItem + { + public readonly ulong mask; + public readonly int frameId; + + public const int kMaxBitCount = 64; + + public bool IsDirty => mask != 0ul; + + private FrameHistoryItem(ulong mask, int frameId) + { + this.mask = mask; + this.frameId = frameId; + } + + public static unsafe FrameHistoryItem Make(T baseData, T newData, NativeArray fieldSizeArray, int frameId) where T : unmanaged + { + var basePtr = (byte*) &baseData; + var newPtr = (byte*) &newData; + + ulong mask = 0ul; + var offset = 0; + for (int i = 0, n = math.min(64, fieldSizeArray.Length); i < n; ++i) + { + var nextOffset = fieldSizeArray[i]; + var fieldSize = nextOffset - offset; + if (UnsafeUtility.MemCmp(basePtr + offset, newPtr + offset, fieldSize) != 0) + { + mask |= 1ul << i; + } + + offset = nextOffset; + } + + return new FrameHistoryItem(mask, frameId); + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs.meta b/LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs.meta new file mode 100644 index 0000000..1f462f3 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/FrameHistoryItem.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 89511e3f5bb34d8e8dd6d7cba42f415c +timeCreated: 1767080177 \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs b/LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs new file mode 100644 index 0000000..aea2320 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs @@ -0,0 +1,15 @@ +using System; + +namespace GameCore.Network +{ + public partial struct NetDatabase + { + public readonly struct Snapshot : IDisposable + { + public void Dispose() + { + + } + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs.meta b/LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs.meta new file mode 100644 index 0000000..9ce0b46 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/NetDatabase.Snapshot.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 00a87772eaf0440eacc57a8efcad5707 +timeCreated: 1767177624 \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/NetDatabase.cs b/LocalPackages/com.nimin.network/Runtime/NetDatabase.cs new file mode 100644 index 0000000..d87d91f --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/NetDatabase.cs @@ -0,0 +1,187 @@ +using System; +using GameCore.LowLevel; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using UnityEngine; + +namespace GameCore.Network +{ + public partial struct NetDatabase : IDisposable + { + private struct Entry + { + public int generation; + public int typeIndex; + public int dataIndex; + public ushort historyOffset; + public ushort historyCount; + public int parentIndex; + public int childrenIndex; + } + + private NativeDataContainer entries; + private NativeArray dataContainers; + private NativeList frameHistoryList; + + private readonly int maxHistoryCount; + private readonly AllocatorManager.AllocatorHandle allocator; + + public NetDatabase(TypeMetaDatabase typeMetaDatabase, int maxHistoryCount, + AllocatorManager.AllocatorHandle allocator) + { + entries = new(allocator); + + var typeCount = typeMetaDatabase.GetTotalTypeCount(); + dataContainers = new NativeArray(typeCount, allocator.ToAllocator); + for (var i = 0; i < typeCount; i++) + { + if (typeMetaDatabase.IsValid(i)) + { + dataContainers[i] = new UnsafeDataContainer(typeMetaDatabase.GetTypeSize(i), allocator); + } + } + + frameHistoryList = new NativeList(allocator); + + this.maxHistoryCount = maxHistoryCount; + this.allocator = allocator; + } + + public void Dispose() + { + entries.Dispose(); + + var typeCount = dataContainers.Length; + for (var i = 0; i < typeCount; i++) + { + if (dataContainers[i].IsCreated) + dataContainers[i].Dispose(); + } + dataContainers.Dispose(); + frameHistoryList.Dispose(); + } + + public int Add(T data, int frameId, int parentIndex, out int generation, + TypeMetaDatabase typeMetaDatabase) where T : unmanaged + { + var typeIndex = typeMetaDatabase.TypeIndexOf(); + + var childrenIndex = 0; + generation = 0; + // if (parentIndex > 0) + // { + // if (parentIndex >= entryList.Length) + // return 0; + // + // ref var parentEntry = ref entryList.ElementAt(parentIndex); + // if (parentEntry.childrenIndex > 0) + // { + // childrenIndex = parentEntry.childrenIndex; + // } + // else + // { + // //TODO + // } + // } + + var entryIndex = entries.Alloc(UnsafeDataContainer.AllocOptions.ClearNew); + ref var entry = ref entries.ElementAt(entryIndex); + generation = entry.generation + 1; + entry.generation = generation; + entry.typeIndex = typeIndex; + + ref var dataContainer = ref dataContainers.GetRef(typeIndex); + var dataIndex = dataContainer.Alloc(); + entry.dataIndex = dataIndex; + dataContainer.ElementAt(dataIndex) = data; + + //TODO + //entry.parentIndex = parentIndex; + + entry.historyOffset = 0; + var fieldSizeArray = typeMetaDatabase.GetFieldSizeArray(typeIndex); + var frameHistoryItem = FrameHistoryItem.Make(default, data, fieldSizeArray, frameId); + var minLength = (entryIndex + 1) * maxHistoryCount; + if (frameHistoryList.Length < minLength) + { + frameHistoryList.Resize(minLength, NativeArrayOptions.UninitializedMemory); + } + + frameHistoryList.ElementAt(entryIndex * maxHistoryCount) = frameHistoryItem; + entry.historyCount = 1; + + return entryIndex; + } + + public ref readonly T Get(int index, TypeMetaDatabase typeMetaDatabase) where T : unmanaged + { + var typeIndex = typeMetaDatabase.TypeIndexOf(); + + ref var entry = ref entries.ElementAt(index); + if (entry.typeIndex != typeIndex) + { + throw new Exception("Type mismatch"); + } + + ref var dataContainer = ref dataContainers.GetRef(typeIndex); + return ref dataContainer.ElementAt(entry.dataIndex); + } + + public bool Update(int index, T data, int frameId, TypeMetaDatabase typeMetaDatabase) where T : unmanaged + { + var typeIndex = typeMetaDatabase.TypeIndexOf(); + + ref var entry = ref entries.ElementAt(index); + if (entry.typeIndex != typeIndex) + { + throw new Exception("Type mismatch"); + } + + ref var dataContainer = ref dataContainers.GetRef(typeIndex); + ref var dataRef = ref dataContainer.ElementAt(entry.dataIndex); + var baseData = dataRef; + + var fieldSizeArray = typeMetaDatabase.GetFieldSizeArray(typeIndex); + var frameHistoryItem = FrameHistoryItem.Make(baseData, data, fieldSizeArray, frameId); + if (!frameHistoryItem.IsDirty) + return false; + + dataRef = data; + + var startIndex = (entry.historyOffset + entry.historyCount) % maxHistoryCount; + frameHistoryList.ElementAt(index * maxHistoryCount + startIndex) = frameHistoryItem; + if (entry.historyCount == maxHistoryCount) + { + entry.historyOffset = (ushort) ((entry.historyOffset + 1) % maxHistoryCount); + } + else + { + entry.historyCount++; + } + + return true; + } + + public bool Remove(int index) + { + if (!entries.Free(index)) + return false; + + ref var entry = ref entries.ElementAt(index); + ref var dataContainer = ref dataContainers.GetRef(entry.dataIndex); + if (!dataContainer.Free(entry.dataIndex)) + throw new Exception("Data mismatch"); + + //TODO + + return true; + } + + public int GetGeneration(int index) + { + ref var entry = ref entries.ElementAt(index); + return entry.generation; + } + + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/NetDatabase.cs.meta b/LocalPackages/com.nimin.network/Runtime/NetDatabase.cs.meta new file mode 100644 index 0000000..3d74239 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/NetDatabase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a240ee2c529a484b845eb5030590debf +timeCreated: 1766976618 \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/Test.meta b/LocalPackages/com.nimin.network/Runtime/Test.meta new file mode 100644 index 0000000..123c200 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/Test.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 60aaddd5a9e34cf68dd79f1a493e34b4 +timeCreated: 1767162757 \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs b/LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs new file mode 100644 index 0000000..ccb907d --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs @@ -0,0 +1,74 @@ +using System; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; + +namespace GameCore.Network.Test +{ + public class TestNetDatabase : MonoBehaviour + { + public struct Troop + { + public float2 position; + } + + public struct Soldier + { + public int id; + public int2 coord; + } + + private TypeMetaDatabase typeMetaDatabase; + private NetDatabase netDatabase; + + private void OnEnable() + { + typeMetaDatabase = new TypeMetaDatabase(Allocator.Persistent); + typeMetaDatabase.RegisterType(); + typeMetaDatabase.RegisterType(); + + netDatabase = new NetDatabase(typeMetaDatabase, 16, Allocator.Persistent); + + var handle = new UpdateJob() + { + typeMetaDatabase = typeMetaDatabase, + netDatabase = netDatabase, + frameId = 1, + }.Schedule(); + handle.Complete(); + } + + private void OnDisable() + { + typeMetaDatabase.Dispose(); + netDatabase.Dispose(); + } + + [BurstCompile] + private struct UpdateJob : IJob + { + public TypeMetaDatabase typeMetaDatabase; + public NetDatabase netDatabase; + public int frameId; + + public void Execute() + { + var troop = new Troop() {position = new float2(1, 2)}; + var troopIndex = netDatabase.Add(troop, frameId, 0, out var gen0, typeMetaDatabase); + Debug.Log($"Troop {troopIndex} added, generation {gen0}"); + + var soldier = new Soldier() {id = 1, coord = new int2(1, 1)}; + var soldierIndex = netDatabase.Add(soldier, frameId, 0, out var gen1, typeMetaDatabase); + Debug.Log($"Soldier {soldierIndex} added, generation {gen1}"); + + ref readonly var soldierData = ref netDatabase.Get(soldierIndex, typeMetaDatabase); + Debug.Log($"Soldier id: {soldierData.id}, coord: {soldierData.coord}"); + soldier.coord = new int2(2, 2); + var ok = netDatabase.Update(soldierIndex, soldier, frameId, typeMetaDatabase); + Debug.Log($"Soldier update: {ok}, id: {soldierData.id}, coord: {soldierData.coord}"); + } + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs.meta b/LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs.meta new file mode 100644 index 0000000..7bb00d8 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/Test/TestNetDatabase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dae5c80c53134e4689ce1b03af81f96e +timeCreated: 1767162806 \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs b/LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs new file mode 100644 index 0000000..d208061 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs @@ -0,0 +1,144 @@ +using System; +using System.Diagnostics; +using Unity.Burst; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace GameCore.Network +{ + public struct TypeMetaDatabase : IDisposable + { + private struct TypeInfo + { + public int offset; + public int fieldCount; + } + + private static class TypeIndexHelper + { + public static int currentIndex; + } + + private struct TypeIndex + { + public int index; + + public static readonly SharedStatic> Instance = + SharedStatic>.GetOrCreate>(); + + [BurstDiscard] + static void EnsureInitialized() + { + if (Instance.Data.index == 0) + { + Instance.Data.index = ++TypeIndexHelper.currentIndex; + } + } + + public static int Get() + { + EnsureInitialized(); + return Instance.Data.index; + } + } + + private NativeList infoList; + private NativeList sizeList; + + public TypeMetaDatabase(AllocatorManager.AllocatorHandle allocator) + { + infoList = new NativeList(allocator); + sizeList = new NativeList(allocator); + sizeList.Add(0); + } + + public void Dispose() + { + infoList.Dispose(); + sizeList.Dispose(); + } + + public int GetTotalTypeCount() + { + return infoList.Length; + } + + public int TypeIndexOf() where T : unmanaged + { + var typeIndex = TypeIndex.Get(); + CheckTypeIndex(typeIndex); + return typeIndex; + } + + public void RegisterType() where T : unmanaged + { + var typeIndex = TypeIndex.Get(); + var sizeListStartIndex = sizeList.Length; + + var type = typeof(T); + var fields = type.GetFields(); + var fieldOffset = 0; + foreach (var f in fields) + { + if (fieldOffset != UnsafeUtility.GetFieldOffset(f)) + throw new Exception("Field offset mismatch"); + var size = UnsafeUtility.SizeOf(f.FieldType); + fieldOffset += size; + sizeList.Add(fieldOffset); + } + + if (infoList.Length <= typeIndex) + { + infoList.Resize(typeIndex + 1, NativeArrayOptions.ClearMemory); + } + infoList.ElementAt(typeIndex) = new() + { + offset = sizeListStartIndex, + fieldCount = sizeList.Length - sizeListStartIndex, + }; + } + + public int GetTypeSize(int typeIndex) + { + CheckTypeIndex(typeIndex); + var info = infoList[typeIndex]; + return sizeList[info.offset + info.fieldCount - 1]; + } + + public int GetTypeSize() where T : unmanaged + { + return GetTypeSize(TypeIndex.Get()); + } + + public NativeArray GetFieldSizeArray(int typeIndex) + { + CheckTypeIndex(typeIndex); + var info = infoList[typeIndex]; + return sizeList.AsArray().GetSubArray(info.offset, info.fieldCount); + } + + public NativeArray GetFieldSizeArray() where T : unmanaged + { + return GetFieldSizeArray(TypeIndex.Get()); + } + + public bool IsValid(int typeIndex) + { + if (typeIndex < 0 || typeIndex >= infoList.Length || infoList[typeIndex].offset == 0) + return false; + return true; + } + + public bool IsValid() + { + return IsValid(TypeIndex.Get()); + } + + [Conditional("DEBUG")] + void CheckTypeIndex(int typeIndex) + { + if (!IsValid(typeIndex)) + throw new Exception("Invalid type index"); + } + } +} \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs.meta b/LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs.meta new file mode 100644 index 0000000..0f9be12 --- /dev/null +++ b/LocalPackages/com.nimin.network/Runtime/TypeMetaDatabase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5c20e85b6570434bbb3ca669abfbcf24 +timeCreated: 1766999459 \ No newline at end of file diff --git a/LocalPackages/com.nimin.network/Runtime/nimin.network.asmdef b/LocalPackages/com.nimin.network/Runtime/nimin.network.asmdef index 4dbc5c6..842af03 100644 --- a/LocalPackages/com.nimin.network/Runtime/nimin.network.asmdef +++ b/LocalPackages/com.nimin.network/Runtime/nimin.network.asmdef @@ -2,10 +2,15 @@ "name": "nimin.network", "rootNamespace": "", "references": [ - "Unity.Networking.Transport" + "Unity.Networking.Transport", + "Unity.Collections", + "Unity.Burst", + "nimin.lowlevel", + "Unity.Mathematics" ], + "includePlatforms": [], "excludePlatforms": [], - "allowUnsafeCode": false, + "allowUnsafeCode": true, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, diff --git a/LocalPackages/com.nimin.tinyecs/Runtime/EntityCollection.cs b/LocalPackages/com.nimin.tinyecs/Runtime/EntityCollection.cs index 58394a1..d49579d 100644 --- a/LocalPackages/com.nimin.tinyecs/Runtime/EntityCollection.cs +++ b/LocalPackages/com.nimin.tinyecs/Runtime/EntityCollection.cs @@ -14,11 +14,12 @@ namespace GameCore.TinyECS public int generation; } + public readonly TEnum entityType; + private NativeList entityBuffer; private NativeList entityIndexToDataIndex; - private TEnum entityType; - public EntityCollection(TEnum type, int capacity, Allocator allocator) + public EntityCollection(TEnum type, int capacity, AllocatorManager.AllocatorHandle allocator) { entityType = type; entityBuffer = new (capacity, allocator); diff --git a/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.Block.cs b/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.Block.cs index f2893b7..68d3ea9 100644 --- a/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.Block.cs +++ b/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.Block.cs @@ -19,22 +19,25 @@ namespace GameCore.TinyECS private FixedList512Bytes blockPtrList; private int maxElementCountPerBlock; private int elementCount; + private AllocatorManager.AllocatorHandle allocator; public int BlockCount => blockPtrList.Length; public int BlockCapacity => blockPtrList.Capacity; public int ElementCount => elementCount; public int MaxElementCount => MaxElementCountPerBlock * BlockCapacity; public int MaxElementCountPerBlock => maxElementCountPerBlock; + public AllocatorManager.AllocatorHandle Allocator => allocator; public bool IsFull => elementCount == MaxElementCount; public bool IsEmpty => elementCount == 0; - - public EntityDataBlockChain(EntityDataBlockMeta blockMeta, int maxElementCountPerBlock = 1024) + + public EntityDataBlockChain(EntityDataBlockMeta blockMeta, AllocatorManager.AllocatorHandle allocator, int maxElementCountPerBlock = 1024) { this.blockMeta = blockMeta; blockPtrList = default; this.maxElementCountPerBlock = maxElementCountPerBlock; elementCount = 0; + this.allocator = allocator; } public void Dispose() @@ -48,7 +51,7 @@ namespace GameCore.TinyECS void AllocBlock() { var bufferSize = blockMeta.elementSize * maxElementCountPerBlock; - var ptr = UnsafeUtility.Malloc(bufferSize, blockMeta.elementAlign, Allocator.Persistent); + var ptr = AllocatorManager.Allocate(allocator, bufferSize, blockMeta.elementAlign); blockPtrList.Add((IntPtr)ptr); } @@ -58,7 +61,7 @@ namespace GameCore.TinyECS var pBlock = (void*)blockPtrList[blockIndex]; if (pBlock != null) { - UnsafeUtility.Free(pBlock, Allocator.Persistent); + AllocatorManager.Free(allocator, pBlock); blockPtrList.RemoveAt(blockIndex); } } diff --git a/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.cs b/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.cs index 36b2fb0..d1d22a9 100644 --- a/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.cs +++ b/LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.cs @@ -1,9 +1,12 @@ using System; +using Unity.Collections; namespace GameCore.TinyECS { public interface IEntityDataStruct : IDisposable { + AllocatorManager.AllocatorHandle Allocator { get; } + void Add(); void Remove(int index); diff --git a/LocalPackages/com.nimin.tinyecs/Runtime/EntityDatabase.cs b/LocalPackages/com.nimin.tinyecs/Runtime/EntityDatabase.cs index 3b59b0e..96d8535 100644 --- a/LocalPackages/com.nimin.tinyecs/Runtime/EntityDatabase.cs +++ b/LocalPackages/com.nimin.tinyecs/Runtime/EntityDatabase.cs @@ -14,10 +14,11 @@ namespace GameCore.TinyECS public int Count => collection.Count; - public EntityDatabase(TEnum type, TData dataStruct, int capacity = 16) + internal EntityDatabase(TEnum type, TData dataStruct, int initCapacity, + AllocatorManager.AllocatorHandle allocator) { - generator = new (type, capacity, Allocator.Persistent); - collection = new (type, capacity, Allocator.Persistent); + generator = new(type, initCapacity, allocator); + collection = new(type, initCapacity, allocator); this.dataStruct = dataStruct; } @@ -41,7 +42,7 @@ namespace GameCore.TinyECS var index = collection.GetIndex(entity); return dataStruct.GetData(index); } - + public ref T GetDataRef(Entity entity) where T : unmanaged { var index = collection.GetIndex(entity); @@ -55,4 +56,14 @@ namespace GameCore.TinyECS dataStruct.Dispose(); } } + + public static class EntityDataBaseExtensions + { + public static EntityDatabase CreateEntityDatabase(this TData dataStruct, TEnum type, int initCapacity) + where TEnum : unmanaged, IConvertible + where TData : unmanaged, IEntityDataStruct + { + return new EntityDatabase(type, dataStruct, initCapacity, dataStruct.Allocator); + } + } } \ No newline at end of file diff --git a/LocalPackages/com.nimin.tinyecs/Runtime/EntityGenerator.cs b/LocalPackages/com.nimin.tinyecs/Runtime/EntityGenerator.cs index c6eee14..48d8130 100644 --- a/LocalPackages/com.nimin.tinyecs/Runtime/EntityGenerator.cs +++ b/LocalPackages/com.nimin.tinyecs/Runtime/EntityGenerator.cs @@ -7,12 +7,12 @@ namespace GameCore.TinyECS public struct EntityGenerator : IDisposable where TEnum : unmanaged, IConvertible { + public readonly TEnum entityType; private NativeList indexToGeneration; private int beginSearchIndex; private int freeCount; - private TEnum entityType; - public EntityGenerator(TEnum type, int capacity, Allocator allocator) + public EntityGenerator(TEnum type, int capacity, AllocatorManager.AllocatorHandle allocator) { indexToGeneration = new(capacity, allocator) { 0 }; beginSearchIndex = 1; diff --git a/UnityECS.sln.DotSettings.user b/UnityECS.sln.DotSettings.user index ee24df2..ec55cfd 100644 --- a/UnityECS.sln.DotSettings.user +++ b/UnityECS.sln.DotSettings.user @@ -1,5 +1,7 @@  ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded \ No newline at end of file