using Ghost.Core; using Misaki.HighPerformance.Jobs; 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)] public 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 World? GetWorld(Identifier id) { if (id.Value < 0 || id.Value >= s_worlds.Count) { return null; } return s_worlds[id.Value]; } } 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 ComponentManager _componentManager; private readonly SystemManager _systemManager; private int _version; private bool _disposed = false; /// /// 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 component manager for this world. /// public ComponentManager ComponentManager => _componentManager; /// /// 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); _componentManager = new ComponentManager(this); _systemManager = new SystemManager(this); if (jobScheduler != null) { _threadLocalECBs = new EntityCommandBuffer[jobScheduler.WorkerCount]; for (var i = 0; i < jobScheduler.WorkerCount; i++) { _threadLocalECBs[i] = new EntityCommandBuffer(_entityManager); } } } ~World() { Dispose(); } [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 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; } _entityManager.Dispose(); _entityCommandBuffer.Dispose(); if (_threadLocalECBs != null) { foreach (var v in _threadLocalECBs) { v.Dispose(); } } s_freeWorldSlots.Enqueue(_id); s_worlds[_id] = null; _disposed = true; GC.SuppressFinalize(this); } }