using System; using System.Collections.Generic; namespace AlicizaX { internal sealed class ServiceScope : IDisposable, IServiceRegistry { private readonly Dictionary _servicesByContract = new Dictionary(); private readonly Dictionary> _contractsByService = new Dictionary>(ReferenceComparer.Instance); private readonly List _registrationOrder = new List(); private readonly List _tickables = new List(); private readonly List _lateTickables = new List(); private readonly List _fixedTickables = new List(); private readonly List _gizmoDrawables = new List(); private IServiceTickable[] _tickableSnapshot = Array.Empty(); private IServiceLateTickable[] _lateTickableSnapshot = Array.Empty(); private IServiceFixedTickable[] _fixedTickableSnapshot = Array.Empty(); private IServiceGizmoDrawable[] _gizmoSnapshot = Array.Empty(); private bool _tickablesDirty; private bool _lateTickablesDirty; private bool _fixedTickablesDirty; private bool _gizmoDrawablesDirty; internal ServiceScope(ServiceWorld world, string name, int order) { World = world ?? throw new ArgumentNullException(nameof(world)); Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Scope name cannot be empty.", nameof(name)) : name; Order = order; } internal ServiceWorld World { get; } public string Name { get; } internal int Order { get; } internal bool IsDisposed { get; private set; } public T Register(T service, params Type[] extraContracts) where T : class, IService { EnsureNotDisposed(); if (service == null) throw new ArgumentNullException(nameof(service)); if (service is not IServiceLifecycle lifecycle) throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}."); ValidateService(service); if (_contractsByService.ContainsKey(service)) throw new InvalidOperationException($"Service {service.GetType().FullName} is already registered in scope {Name}."); var contracts = ServiceContractUtility.Collect(service.GetType(), extraContracts); for (var i = 0; i < contracts.Count; i++) { var contract = contracts[i]; if (_servicesByContract.TryGetValue(contract, out var existing)) { throw new InvalidOperationException( $"Scope {Name} already contains contract {contract.FullName} bound to {existing.GetType().FullName}."); } } _contractsByService.Add(service, contracts); _registrationOrder.Add(service); for (var i = 0; i < contracts.Count; i++) _servicesByContract.Add(contracts[i], service); try { lifecycle.Initialize(new ServiceContext(World, this)); AddToLifecycleLists(service); } catch { RemoveBindings(service); throw; } return service; } public bool Unregister() where T : class, IService { if (!_servicesByContract.TryGetValue(typeof(T), out var service)) return false; return Unregister(service); } public bool Unregister(IService service) { if (service == null || !_contractsByService.ContainsKey(service)) return false; if (service is not IServiceLifecycle lifecycle) throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}."); RemoveFromLifecycleLists(service); RemoveBindings(service); lifecycle.Destroy(); return true; } public bool TryGet(out T service) where T : class, IService { if (_servicesByContract.TryGetValue(typeof(T), out var raw)) { service = raw as T; return service != null; } service = null; return false; } public T Require() where T : class, IService { if (TryGet(out T service)) return service; Log.Error($"Scope {Name} does not contain service {typeof(T).FullName}."); return default; } public bool HasContract(Type contractType) => _servicesByContract.ContainsKey(contractType); internal void Tick(float deltaTime) { var snapshot = GetTickSnapshot(); for (var i = 0; i < snapshot.Length; i++) snapshot[i].Tick(deltaTime); } internal void LateTick(float deltaTime) { var snapshot = GetLateTickSnapshot(); for (var i = 0; i < snapshot.Length; i++) snapshot[i].LateTick(deltaTime); } internal void FixedTick(float fixedDeltaTime) { var snapshot = GetFixedTickSnapshot(); for (var i = 0; i < snapshot.Length; i++) snapshot[i].FixedTick(fixedDeltaTime); } internal void DrawGizmos() { var snapshot = GetGizmoSnapshot(); for (var i = 0; i < snapshot.Length; i++) snapshot[i].DrawGizmos(); } public void Dispose() { if (IsDisposed) return; var snapshot = _registrationOrder.ToArray(); for (var i = snapshot.Length - 1; i >= 0; i--) { var service = snapshot[i]; if (!_contractsByService.ContainsKey(service)) continue; if (service is not IServiceLifecycle lifecycle) throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}."); RemoveFromLifecycleLists(service); RemoveContractBindings(service); // skip _registrationOrder.Remove — we clear below lifecycle.Destroy(); } _registrationOrder.Clear(); IsDisposed = true; } private void EnsureNotDisposed() { if (IsDisposed) throw new ObjectDisposedException(Name); } private static void ValidateService(IService service) { if (service is IMonoService && (service is IServiceTickable || service is IServiceLateTickable || service is IServiceFixedTickable || service is IServiceGizmoDrawable)) { throw new InvalidOperationException( $"Mono service {service.GetType().FullName} cannot implement tick lifecycle interfaces."); } } private void AddToLifecycleLists(IService service) { if (service is IServiceTickable tickable) { _tickables.Add(tickable); _tickablesDirty = true; } if (service is IServiceLateTickable late) { _lateTickables.Add(late); _lateTickablesDirty = true; } if (service is IServiceFixedTickable fixed_) { _fixedTickables.Add(fixed_); _fixedTickablesDirty = true; } if (service is IServiceGizmoDrawable gizmo) { _gizmoDrawables.Add(gizmo); _gizmoDrawablesDirty = true; } } private void RemoveFromLifecycleLists(IService service) { if (service is IServiceTickable tickable && _tickables.Remove(tickable)) _tickablesDirty = true; if (service is IServiceLateTickable late && _lateTickables.Remove(late)) _lateTickablesDirty = true; if (service is IServiceFixedTickable fixed_ && _fixedTickables.Remove(fixed_)) _fixedTickablesDirty = true; if (service is IServiceGizmoDrawable gizmo && _gizmoDrawables.Remove(gizmo)) _gizmoDrawablesDirty = true; } private void RemoveBindings(IService service) { if (_contractsByService.TryGetValue(service, out var contracts)) { for (var i = 0; i < contracts.Count; i++) _servicesByContract.Remove(contracts[i]); } _contractsByService.Remove(service); _registrationOrder.Remove(service); } // Used during full Dispose — skips the O(n) _registrationOrder.Remove since we clear the list afterwards. private void RemoveContractBindings(IService service) { if (_contractsByService.TryGetValue(service, out var contracts)) { for (var i = 0; i < contracts.Count; i++) _servicesByContract.Remove(contracts[i]); } _contractsByService.Remove(service); } private IServiceTickable[] GetTickSnapshot() { if (_tickablesDirty) { _tickables.Sort(CompareByOrder); _tickableSnapshot = _tickables.Count > 0 ? _tickables.ToArray() : Array.Empty(); _tickablesDirty = false; } return _tickableSnapshot; } private IServiceLateTickable[] GetLateTickSnapshot() { if (_lateTickablesDirty) { _lateTickables.Sort(CompareByOrder); _lateTickableSnapshot = _lateTickables.Count > 0 ? _lateTickables.ToArray() : Array.Empty(); _lateTickablesDirty = false; } return _lateTickableSnapshot; } private IServiceFixedTickable[] GetFixedTickSnapshot() { if (_fixedTickablesDirty) { _fixedTickables.Sort(CompareByOrder); _fixedTickableSnapshot = _fixedTickables.Count > 0 ? _fixedTickables.ToArray() : Array.Empty(); _fixedTickablesDirty = false; } return _fixedTickableSnapshot; } private IServiceGizmoDrawable[] GetGizmoSnapshot() { if (_gizmoDrawablesDirty) { _gizmoDrawables.Sort(CompareByOrder); _gizmoSnapshot = _gizmoDrawables.Count > 0 ? _gizmoDrawables.ToArray() : Array.Empty(); _gizmoDrawablesDirty = false; } return _gizmoSnapshot; } private static int CompareByOrder(T a, T b) { var left = a is IServiceOrder oa ? oa.Order : 0; var right = b is IServiceOrder ob ? ob.Order : 0; return left.CompareTo(right); } } }