整理EntityDatabase代码

master
sunqinzhe 1 week ago
parent 6c8108e3ea
commit d9b66edd89
  1. 19
      Assets/Scripts/Server/ServerGameData.cs
  2. 3
      Assets/Scripts/Server/ServerGameData.cs.meta
  3. 53
      Assets/Scripts/Server/ServerGameLogicJobs.cs
  4. 31
      Assets/Scripts/Server/ServerMain.cs
  5. 17
      Assets/Scripts/Server/World.cs
  6. 0
      Assets/Scripts/Server/World.cs.meta
  7. 9
      Assets/Scripts/Server/WorldManager.cs
  8. 2
      Assets/Scripts/TestEntityDataBlock.cs
  9. 33
      LocalPackages/com.nimin.tinyecs/Runtime/ComponentId.cs
  10. 5
      LocalPackages/com.nimin.tinyecs/Runtime/Entity.cs
  11. 25
      LocalPackages/com.nimin.tinyecs/Runtime/EntityCollection.cs
  12. 19
      LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.Block.cs
  13. 6
      LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.Block.gen.cs
  14. 3
      LocalPackages/com.nimin.tinyecs/Runtime/EntityDataStruct.cs
  15. 25
      LocalPackages/com.nimin.tinyecs/Runtime/EntityDatabase.cs
  16. 3
      LocalPackages/com.nimin.tinyecs/Runtime/EntityGenerator.cs
  17. 21
      LocalPackages/com.nimin.tinyecs/Runtime/nimin.tinyecs.asmdef
  18. 5
      UnityECS.sln.DotSettings.user

@ -0,0 +1,19 @@
using Unity.Mathematics;
namespace ECSTest.Server
{
public struct CannonData
{
public float2 position;
public float2 velocity;
public float direction;
public int cooldownFrameId;
public int health;
public int deathFrameId;
}
public struct BulletData
{
public float2 position;
}
}

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8e44ed253ab647b3910794e124e6402e
timeCreated: 1766030729

@ -1,13 +1,64 @@
using GameCore.TinyECS;
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
using UnityEngine;
using Random = Unity.Mathematics.Random;
namespace ECSTest.Server
{
[BurstCompile]
public struct TestWorldJob : IJob
{
public World world;
public void Execute()
{
// var componentId = ComponentId<BulletData>.Get();
// Debug.LogError($"XXXX bulletData id: {componentId}");
// var componentId2 = ComponentId<CannonData>.Get();
// Debug.LogError($"xxxx2222 CannonData id: {componentId2}");
}
}
[BurstCompile]
public struct SpawnCannonJob : IJob
{
public World world;
public NativeList<bool> deferList;
public uint seed;
public int maxCount;
public int spawnCountPerFrame;
public Rect rect;
public void Execute()
{
ref var database = ref world.cannonDatabase;
if (database.Count >= maxCount)
return;
var random = new Random(seed);
for (int i = 0; i < spawnCountPerFrame; i++)
{
var pos = random.NextFloat2(rect.min, rect.max);
var dir = random.NextFloat(math.PI2);
math.sincos(dir, out var s, out var c);
var velocity = new float2(c, s);
var entity = database.AddEntity();
//TODO
}
}
}
[BurstCompile]
public struct SpawnBulletJob : IJob
{
public WorldManager worldManager;
public World World;
public void Execute()
{

@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using GameCore.Network;
using GameCore.TinyECS;
using Unity.Collections;
using Unity.Jobs;
using Unity.Networking.Transport;
@ -21,26 +22,47 @@ namespace ECSTest.Server
private NativeList<NetworkConnection> connectionList;
private NativeList<int> disconnectIndexList;
private World world;
private JobHandle gameLogicJobHandle;
private void OnEnable()
{
driver = driverAsset.CreateServerDriver(address, out unreliablePipeline, out reliablePipeline);
connectionList = new NativeList<NetworkConnection>(16, Allocator.Persistent);
disconnectIndexList = new NativeList<int>(16, Allocator.Persistent);
var cannonMeta = EntityDataBlockMeta.Create<CannonData>();
world.cannonDatabase =
new EntityDatabase<EntityType, EntityDataBlockChain>(EntityType.Cannon,
new EntityDataBlockChain(cannonMeta));
var bulletMeta = EntityDataBlockMeta.Create<BulletData>();
world.bulletDatabase =
new EntityDatabase<EntityType, EntityDataBlockChain>(EntityType.Bullet,
new EntityDataBlockChain(bulletMeta));
}
private void OnDisable()
{
networkJobHandle.Complete();
gameLogicJobHandle.Complete();
driver.Dispose();
connectionList.Dispose();
disconnectIndexList.Dispose();
world.Dispose();
}
void Update()
{
var componentId = ComponentId<BulletData>.Get();
Debug.LogError($"AAAA bulletData id: {componentId}");
var componentId2 = ComponentId<CannonData>.Get();
Debug.LogError($"AAAA2222 CannonData id: {componentId2}");
networkJobHandle.Complete();
var concurrentDriver = driver.ToConcurrent();
@ -60,6 +82,15 @@ namespace ECSTest.Server
disconnectIndexList = disconnectIndexList.AsParallelWriter(),
}.Schedule(connectionList, 16, networkJobHandle);
if (gameLogicJobHandle.IsCompleted)
{
gameLogicJobHandle.Complete();
gameLogicJobHandle = new TestWorldJob()
{
world = world,
}.Schedule(networkJobHandle);
}
}
}
}

