com.alicizax.unity.framework/Runtime/ABase/Service/Core/ServiceScope.cs

308 lines
11 KiB
C#
Raw Normal View History

2026-03-26 10:49:41 +08:00
using System;
using System.Collections.Generic;
namespace AlicizaX
{
2026-04-20 13:46:44 +08:00
internal sealed class ServiceScope : IDisposable, IServiceRegistry
2026-03-26 10:49:41 +08:00
{
private readonly Dictionary<Type, IService> _servicesByContract = new Dictionary<Type, IService>();
private readonly Dictionary<IService, List<Type>> _contractsByService = new Dictionary<IService, List<Type>>(ReferenceComparer<IService>.Instance);
private readonly List<IService> _registrationOrder = new List<IService>();
private readonly List<IServiceTickable> _tickables = new List<IServiceTickable>();
private readonly List<IServiceLateTickable> _lateTickables = new List<IServiceLateTickable>();
private readonly List<IServiceFixedTickable> _fixedTickables = new List<IServiceFixedTickable>();
private readonly List<IServiceGizmoDrawable> _gizmoDrawables = new List<IServiceGizmoDrawable>();
private IServiceTickable[] _tickableSnapshot = Array.Empty<IServiceTickable>();
private IServiceLateTickable[] _lateTickableSnapshot = Array.Empty<IServiceLateTickable>();
private IServiceFixedTickable[] _fixedTickableSnapshot = Array.Empty<IServiceFixedTickable>();
private IServiceGizmoDrawable[] _gizmoSnapshot = Array.Empty<IServiceGizmoDrawable>();
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;
}
2026-04-20 13:46:44 +08:00
internal ServiceWorld World { get; }
2026-03-26 10:49:41 +08:00
public string Name { get; }
2026-04-20 13:46:44 +08:00
internal int Order { get; }
2026-03-26 10:49:41 +08:00
2026-04-20 13:46:44 +08:00
internal bool IsDisposed { get; private set; }
2026-03-26 10:49:41 +08:00
public T Register<T>(T service, params Type[] extraContracts)
where T : class, IService
{
EnsureNotDisposed();
if (service == null)
throw new ArgumentNullException(nameof(service));
2026-04-20 13:46:44 +08:00
if (service is not IServiceLifecycle lifecycle)
throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}.");
2026-03-26 10:49:41 +08:00
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
{
2026-04-20 13:46:44 +08:00
lifecycle.Initialize(new ServiceContext(World, this));
2026-03-26 10:49:41 +08:00
AddToLifecycleLists(service);
}
catch
{
RemoveBindings(service);
throw;
}
return service;
}
public bool Unregister<T>() 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;
2026-04-20 13:46:44 +08:00
if (service is not IServiceLifecycle lifecycle)
throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}.");
2026-03-26 10:49:41 +08:00
RemoveFromLifecycleLists(service);
RemoveBindings(service);
2026-04-20 13:46:44 +08:00
lifecycle.Destroy();
2026-03-26 10:49:41 +08:00
return true;
}
public bool TryGet<T>(out T service) where T : class, IService
{
if (_servicesByContract.TryGetValue(typeof(T), out var raw))
{
service = raw as T;
return service != null;
}
2026-04-07 15:16:34 +08:00
2026-03-26 10:49:41 +08:00
service = null;
return false;
}
public T Require<T>() where T : class, IService
{
if (TryGet(out T service)) return service;
2026-04-07 15:16:34 +08:00
Log.Error($"Scope {Name} does not contain service {typeof(T).FullName}.");
return default;
2026-03-26 10:49:41 +08:00
}
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;
2026-04-20 13:46:44 +08:00
if (service is not IServiceLifecycle lifecycle)
throw new InvalidOperationException($"Service {service.GetType().FullName} must implement {nameof(IServiceLifecycle)}.");
2026-03-26 10:49:41 +08:00
RemoveFromLifecycleLists(service);
2026-03-26 13:51:09 +08:00
RemoveContractBindings(service); // skip _registrationOrder.Remove — we clear below
2026-04-20 13:46:44 +08:00
lifecycle.Destroy();
2026-03-26 10:49:41 +08:00
}
2026-03-26 13:51:09 +08:00
_registrationOrder.Clear();
2026-03-26 10:49:41 +08:00
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)
{
2026-04-07 15:16:34 +08:00
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;
}
2026-03-26 10:49:41 +08:00
}
private void RemoveFromLifecycleLists(IService service)
{
2026-04-07 15:16:34 +08:00
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;
2026-03-26 10:49:41 +08:00
}
private void RemoveBindings(IService service)
{
if (_contractsByService.TryGetValue(service, out var contracts))
{
for (var i = 0; i < contracts.Count; i++)
_servicesByContract.Remove(contracts[i]);
}
2026-04-07 15:16:34 +08:00
2026-03-26 10:49:41 +08:00
_contractsByService.Remove(service);
_registrationOrder.Remove(service);
}
2026-03-26 13:51:09 +08:00
// 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]);
}
2026-04-07 15:16:34 +08:00
2026-03-26 13:51:09 +08:00
_contractsByService.Remove(service);
}
2026-03-26 10:49:41 +08:00
private IServiceTickable[] GetTickSnapshot()
{
if (_tickablesDirty)
{
_tickables.Sort(CompareByOrder);
_tickableSnapshot = _tickables.Count > 0 ? _tickables.ToArray() : Array.Empty<IServiceTickable>();
_tickablesDirty = false;
}
2026-04-07 15:16:34 +08:00
2026-03-26 10:49:41 +08:00
return _tickableSnapshot;
}
private IServiceLateTickable[] GetLateTickSnapshot()
{
if (_lateTickablesDirty)
{
_lateTickables.Sort(CompareByOrder);
_lateTickableSnapshot = _lateTickables.Count > 0 ? _lateTickables.ToArray() : Array.Empty<IServiceLateTickable>();
_lateTickablesDirty = false;
}
2026-04-07 15:16:34 +08:00
2026-03-26 10:49:41 +08:00
return _lateTickableSnapshot;
}
private IServiceFixedTickable[] GetFixedTickSnapshot()
{
if (_fixedTickablesDirty)
{
_fixedTickables.Sort(CompareByOrder);
_fixedTickableSnapshot = _fixedTickables.Count > 0 ? _fixedTickables.ToArray() : Array.Empty<IServiceFixedTickable>();
_fixedTickablesDirty = false;
}
2026-04-07 15:16:34 +08:00
2026-03-26 10:49:41 +08:00
return _fixedTickableSnapshot;
}
private IServiceGizmoDrawable[] GetGizmoSnapshot()
{
if (_gizmoDrawablesDirty)
{
_gizmoDrawables.Sort(CompareByOrder);
_gizmoSnapshot = _gizmoDrawables.Count > 0 ? _gizmoDrawables.ToArray() : Array.Empty<IServiceGizmoDrawable>();
_gizmoDrawablesDirty = false;
}
2026-04-07 15:16:34 +08:00
2026-03-26 10:49:41 +08:00
return _gizmoSnapshot;
}
private static int CompareByOrder<T>(T a, T b)
{
2026-04-07 15:16:34 +08:00
var left = a is IServiceOrder oa ? oa.Order : 0;
2026-03-26 10:49:41 +08:00
var right = b is IServiceOrder ob ? ob.Order : 0;
return left.CompareTo(right);
}
}
}