mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-15 14:52:35 +02:00
* [Dependency] source generator No more reflection, no more codegen at runtime Also various changes to Roslyn helpers to make this easier to write. Requires all types with dependencies to be partial and not have readonly dependency fields. An analyzer enforces this at warning level, the previous injection strategies have remained in the code *for now* as a fallback. No fallback is available for [field: Dependency] properties, due to a Roslyn bug. Code Fixes exist. We love Roslyn * Apply dependencies generator changes to all code * Release notes * Preprocessor got hands * Handle nullable dependencies These are bad but gotta deal with it. * Apply suggestions from code review Co-authored-by: Moony <moony@hellomouse.net> * Fine, let's not use collection expressions --------- Co-authored-by: Moony <moony@hellomouse.net>
460 lines
16 KiB
C#
460 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading.Tasks;
|
|
using Prometheus;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.IoC.Exceptions;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Profiling;
|
|
using Robust.Shared.Reflection;
|
|
using Robust.Shared.Utility;
|
|
using Robust.Shared.ViewVariables;
|
|
using Dependency = Robust.Shared.IoC.DependencyAttribute;
|
|
using Stopwatch = System.Diagnostics.Stopwatch;
|
|
#if EXCEPTION_TOLERANCE
|
|
using Robust.Shared.Exceptions;
|
|
#endif
|
|
|
|
|
|
namespace Robust.Shared.GameObjects
|
|
{
|
|
public sealed partial class EntitySystemManager : IEntitySystemManager, IPostInjectInit
|
|
{
|
|
[Dependency] private IReflectionManager _reflectionManager = default!;
|
|
[Dependency] private IEntityManager _entityManager = default!;
|
|
[Dependency] private ProfManager _profManager = default!;
|
|
[Dependency] private IDependencyCollection _dependencyCollection = default!;
|
|
[Dependency] private ILogManager _logManager = default!;
|
|
|
|
#if EXCEPTION_TOLERANCE
|
|
[Dependency] private IRuntimeLog _runtimeLog = default!;
|
|
#endif
|
|
|
|
private ISawmill _sawmill = default!;
|
|
|
|
internal DependencyCollection SystemDependencyCollection = default!;
|
|
|
|
public IDependencyCollection DependencyCollection
|
|
{
|
|
get
|
|
{
|
|
if (_initialized)
|
|
return SystemDependencyCollection;
|
|
|
|
throw new InvalidOperationException($"{nameof(EntitySystemManager)} has not been initialized.");
|
|
}
|
|
}
|
|
|
|
private readonly List<Type> _systemTypes = new();
|
|
|
|
private static readonly Histogram _tickUsageHistogram = Metrics.CreateHistogram("robust_entity_systems_update_usage",
|
|
"Amount of time spent processing each entity system", new HistogramConfiguration
|
|
{
|
|
LabelNames = new[] {"system"},
|
|
Buckets = Histogram.ExponentialBuckets(0.000_001, 1.5, 25)
|
|
});
|
|
|
|
[ViewVariables]
|
|
private readonly List<Type> _extraLoadedTypes = new();
|
|
|
|
private readonly Stopwatch _stopwatch = new();
|
|
|
|
private bool _initialized;
|
|
|
|
[ViewVariables] private UpdateReg[] _updateOrder = Array.Empty<UpdateReg>();
|
|
[ViewVariables] private IEntitySystem[] _frameUpdateOrder = Array.Empty<IEntitySystem>();
|
|
|
|
public bool MetricsEnabled { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public event EventHandler<SystemChangedArgs>? SystemLoaded;
|
|
|
|
/// <inheritdoc />
|
|
public event EventHandler<SystemChangedArgs>? SystemUnloaded;
|
|
|
|
/// <exception cref="UnregisteredTypeException">Thrown if the provided type is not registered.</exception>
|
|
public T GetEntitySystem<T>()
|
|
where T : IEntitySystem
|
|
{
|
|
return SystemDependencyCollection.Resolve<T>();
|
|
}
|
|
|
|
public T? GetEntitySystemOrNull<T>() where T : IEntitySystem
|
|
{
|
|
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
|
if (SystemDependencyCollection == null)
|
|
return default;
|
|
|
|
SystemDependencyCollection.TryResolveType<T>(out var system);
|
|
return system;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Resolve<T>([NotNull] ref T? instance)
|
|
where T : IEntitySystem
|
|
{
|
|
SystemDependencyCollection.Resolve(ref instance);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Resolve<T1, T2>([NotNull] ref T1? instance1, [NotNull] ref T2? instance2)
|
|
where T1 : IEntitySystem
|
|
where T2 : IEntitySystem
|
|
{
|
|
SystemDependencyCollection.Resolve(ref instance1, ref instance2);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Resolve<T1, T2, T3>([NotNull] ref T1? instance1, [NotNull] ref T2? instance2, [NotNull] ref T3? instance3)
|
|
where T1 : IEntitySystem
|
|
where T2 : IEntitySystem
|
|
where T3 : IEntitySystem
|
|
{
|
|
SystemDependencyCollection.Resolve(ref instance1, ref instance2, ref instance3);
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Resolve<T1, T2, T3, T4>([NotNull] ref T1? instance1, [NotNull] ref T2? instance2, [NotNull] ref T3? instance3, [NotNull] ref T4? instance4)
|
|
where T1 : IEntitySystem
|
|
where T2 : IEntitySystem
|
|
where T3 : IEntitySystem
|
|
where T4 : IEntitySystem
|
|
{
|
|
SystemDependencyCollection.Resolve(ref instance1, ref instance2, ref instance3, ref instance4);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool TryGetEntitySystem<T>([NotNullWhen(true)] out T? entitySystem)
|
|
where T : IEntitySystem
|
|
{
|
|
entitySystem = default;
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
return SystemDependencyCollection?.TryResolveType(out entitySystem) ?? false;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Initialize(bool discover = true)
|
|
{
|
|
// Tempted to make this an assert
|
|
// However, EntityManager calls this directly so we'd need to remove that and manually call it.
|
|
if (_initialized) return;
|
|
|
|
var excludedTypes = new HashSet<Type>();
|
|
|
|
SystemDependencyCollection = new(_dependencyCollection);
|
|
var subTypes = new Dictionary<Type, Type>();
|
|
_systemTypes.Clear();
|
|
IEnumerable<Type> systems;
|
|
|
|
if (discover)
|
|
{
|
|
systems = _reflectionManager.GetAllChildren<IEntitySystem>().Concat(_extraLoadedTypes);
|
|
}
|
|
else
|
|
{
|
|
systems = _extraLoadedTypes;
|
|
}
|
|
|
|
foreach (var type in systems)
|
|
{
|
|
_sawmill.Debug("Initializing entity system {0}", type);
|
|
|
|
SystemDependencyCollection.Register(type);
|
|
_systemTypes.Add(type);
|
|
|
|
excludedTypes.Add(type);
|
|
if (subTypes.ContainsKey(type))
|
|
{
|
|
subTypes.Remove(type);
|
|
}
|
|
|
|
// also register systems under their supertypes, so they can be retrieved by their supertype.
|
|
// We don't do this if there are multiple subtype systems of that supertype though, otherwise
|
|
// it wouldn't be clear which instance to return when asking for the supertype
|
|
foreach (var baseType in GetBaseTypes(type))
|
|
{
|
|
// already known that there are multiple subtype systems of this type,
|
|
// so don't register under the supertype because it would be unclear
|
|
// which instance to return if we retrieved it by the supertype
|
|
if (excludedTypes.Contains(baseType)) continue;
|
|
|
|
if (subTypes.ContainsKey(baseType))
|
|
{
|
|
subTypes.Remove(baseType);
|
|
excludedTypes.Add(baseType);
|
|
}
|
|
else
|
|
{
|
|
subTypes.Add(baseType, type);
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (var (baseType, type) in subTypes)
|
|
{
|
|
SystemDependencyCollection.Register(baseType, type, overwrite: true);
|
|
_systemTypes.Remove(baseType);
|
|
}
|
|
|
|
var queryMethod = typeof(EntityManager).GetMethod(nameof(EntityManager.GetEntityQuery), 1, [])!;
|
|
SystemDependencyCollection.RegisterBaseGenericLazy(
|
|
typeof(EntityQuery<>),
|
|
(queryType, dep) => queryMethod
|
|
.MakeGenericMethod(queryType.GetGenericArguments()[0])
|
|
.Invoke(dep.Resolve<IEntityManager>(), null)!
|
|
);
|
|
|
|
SystemDependencyCollection.BuildGraph();
|
|
|
|
foreach (var systemType in _systemTypes)
|
|
{
|
|
var system = (IEntitySystem)SystemDependencyCollection.ResolveType(systemType);
|
|
system.Initialize();
|
|
SystemLoaded?.Invoke(this, new SystemChangedArgs(system));
|
|
}
|
|
|
|
// Create update order for entity systems.
|
|
var (fUpdate, update) = CalculateUpdateOrder(_systemTypes, subTypes, SystemDependencyCollection);
|
|
|
|
_frameUpdateOrder = fUpdate.ToArray();
|
|
_updateOrder = update
|
|
.Select(s => new UpdateReg
|
|
{
|
|
System = s,
|
|
Monitor = _tickUsageHistogram.WithLabels(s.GetType().Name)
|
|
})
|
|
.ToArray();
|
|
|
|
_initialized = true;
|
|
}
|
|
|
|
private static (IEnumerable<IEntitySystem> frameUpd, IEnumerable<IEntitySystem> upd)
|
|
CalculateUpdateOrder(
|
|
List<Type> systemTypes,
|
|
Dictionary<Type, Type> subTypes,
|
|
DependencyCollection dependencyCollection)
|
|
{
|
|
var allNodes = new List<TopologicalSort.GraphNode<IEntitySystem>>();
|
|
var typeToNode = new Dictionary<Type, TopologicalSort.GraphNode<IEntitySystem>>();
|
|
|
|
foreach (var systemType in systemTypes)
|
|
{
|
|
var node = new TopologicalSort.GraphNode<IEntitySystem>((IEntitySystem) dependencyCollection.ResolveType(systemType));
|
|
|
|
typeToNode.Add(systemType, node);
|
|
allNodes.Add(node);
|
|
}
|
|
|
|
foreach (var (type, system) in subTypes)
|
|
{
|
|
var node = typeToNode[system];
|
|
typeToNode[type] = node;
|
|
}
|
|
|
|
foreach (var node in typeToNode.Values)
|
|
{
|
|
foreach (var after in node.Value.UpdatesAfter)
|
|
{
|
|
var system = typeToNode[after];
|
|
|
|
system.Dependant.Add(node);
|
|
}
|
|
|
|
foreach (var before in node.Value.UpdatesBefore)
|
|
{
|
|
var system = typeToNode[before];
|
|
|
|
node.Dependant.Add(system);
|
|
}
|
|
}
|
|
|
|
var order = TopologicalSort.Sort(allNodes).ToArray();
|
|
var frameUpdate = order.Where(p => NeedsFrameUpdate(p.GetType()));
|
|
var update = order.Where(p => NeedsUpdate(p.GetType()));
|
|
|
|
return (frameUpdate, update);
|
|
}
|
|
|
|
private static IEnumerable<Type> GetBaseTypes(Type type) {
|
|
if(type.BaseType == null) return type.GetInterfaces();
|
|
|
|
return Enumerable.Repeat(type.BaseType, 1)
|
|
.Concat(type.GetInterfaces())
|
|
.Concat(type.GetInterfaces().SelectMany<Type, Type>(GetBaseTypes))
|
|
.Concat(GetBaseTypes(type.BaseType));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Shutdown()
|
|
{
|
|
// System.Values is modified by RemoveSystem
|
|
foreach (var systemType in _systemTypes)
|
|
{
|
|
if(SystemDependencyCollection == null) continue;
|
|
var system = (IEntitySystem)SystemDependencyCollection.ResolveType(systemType);
|
|
SystemUnloaded?.Invoke(this, new SystemChangedArgs(system));
|
|
system.Shutdown();
|
|
_entityManager.EventBus.UnsubscribeEvents(system);
|
|
}
|
|
|
|
Clear();
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_extraLoadedTypes.Clear();
|
|
_systemTypes.Clear();
|
|
_updateOrder = Array.Empty<UpdateReg>();
|
|
_frameUpdateOrder = Array.Empty<IEntitySystem>();
|
|
_initialized = false;
|
|
SystemDependencyCollection?.Clear();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void TickUpdate(float frameTime, bool noPredictions)
|
|
{
|
|
foreach (var updReg in _updateOrder)
|
|
{
|
|
if (noPredictions && !updReg.System.UpdatesOutsidePrediction)
|
|
continue;
|
|
|
|
if (MetricsEnabled)
|
|
{
|
|
_stopwatch.Restart();
|
|
}
|
|
#if EXCEPTION_TOLERANCE
|
|
try
|
|
{
|
|
#endif
|
|
using (_profManager.Value(updReg.System.GetType().Name))
|
|
{
|
|
updReg.System.Update(frameTime);
|
|
}
|
|
#if EXCEPTION_TOLERANCE
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_runtimeLog.LogException(e, "entsys");
|
|
}
|
|
#endif
|
|
|
|
if (MetricsEnabled)
|
|
{
|
|
updReg.Monitor.Observe(_stopwatch.Elapsed.TotalSeconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void FrameUpdate(float frameTime)
|
|
{
|
|
foreach (var system in _frameUpdateOrder)
|
|
{
|
|
#if EXCEPTION_TOLERANCE
|
|
try
|
|
{
|
|
#endif
|
|
using (_profManager.Value(system.GetType().Name))
|
|
{
|
|
system.FrameUpdate(frameTime);
|
|
}
|
|
#if EXCEPTION_TOLERANCE
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_runtimeLog.LogException(e, "entsys");
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public void LoadExtraSystemType<T>() where T : IEntitySystem, new()
|
|
{
|
|
if (_initialized)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Cannot use LoadExtraSystemType when the entity system manager is initialized.");
|
|
}
|
|
|
|
_extraLoadedTypes.Add(typeof(T));
|
|
}
|
|
|
|
public IEnumerable<Type> GetEntitySystemTypes()
|
|
{
|
|
return _systemTypes;
|
|
}
|
|
|
|
public bool TryGetEntitySystem(Type sysType, [NotNullWhen(true)] out object? system)
|
|
{
|
|
system = null;
|
|
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
|
return SystemDependencyCollection?.TryResolveType(sysType, out system) ?? false;
|
|
}
|
|
|
|
public object GetEntitySystem(Type sysType)
|
|
{
|
|
return SystemDependencyCollection.ResolveType(sysType);
|
|
}
|
|
|
|
private static bool NeedsUpdate(Type type)
|
|
{
|
|
if (!typeof(EntitySystem).IsAssignableFrom(type))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var mUpdate = type.GetMethod(nameof(EntitySystem.Update), new[] {typeof(float)});
|
|
|
|
DebugTools.AssertNotNull(mUpdate);
|
|
|
|
return mUpdate!.DeclaringType != typeof(EntitySystem);
|
|
}
|
|
|
|
private static bool NeedsFrameUpdate(Type type)
|
|
{
|
|
if (!typeof(EntitySystem).IsAssignableFrom(type))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var mFrameUpdate = type.GetMethod(nameof(EntitySystem.FrameUpdate), new[] {typeof(float)});
|
|
|
|
DebugTools.AssertNotNull(mFrameUpdate);
|
|
|
|
return mFrameUpdate!.DeclaringType != typeof(EntitySystem);
|
|
}
|
|
|
|
internal IEnumerable<Type> FrameUpdateOrder => _frameUpdateOrder.Select(c => c.GetType());
|
|
internal IEnumerable<Type> TickUpdateOrder => _updateOrder.Select(c => c.System.GetType());
|
|
|
|
private struct UpdateReg
|
|
{
|
|
[ViewVariables] public IEntitySystem System;
|
|
[ViewVariables] public Histogram.Child Monitor;
|
|
|
|
public override string? ToString()
|
|
{
|
|
return System.ToString();
|
|
}
|
|
}
|
|
|
|
void IPostInjectInit.PostInject()
|
|
{
|
|
_sawmill = _logManager.GetSawmill("go.sys");
|
|
}
|
|
}
|
|
|
|
public sealed class SystemChangedArgs : EventArgs
|
|
{
|
|
public IEntitySystem System { get; }
|
|
|
|
public SystemChangedArgs(IEntitySystem system)
|
|
{
|
|
System = system;
|
|
}
|
|
}
|
|
}
|