@ -0,0 +1,17 @@
using System;
using GameCore.TinyECS;
namespace ECSTest.Server
{
public struct World : IDisposable
{
public EntityDatabase<EntityType, EntityDataBlockChain> cannonDatabase;
public EntityDatabase<EntityType, EntityDataBlockChain> bulletDatabase;
public void Dispose()
{
cannonDatabase.Dispose();
bulletDatabase.Dispose();
}
}
}

@ -1,9 +0,0 @@
using GameCore.TinyECS;
namespace ECSTest.Server
{
public struct WorldManager
{
public EntityDatabase<EntityType, EntityDataBlockChain> bulletDatabase;
}
}

@ -11,7 +11,7 @@ namespace ECSTest
using var blockChain = new EntityDataBlockChain(meta, 1000);
blockChain.Add();
ref var stateData = ref blockChain.GetData<StateData>(0);
ref var stateData = ref blockChain.GetDataRef<StateData>(0);
stateData.hp = 1000;
var view = blockChain.AsBlockView(0);

@ -1,25 +1,30 @@
using System;
using Unity.Burst;
namespace GameCore.TinyECS
{
public struct ComponentId
internal static class ComponentIdHelper
{
struct Inner<T> where T : unmanaged
{
public int id;
private Inner(int i) => id = i;
public static Inner<T> Instance = default;
}
static int currentId;
public static int currentId;
}
public struct ComponentId<T>
{
public int id;
public static readonly SharedStatic<ComponentId<T>> Instance = SharedStatic<ComponentId<T>>.GetOrCreate<ComponentId<T>>();
public static int Get<T>() where T : unmanaged
[BurstDiscard]
static void EnsureInitialized()
{
if (Inner<T>.Instance.id == 0)
if (Instance.Data.id == 0)
{
Inner<T>.Instance.id = ++currentId;
Instance.Data.id = ++ComponentIdHelper.currentId;
}
return Inner<T>.Instance.id;
}
public static int Get()
{
EnsureInitialized();
return Instance.Data.id;
}
}
}

