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 = null, 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)] internal static World GetWorldUncheck(Identifier id) { #if DEBUG || GHOST_EDITOR if (id.Value < 0 || id.Value >= s_worlds.Count) { throw new ArgumentOutOfRangeException(nameof(id), "World ID is out of range."); } var world = s_worlds[id.Value]; return world is null ? throw new InvalidOperationException("World not found.") : world; #else return s_worlds[id.value]!; #endif } [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]; return world is null ? ErrorStatus.NotFound : world; } } public partial class World : IDisposable, IEquatable { private readonly Identifier _id; private readonly JobScheduler? _jobScheduler; private readonly EntityManager _entityManager; private readonly EntityCommandBuffer _entityCommandBuffer; private readonly EntityCommandBuffer[]? _threadLocalECBs; private readonly SystemManager _systemManager; private UnsafeList _archetypes; private UnsafeList _entityQueries; private UnsafeHashMap> _archetypeLookup; // Signature Hash to Archetype ID private UnsafeHashMap> _querieLookup; // Query Mask Hash to Query ID private int _version; 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 system manager for this world. /// public SystemManager SystemManager => _systemManager; /// /// Gets the current version number of the world. /// public int Version => Interlocked.CompareExchange(ref _version, 0, 0); /// /// 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; _entityManager = new EntityManager(this, entityCapacity); _entityCommandBuffer = new EntityCommandBuffer(_entityManager); _systemManager = new SystemManager(this); _archetypes = new UnsafeList(16, Allocator.Persistent); _entityQueries = new UnsafeList(16, Allocator.Persistent); _archetypeLookup = new UnsafeHashMap>(16, Allocator.Persistent); _querieLookup = new UnsafeHashMap>(16, Allocator.Persistent); if (jobScheduler != null) { _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; } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void PlaybackEntityCommandBuffers() { _entityCommandBuffer.Playback(); if (_threadLocalECBs != null) { for (var i = 0; i < _threadLocalECBs.Length; i++) { _threadLocalECBs[i].Playback(); } } } [MethodImpl(MethodImplOptions.AggressiveInlining)] internal int AdvanceVersion() { return Interlocked.Increment(ref _version); } /// /// 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) { if (_threadLocalECBs == null) { throw new InvalidOperationException("This world does not have a JobScheduler associated with it."); } 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 (ref var archetype in _archetypes) { archetype.Dispose(); } foreach (ref var query in _entityQueries) { query.Dispose(); } _entityManager.Dispose(); _entityCommandBuffer.Dispose(); if (_threadLocalECBs != null) { foreach (var v in _threadLocalECBs) { v.Dispose(); } } _archetypes.Dispose(); _entityQueries.Dispose(); _archetypeLookup.Dispose(); _querieLookup.Dispose(); s_freeWorldSlots.Enqueue(_id); s_worlds[_id] = null; _disposed = true; GC.SuppressFinalize(this); } }