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

533 lines
20 KiB
C#

using System;
using System.Collections.Generic;
using Cysharp.Text;
namespace AlicizaX
{
internal sealed class ServiceScope : IDisposable, IServiceRegistry
{
private const int MissingIndex = -1;
private readonly Dictionary<RuntimeTypeHandle, IService> _servicesByContract = new Dictionary<RuntimeTypeHandle, IService>();
private readonly Dictionary<IService, ServiceEntry> _entriesByService = new Dictionary<IService, ServiceEntry>(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 readonly List<PendingChange> _pendingChanges = new List<PendingChange>();
private bool _tickablesDirty;
private bool _lateTickablesDirty;
private bool _fixedTickablesDirty;
private bool _gizmoDrawablesDirty;
private bool _isIterating;
internal ServiceScope(ServiceWorld world, ServiceScopeKind kind, string name, int order, int creationIndex)
{
World = world ?? throw new ArgumentNullException(nameof(world));
Kind = kind;
Name = string.IsNullOrWhiteSpace(name) ? throw new ArgumentException("Scope name cannot be empty.", nameof(name)) : name;
Order = order;
CreationIndex = creationIndex;
}
internal ServiceWorld World { get; }
internal ServiceScopeKind Kind { get; }
public string Name { get; }
internal int Order { get; }
internal int CreationIndex { get; }
internal bool IsDisposed { get; private set; }
public T RegisterSelf<T>(T service) where T : class, IService
=> RegisterInternal(service, ServiceContractUtility.Create(service != null ? service.GetType() : typeof(T)));
public TContract Register<TContract>(IService service)
where TContract : class, IService
=> RegisterInternal(service, ServiceContractUtility.Create(service != null ? service.GetType() : typeof(TContract), typeof(TContract))) as TContract;
public bool Unregister<T>() where T : class, IService
{
if (!_servicesByContract.TryGetValue(typeof(T).TypeHandle, out var service))
return false;
return Unregister(service);
}
public bool Unregister(IService service)
{
if (service == null || !_entriesByService.TryGetValue(service, out var entry))
return false;
if (service is not IServiceLifecycle lifecycle)
throw new InvalidOperationException(ZString.Format("Service {0} must implement {1}.", service.GetType().FullName, nameof(IServiceLifecycle)));
if (_isIterating)
{
if (entry.PendingRemove) return true;
entry.PendingRemove = true;
_entriesByService[service] = entry;
_pendingChanges.Add(PendingChange.Remove(service));
return true;
}
RemoveEntry(service, entry, true);
lifecycle.Destroy();
return true;
}
public bool TryGet<T>(out T service) where T : class, IService
{
if (_servicesByContract.TryGetValue(typeof(T).TypeHandle, out var raw))
{
service = raw as T;
return service != null;
}
service = null;
return false;
}
internal bool TryGet(Type contract, out IService service)
=> _servicesByContract.TryGetValue(contract.TypeHandle, out service);
public T Require<T>() where T : class, IService
{
if (TryGet(out T service)) return service;
throw new InvalidOperationException(ZString.Format("Scope {0} does not contain service {1}.", Name, typeof(T).FullName));
}
public bool HasContract(Type contractType)
=> _servicesByContract.ContainsKey(contractType.TypeHandle);
internal void Tick(float deltaTime)
{
SortTickablesIfDirty();
_isIterating = true;
for (var i = 0; i < _tickables.Count; i++) _tickables[i].Tick(deltaTime);
_isIterating = false;
FlushPendingChanges();
}
internal void LateTick(float deltaTime)
{
SortLateTickablesIfDirty();
_isIterating = true;
for (var i = 0; i < _lateTickables.Count; i++) _lateTickables[i].LateTick(deltaTime);
_isIterating = false;
FlushPendingChanges();
}
internal void FixedTick(float fixedDeltaTime)
{
SortFixedTickablesIfDirty();
_isIterating = true;
for (var i = 0; i < _fixedTickables.Count; i++) _fixedTickables[i].FixedTick(fixedDeltaTime);
_isIterating = false;
FlushPendingChanges();
}
internal void DrawGizmos()
{
SortGizmoDrawablesIfDirty();
_isIterating = true;
for (var i = 0; i < _gizmoDrawables.Count; i++) _gizmoDrawables[i].DrawGizmos();
_isIterating = false;
FlushPendingChanges();
}
public void Dispose()
{
if (IsDisposed) return;
_isIterating = false;
_pendingChanges.Clear();
for (var i = _registrationOrder.Count - 1; i >= 0; i--)
{
var service = _registrationOrder[i];
if (service == null || !_entriesByService.TryGetValue(service, out var entry)) continue;
if (service is not IServiceLifecycle lifecycle)
throw new InvalidOperationException(ZString.Format("Service {0} must implement {1}.", service.GetType().FullName, nameof(IServiceLifecycle)));
RemoveEntry(service, entry, false);
lifecycle.Destroy();
}
_registrationOrder.Clear();
_tickables.Clear();
_lateTickables.Clear();
_fixedTickables.Clear();
_gizmoDrawables.Clear();
IsDisposed = true;
}
private T RegisterInternal<T>(T service, ServiceContracts contracts) where T : class, IService
{
EnsureNotDisposed();
if (service == null)
throw new ArgumentNullException(nameof(service));
if (service is not IServiceLifecycle lifecycle)
throw new InvalidOperationException(ZString.Format("Service {0} must implement {1}.", service.GetType().FullName, nameof(IServiceLifecycle)));
ValidateService(service);
if (_entriesByService.ContainsKey(service))
throw new InvalidOperationException(ZString.Format("Service {0} is already registered in scope {1}.", service.GetType().FullName, Name));
if (_isIterating)
{
ValidateContracts(contracts);
_pendingChanges.Add(PendingChange.Add(service, contracts));
return service;
}
ValidateContracts(contracts);
lifecycle.Initialize(new ServiceContext(World, this));
AddEntry(service, contracts);
return service;
}
private void ValidateContracts(ServiceContracts contracts)
{
for (var i = 0; i < contracts.Count; i++)
{
var contract = contracts[i];
if (_servicesByContract.TryGetValue(contract.TypeHandle, out var existing))
{
throw new InvalidOperationException(
ZString.Format("Scope {0} already contains contract {1} bound to {2}.", Name, contract.FullName, existing.GetType().FullName));
}
}
for (var i = 0; i < _pendingChanges.Count; i++)
{
var change = _pendingChanges[i];
if (!change.IsAdd) continue;
for (var contractIndex = 0; contractIndex < contracts.Count; contractIndex++)
{
var contract = contracts[contractIndex];
for (var pendingContractIndex = 0; pendingContractIndex < change.Contracts.Count; pendingContractIndex++)
{
if (!change.Contracts[pendingContractIndex].TypeHandle.Equals(contract.TypeHandle)) continue;
throw new InvalidOperationException(
ZString.Format("Scope {0} already contains pending contract {1}.", Name, contract.FullName));
}
}
}
}
private void AddEntry(IService service, ServiceContracts contracts)
{
var entry = new ServiceEntry(contracts, _registrationOrder.Count);
_registrationOrder.Add(service);
for (var i = 0; i < contracts.Count; i++)
{
var contract = contracts[i];
_servicesByContract.Add(contract.TypeHandle, service);
World.AddContract(this, contract, service);
}
AddToLifecycleLists(service, ref entry);
_entriesByService.Add(service, entry);
}
private void RemoveEntry(IService service, ServiceEntry entry, bool removeRegistrationOrder)
{
_entriesByService.Remove(service);
RemoveFromLifecycleLists(service, entry);
for (var i = 0; i < entry.Contracts.Count; i++)
{
var contract = entry.Contracts[i];
_servicesByContract.Remove(contract.TypeHandle);
World.RemoveContract(this, contract, service);
}
if (!removeRegistrationOrder) return;
var lastIndex = _registrationOrder.Count - 1;
var removedIndex = entry.RegistrationIndex;
var lastService = _registrationOrder[lastIndex];
_registrationOrder[removedIndex] = lastService;
_registrationOrder.RemoveAt(lastIndex);
if (removedIndex != lastIndex && lastService != null && _entriesByService.TryGetValue(lastService, out var lastEntry))
{
lastEntry.RegistrationIndex = removedIndex;
_entriesByService[lastService] = lastEntry;
}
}
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(
ZString.Format("Mono service {0} cannot implement tick lifecycle interfaces.", service.GetType().FullName));
}
}
private void AddToLifecycleLists(IService service, ref ServiceEntry entry)
{
if (service is IServiceTickable tickable)
{
entry.TickIndex = _tickables.Count;
_tickables.Add(tickable);
_tickablesDirty = true;
}
if (service is IServiceLateTickable late)
{
entry.LateTickIndex = _lateTickables.Count;
_lateTickables.Add(late);
_lateTickablesDirty = true;
}
if (service is IServiceFixedTickable fixed_)
{
entry.FixedTickIndex = _fixedTickables.Count;
_fixedTickables.Add(fixed_);
_fixedTickablesDirty = true;
}
if (service is IServiceGizmoDrawable gizmo)
{
entry.GizmoIndex = _gizmoDrawables.Count;
_gizmoDrawables.Add(gizmo);
_gizmoDrawablesDirty = true;
}
}
private void RemoveFromLifecycleLists(IService service, ServiceEntry entry)
{
if (entry.TickIndex != MissingIndex) RemoveTickableAt(entry.TickIndex);
if (entry.LateTickIndex != MissingIndex) RemoveLateTickableAt(entry.LateTickIndex);
if (entry.FixedTickIndex != MissingIndex) RemoveFixedTickableAt(entry.FixedTickIndex);
if (entry.GizmoIndex != MissingIndex) RemoveGizmoDrawableAt(entry.GizmoIndex);
}
private void RemoveTickableAt(int index)
{
var lastIndex = _tickables.Count - 1;
var moved = _tickables[lastIndex];
_tickables[index] = moved;
_tickables.RemoveAt(lastIndex);
if (index != lastIndex) UpdateTickIndex(moved, index);
_tickablesDirty = true;
}
private void RemoveLateTickableAt(int index)
{
var lastIndex = _lateTickables.Count - 1;
var moved = _lateTickables[lastIndex];
_lateTickables[index] = moved;
_lateTickables.RemoveAt(lastIndex);
if (index != lastIndex) UpdateLateTickIndex(moved, index);
_lateTickablesDirty = true;
}
private void RemoveFixedTickableAt(int index)
{
var lastIndex = _fixedTickables.Count - 1;
var moved = _fixedTickables[lastIndex];
_fixedTickables[index] = moved;
_fixedTickables.RemoveAt(lastIndex);
if (index != lastIndex) UpdateFixedTickIndex(moved, index);
_fixedTickablesDirty = true;
}
private void RemoveGizmoDrawableAt(int index)
{
var lastIndex = _gizmoDrawables.Count - 1;
var moved = _gizmoDrawables[lastIndex];
_gizmoDrawables[index] = moved;
_gizmoDrawables.RemoveAt(lastIndex);
if (index != lastIndex) UpdateGizmoIndex(moved, index);
_gizmoDrawablesDirty = true;
}
private void UpdateTickIndex(IServiceTickable service, int index)
{
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
entry.TickIndex = index;
_entriesByService[(IService)service] = entry;
}
private void UpdateLateTickIndex(IServiceLateTickable service, int index)
{
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
entry.LateTickIndex = index;
_entriesByService[(IService)service] = entry;
}
private void UpdateFixedTickIndex(IServiceFixedTickable service, int index)
{
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
entry.FixedTickIndex = index;
_entriesByService[(IService)service] = entry;
}
private void UpdateGizmoIndex(IServiceGizmoDrawable service, int index)
{
if (!_entriesByService.TryGetValue((IService)service, out var entry)) return;
entry.GizmoIndex = index;
_entriesByService[(IService)service] = entry;
}
private void FlushPendingChanges()
{
if (_pendingChanges.Count == 0) return;
for (var i = 0; i < _pendingChanges.Count; i++)
{
var change = _pendingChanges[i];
if (change.IsAdd)
{
if (!_entriesByService.ContainsKey(change.Service))
{
((IServiceLifecycle)change.Service).Initialize(new ServiceContext(World, this));
AddEntry(change.Service, change.Contracts);
}
continue;
}
if (_entriesByService.TryGetValue(change.Service, out var entry))
{
RemoveEntry(change.Service, entry, true);
((IServiceLifecycle)change.Service).Destroy();
}
}
_pendingChanges.Clear();
}
private void SortTickablesIfDirty()
{
if (_tickablesDirty)
{
_tickables.Sort(CompareByOrder);
RebuildTickIndices();
_tickablesDirty = false;
}
}
private void SortLateTickablesIfDirty()
{
if (_lateTickablesDirty)
{
_lateTickables.Sort(CompareByOrder);
RebuildLateTickIndices();
_lateTickablesDirty = false;
}
}
private void SortFixedTickablesIfDirty()
{
if (_fixedTickablesDirty)
{
_fixedTickables.Sort(CompareByOrder);
RebuildFixedTickIndices();
_fixedTickablesDirty = false;
}
}
private void SortGizmoDrawablesIfDirty()
{
if (_gizmoDrawablesDirty)
{
_gizmoDrawables.Sort(CompareByOrder);
RebuildGizmoIndices();
_gizmoDrawablesDirty = false;
}
}
private void RebuildTickIndices()
{
for (var i = 0; i < _tickables.Count; i++) UpdateTickIndex(_tickables[i], i);
}
private void RebuildLateTickIndices()
{
for (var i = 0; i < _lateTickables.Count; i++) UpdateLateTickIndex(_lateTickables[i], i);
}
private void RebuildFixedTickIndices()
{
for (var i = 0; i < _fixedTickables.Count; i++) UpdateFixedTickIndex(_fixedTickables[i], i);
}
private void RebuildGizmoIndices()
{
for (var i = 0; i < _gizmoDrawables.Count; i++) UpdateGizmoIndex(_gizmoDrawables[i], i);
}
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);
}
private struct ServiceEntry
{
public readonly ServiceContracts Contracts;
public int RegistrationIndex;
public int TickIndex;
public int LateTickIndex;
public int FixedTickIndex;
public int GizmoIndex;
public bool PendingRemove;
public ServiceEntry(ServiceContracts contracts, int registrationIndex)
{
Contracts = contracts;
RegistrationIndex = registrationIndex;
TickIndex = MissingIndex;
LateTickIndex = MissingIndex;
FixedTickIndex = MissingIndex;
GizmoIndex = MissingIndex;
PendingRemove = false;
}
}
private readonly struct PendingChange
{
public readonly bool IsAdd;
public readonly IService Service;
public readonly ServiceContracts Contracts;
private PendingChange(bool isAdd, IService service, ServiceContracts contracts)
{
IsAdd = isAdd;
Service = service;
Contracts = contracts;
}
public static PendingChange Add(IService service, ServiceContracts contracts)
=> new PendingChange(true, service, contracts);
public static PendingChange Remove(IService service)
=> new PendingChange(false, service, default);
}
}
}