@ -1,9 +1,10 @@
using System;
using GameCore.LowLevel;
using Unity.Collections.LowLevel.Unsafe;
namespace GameCore.TinyECS
{
public readonly struct Entity<TEnum> : IEquatable<Entity<TEnum>> where TEnum : unmanaged
public readonly struct Entity<TEnum> : IEquatable<Entity<TEnum>> where TEnum : unmanaged, IConvertible
{
public readonly TEnum type;
public readonly int index;
@ -19,7 +20,7 @@ namespace GameCore.TinyECS
public override string ToString() => $"Entity<{type}>({index},{generation})";
public bool Equals(Entity<TEnum> other) => type.Equals(other.type) && index == other.index && generation == other.generation;
public override bool Equals(object obj) => obj is Entity<TEnum> other && Equals(other);
public override int GetHashCode() => LowLevelUtility.HashCombine(type.GetHashCode(), index, generation);
public override int GetHashCode() => LowLevelUtility.HashCombine(UnsafeUtility.EnumToInt(type), index, generation);
public static bool operator ==(Entity<TEnum> lhs, Entity<TEnum> rhs) => lhs.Equals(rhs);
public static bool operator !=(Entity<TEnum> lhs, Entity<TEnum> rhs) => !(lhs == rhs);

@ -1,10 +1,12 @@
using System;
using Unity.Collections;
using System.Diagnostics;
using Unity.Collections.LowLevel.Unsafe;
namespace GameCore.TinyECS
{
public struct EntityCollection<TEnum> : IDisposable where TEnum : unmanaged
public struct EntityCollection<TEnum> : IDisposable
where TEnum : unmanaged, IConvertible
{
private struct Buffer
{
@ -60,6 +62,25 @@ namespace GameCore.TinyECS
return dataIndex;
}
public int GetIndex(Entity<TEnum> entity)
{
CheckEntityType(entity);
if (entity.index >= entityIndexToDataIndex.Length)
{
throw new Exception($"Invalid entity: {entity}");
}
var index = entityIndexToDataIndex[entity.index];
var buf = entityBuffer[index];
if (buf.index != entity.index || buf.generation != entity.generation)
{
throw new Exception($"Invalid entity: {entity}");
}
return index;
}
public bool TryGetIndex(Entity<TEnum> entity, out int index)
{
CheckEntityType(entity);
@ -115,7 +136,7 @@ namespace GameCore.TinyECS
[Conditional("DEBUG")]
void CheckEntityType(Entity<TEnum> entity)
{
if (!entity.type.Equals(entityType))
if (!UnsafeUtility.EnumEquals(entityType, entity.type))
throw new Exception($"Wrong entity type: {entity.type}");
}

@ -29,7 +29,7 @@ namespace GameCore.TinyECS
public bool IsFull => elementCount == MaxElementCount;
public bool IsEmpty => elementCount == 0;
public EntityDataBlockChain(EntityDataBlockMeta blockMeta, int maxElementCountPerBlock)
public EntityDataBlockChain(EntityDataBlockMeta blockMeta, int maxElementCountPerBlock = 1024)
{
this.blockMeta = blockMeta;
blockPtrList = default;
@ -72,7 +72,7 @@ namespace GameCore.TinyECS
void* ComponentPtrAt<T>(int elementIndex) where T : unmanaged
{
var componentId = ComponentId.Get<T>();
var componentId = ComponentId<T>.Get();
var componentIndex = blockMeta.componentIdToIndex[componentId];
if (componentIndex > 0)
{
@ -133,7 +133,16 @@ namespace GameCore.TinyECS
elementCount--;
}
public ref T GetData<T>(int index) where T : unmanaged
public T GetData<T>(int index) where T : unmanaged
{
CheckElementIndex(index);
var pComponent = ComponentPtrAt<T>(index);
if (pComponent == null)
throw new Exception($"Invalid component type: {typeof(T)}");
return UnsafeUtility.AsRef<T>(pComponent);
}
public ref T GetDataRef<T>(int index) where T : unmanaged
{
CheckElementIndex(index);
var pComponent = ComponentPtrAt<T>(index);
@ -150,7 +159,7 @@ namespace GameCore.TinyECS
}
}
public unsafe readonly struct EntityDataBlockView
public readonly unsafe struct EntityDataBlockView
{
private readonly void* ptr;
public readonly EntityDataBlockMeta meta;
@ -172,7 +181,7 @@ namespace GameCore.TinyECS
void* ComponentPtrAt<T>(int elementIndex) where T : unmanaged
{
var componentId = ComponentId.Get<T>();
var componentId = ComponentId<T>.Get();
var componentIndex = meta.componentIdToIndex[componentId];
if (componentIndex > 0)
{

@ -15,7 +15,7 @@ namespace GameCore.TinyECS
meta.componentIdToIndex.Length = meta.componentIdToIndex.Capacity;
meta.componentIdToOffset.Length = meta.componentIdToOffset.Capacity;
var id1 = ComponentId.Get<T1>();
var id1 = ComponentId<T1>.Get();
meta.componentIdToIndex[id1] = 1;
meta.componentIdToOffset[id1] = 0;
meta.elementSize = UnsafeUtility.SizeOf<T1>();
@ -31,8 +31,8 @@ namespace GameCore.TinyECS
meta.componentIdToIndex.Length = meta.componentIdToIndex.Capacity;
meta.componentIdToOffset.Length = meta.componentIdToOffset.Capacity;
var id1 = ComponentId.Get<T1>();
var id2 = ComponentId.Get<T2>();
var id1 = ComponentId<T1>.Get();
var id2 = ComponentId<T2>.Get();
meta.componentIdToIndex[id1] = 1;
meta.componentIdToIndex[id2] = 2;
meta.componentIdToOffset[id1] = (short)LowLevelUtility.OffsetOf<(T1, T2), T1>();

@ -6,5 +6,8 @@ namespace GameCore.TinyECS
{
void Add();
void Remove(int index);
T GetData<T>(int index) where T : unmanaged;
ref T GetDataRef<T>(int index) where T : unmanaged;
}
}

@ -4,12 +4,23 @@ using Unity.Mathematics;
namespace GameCore.TinyECS
{
public struct EntityDatabase<TEnum, TData> : IDisposable where TEnum : unmanaged where TData : unmanaged, IEntityDataStruct
public struct EntityDatabase<TEnum, TData> : IDisposable
where TEnum : unmanaged, IConvertible
where TData : unmanaged, IEntityDataStruct
{
public TData dataStruct;
public EntityGenerator<TEnum> generator;
public EntityCollection<TEnum> collection;
public int Count => collection.Count;
public EntityDatabase(TEnum type, TData dataStruct, int capacity = 16)
{
generator = new (type, capacity, Allocator.Persistent);
collection = new (type, capacity, Allocator.Persistent);
this.dataStruct = dataStruct;
}
public Entity<TEnum> AddEntity()
{
var entity = generator.Create();
@ -25,6 +36,18 @@ namespace GameCore.TinyECS
dataStruct.Remove(index);
}
public T GetData<T>(Entity<TEnum> entity) where T : unmanaged
{
var index = collection.GetIndex(entity);
return dataStruct.GetData<T>(index);
}
public ref T GetDataRef<T>(Entity<TEnum> entity) where T : unmanaged
{
var index = collection.GetIndex(entity);
return ref dataStruct.GetDataRef<T>(index);
}
public void Dispose()
{
generator.Dispose();

@ -4,7 +4,8 @@ using Unity.Mathematics;
namespace GameCore.TinyECS
{
public struct EntityGenerator<TEnum> : IDisposable where TEnum : unmanaged
public struct EntityGenerator<TEnum> : IDisposable
where TEnum : unmanaged, IConvertible
{
private NativeList<int> indexToGeneration;
private int beginSearchIndex;

@ -4,25 +4,10 @@
"references": [
"Unity.Collections",
"Unity.Mathematics",
"nimin.lowlevel"
],
"includePlatforms": [
"Android",
"Editor",
"iOS",
"LinuxStandalone64",
"Lumin",
"macOSStandalone",
"PS4",
"Stadia",
"Switch",
"tvOS",
"WSA",
"WebGL",
"WindowsStandalone32",
"WindowsStandalone64",
"XboxOne"
"nimin.lowlevel",
"Unity.Burst"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,

@ -1,2 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystem_002Ecs_002Fl_003AC_0021_003FUsers_003Fsunqinzhe_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F3ea01e77be99618395f448b16bd7cc2bd4248bd9e5d8474964b2d97f39c5d74_003FSystem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAllocator_002Ecs_002Fl_003AC_0021_003FUsers_003Fsunqinzhe_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F37cc348edc804f4cb176b63962c716e7177200_003Fa6_003F9ff064e4_003FAllocator_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJobsUtility_002Ecs_002Fl_003AC_0021_003FUsers_003Fsunqinzhe_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F37cc348edc804f4cb176b63962c716e7177200_003F4c_003Fe16da97b_003FJobsUtility_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASystem_002Ecs_002Fl_003AC_0021_003FUsers_003Fsunqinzhe_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F3ea01e77be99618395f448b16bd7cc2bd4248bd9e5d8474964b2d97f39c5d74_003FSystem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUnsafeUtility_002Ecs_002Fl_003AC_0021_003FUsers_003Fsunqinzhe_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F37cc348edc804f4cb176b63962c716e7177200_003F8b_003F35bd5f7e_003FUnsafeUtility_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
Loading…
Cancel
Save