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; } private NativeDataContainer entries; private NativeHashMap> childrenIndexMap; 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); childrenIndexMap = new(8, 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(); foreach (var kvp in childrenIndexMap) { kvp.Value.Dispose(); } childrenIndexMap.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(); generation = 0; 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; entry.parentIndex = parentIndex; if (parentIndex > 0) { if (!childrenIndexMap.TryGetValue(parentIndex, out var list)) { list = new UnsafeList(4, allocator); } list.Add(entryIndex); childrenIndexMap[parentIndex] = list; } 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) { return RemoveInternal(index, true); } private bool RemoveInternal(int index, bool removeFromParent) { if (!entries.HasValue(index)) return false; ref var entry = ref entries.ElementAt(index); ref var dataContainer = ref dataContainers.GetRef(entry.typeIndex); if (!dataContainer.Free(entry.dataIndex)) throw new Exception("Data mismatch"); if (entry.parentIndex > 0) { if (removeFromParent && childrenIndexMap.TryGetValue(entry.parentIndex, out var list)) { var i = list.IndexOf(index); list.RemoveAtSwapBack(i); childrenIndexMap[entry.parentIndex] = list; } } { if (childrenIndexMap.TryGetValue(index, out var list)) { for (int i = 0, len = list.Length; i < len; ++i) { RemoveInternal(list[i], false); } list.Dispose(); childrenIndexMap.Remove(index); } } entries.Free(index); return true; } public int GetGeneration(int index) { ref var entry = ref entries.ElementAt(index); return entry.generation; } } }