using Ghost.Core; using Misaki.HighPerformance.Jobs; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using System.Runtime.CompilerServices; namespace Ghost.Entities; public partial class World { private static readonly List s_worlds = new(4); private static readonly Queue> s_freeWorldSlots = new(); internal static Identifier EmptyArchetypeID => new (0); public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count; public static World Create(JobScheduler jobScheduler, int entityCapacity = 16) { lock (s_worlds) { if (s_freeWorldSlots.TryDequeue(out var index)) { s_worlds[index.value] = new World(index, entityCapacity, jobScheduler); } else { index = new Identifier(s_worlds.Count); s_worlds.Add(new World(index, entityCapacity, jobScheduler)); } return s_worlds[index.value]!; } } public static void Destroy(Identifier id) { lock (s_worlds) { if (id.value < 0 || id.value >= s_worlds.Count) { return; } var world = s_worlds[id.value]; world?.Dispose(); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Result GetWorld(Identifier id) { if (id.value < 0 || id.value >= s_worlds.Count) { return ErrorStatus.InvalidArgument; } var world = s_worlds[id.value]; if (world is null) { return ErrorStatus.NotFound; } return world; } } public partial class World : IIdentifierType, IDisposable, IEquatable { private readonly Identifier _id; private readonly JobScheduler _jobScheduler; private readonly EntityManager _entityManager; private readonly EntityCommandBuffer _entityCommandBuffer; private readonly EntityCommandBuffer[] _threadLocalECBs; private UnsafeList _archetypes; private UnsafeList _entityQueries; private UnsafeHashMap> _archetypeLookup; // Signature Hash to Archetype ID private UnsafeHashMap> _querieLookup; // Query Mask Hash to Query ID private bool _disposed = false; internal int ArchetypeCount => _archetypes.Count; /// /// Gets the unique identifier of this world. /// public Identifier ID => _id; /// /// Gets the job scheduler associated with this world. /// public JobScheduler JobScheduler => _jobScheduler; /// /// Gets the publicntity manager for this world. /// public EntityManager EntityManager => _entityManager; /// /// Gets the main entity command buffer for this world. /// /// /// Use to get thread-local command buffers for multi-threaded jobs. /// public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer; private World(Identifier id, int entityCapacity, JobScheduler jobScheduler) { _id = id; _jobScheduler = jobScheduler; _archetypes = new UnsafeList(16, Allocator.Persistent); _entityQueries = new UnsafeList(16, Allocator.Persistent); _archetypeLookup = new UnsafeHashMap>(16, Allocator.Persistent); _querieLookup = new UnsafeHashMap>(16, Allocator.Persistent); _entityManager = new EntityManager(this, entityCapacity); _entityCommandBuffer = new EntityCommandBuffer(_entityManager); _threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount]; for (var i = 0; i < jobScheduler.WorkerCount; i++) { _threadLocalECBs[i] = new EntityCommandBuffer(_entityManager); } // Create the empty archetype CreateArchetype(ReadOnlySpan>.Empty, 0); } ~World() { Dispose(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier CreateArchetype(ReadOnlySpan> componentTypeIDs, int signatureHash) { var arcID = new Identifier(_archetypes.Count); _archetypes.Add(new Archetype(arcID, _id, componentTypeIDs)); _archetypeLookup.Add(signatureHash, arcID); for (int i = 0; i < _entityQueries.Count; i++) { ref var query = ref _entityQueries[i]; query.AddArchetypeIfMatch(in _archetypes[arcID.value]); } return arcID; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier GetArchetypeIDBySignatureHash(int signatureHash) { if (_archetypeLookup.TryGetValue(signatureHash, out var arcID)) { return arcID; } return Identifier.Invalid; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ref Archetype GetArchetypeReference(Identifier id) { return ref _archetypes[id.value]; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier CreateEntityQuery(EntityQueryMask mask, int maskHash) { var queryID = new Identifier(_entityQueries.Count); _entityQueries.Add(new EntityQuery(queryID, _id, mask)); _querieLookup.Add(maskHash, queryID); ref var query = ref _entityQueries[queryID.value]; for (var i = 0; i < _archetypes.Count; i++) { query.AddArchetypeIfMatch(in _archetypes[i]); } return queryID; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal Identifier GetEntityQueryIDByMaskHash(int maskHash) { if (_querieLookup.TryGetValue(maskHash, out var queryID)) { return queryID; } return Identifier.Invalid; } internal void PlaybackEntityCommandBuffers() { _entityCommandBuffer.Playback(); for (var i = 0; i < _threadLocalECBs.Length; i++) { _threadLocalECBs[i].Playback(); } } /// /// Gets a reference to the entity query with the specified identifier. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public ref EntityQuery GetEntityQueryReference(Identifier id) { return ref _entityQueries[id.value]; } /// /// Gets the thread-local entity command buffer for the specified thread index. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public EntityCommandBuffer GetThreadLocalEntityCommandBuffer(int threadIndex) { return _threadLocalECBs[threadIndex]; } public bool Equals(World? other) { return other is not null && _id == other._id; } public override int GetHashCode() { return _id.GetHashCode(); } public override bool Equals(object? obj) { return obj is World other && Equals(other); } public static bool operator ==(World? left, World? right) { return left?.Equals(right) ?? right is null; } public static bool operator !=(World? left, World? right) { return !(left == right); } public void Dispose() { if (_disposed) { return; } foreach (var archetype in _archetypes) { archetype.Dispose(); } foreach (var query in _entityQueries) { query.Dispose(); } _entityManager.Dispose(); _entityCommandBuffer.Dispose(); for (var i = 0; i < _threadLocalECBs.Length; i++) { _threadLocalECBs[i].Dispose(); } _archetypes.Dispose(); _entityQueries.Dispose(); _archetypeLookup.Dispose(); _querieLookup.Dispose(); s_freeWorldSlots.Enqueue(_id); s_worlds[_id] = null; _disposed = true; GC.SuppressFinalize(this); } }