308 lines
11 KiB
C#
308 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace AlicizaX
|
|
{
|
|
internal sealed class ServiceScope : IDisposable, IServiceRegistry
|
|
{
|
|
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;
|
|
}
|
|
|
|
internal ServiceWorld World { get; }
|
|
|
|
public string Name { get; }
|
|
|
|
internal int Order { get; }
|
|
|
|
internal bool IsDisposed { get; private set; }
|
|
|
|
public T Register<T>(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<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;
|
|
|
|
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<T>(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<T>() 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<IServiceTickable>();
|
|
_tickablesDirty = false;
|
|
}
|
|
|
|
return _tickableSnapshot;
|
|
}
|
|
|
|
private IServiceLateTickable[] GetLateTickSnapshot()
|
|
{
|
|
if (_lateTickablesDirty)
|
|
{
|
|
_lateTickables.Sort(CompareByOrder);
|
|
_lateTickableSnapshot = _lateTickables.Count > 0 ? _lateTickables.ToArray() : Array.Empty<IServiceLateTickable>();
|
|
_lateTickablesDirty = false;
|
|
}
|
|
|
|
return _lateTickableSnapshot;
|
|
}
|
|
|
|
private IServiceFixedTickable[] GetFixedTickSnapshot()
|
|
{
|
|
if (_fixedTickablesDirty)
|
|
{
|
|
_fixedTickables.Sort(CompareByOrder);
|
|
_fixedTickableSnapshot = _fixedTickables.Count > 0 ? _fixedTickables.ToArray() : Array.Empty<IServiceFixedTickable>();
|
|
_fixedTickablesDirty = false;
|
|
}
|
|
|
|
return _fixedTickableSnapshot;
|
|
}
|
|
|
|
private IServiceGizmoDrawable[] GetGizmoSnapshot()
|
|
{
|
|
if (_gizmoDrawablesDirty)
|
|
{
|
|
_gizmoDrawables.Sort(CompareByOrder);
|
|
_gizmoSnapshot = _gizmoDrawables.Count > 0 ? _gizmoDrawables.ToArray() : Array.Empty<IServiceGizmoDrawable>();
|
|
_gizmoDrawablesDirty = false;
|
|
}
|
|
|
|
return _gizmoSnapshot;
|
|
}
|
|
|
|
private static int CompareByOrder<T>(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);
|
|
}
|
|
}
|
|
}
|