mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Data definitions created individual read/write methods for every single field that can be serialized. This was extremely inefficient and likely caused lots of overhead. These methods are necessary because, in some cases, we can't directly use expression trees to write fields. But... we can still do it most of the time! So now for most data fields we can avoid a DynamicMethod entirely.
357 lines
13 KiB
C#
357 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Runtime.InteropServices;
|
|
using Robust.Client.State;
|
|
using Robust.Client.UserInterface.Controllers;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
// ReSharper disable once CheckNamespace
|
|
namespace Robust.Client.UserInterface;
|
|
|
|
internal partial class UserInterfaceManager
|
|
{
|
|
/// <summary>
|
|
/// All registered <see cref="UIController"/> instances indexed by type
|
|
/// </summary>
|
|
private readonly Dictionary<Type, UIController> _uiControllers = new();
|
|
|
|
/// <summary>
|
|
/// Dependency collection holding UI controllers and IoC services
|
|
/// </summary>
|
|
private DependencyCollection _dependencies = default!;
|
|
|
|
/// <summary>
|
|
/// Implementations of <see cref="IOnStateEntered{T}"/> to invoke when a state is entered
|
|
/// State Type -> (UIController, Caller)
|
|
/// </summary>
|
|
private readonly Dictionary<Type, Dictionary<UIController, StateChangedCaller>> _onStateEnteredDelegates = new();
|
|
|
|
/// <summary>
|
|
/// Implementations of <see cref="IOnStateExited{T}"/> to invoke when a state is exited
|
|
/// State Type -> (UIController, Caller)
|
|
/// </summary>
|
|
private readonly Dictionary<Type, Dictionary<UIController, StateChangedCaller>> _onStateExitedDelegates = new();
|
|
|
|
/// <summary>
|
|
/// Implementations of <see cref="IOnSystemLoaded{T}"/> to invoke when an entity system is loaded
|
|
/// Entity System Type -> (UIController, Caller)
|
|
/// </summary>
|
|
private readonly Dictionary<Type, Dictionary<UIController, SystemChangedCaller>> _onSystemLoadedDelegates = new();
|
|
|
|
/// <summary>
|
|
/// Implementations of <see cref="IOnSystemUnloaded{T}"/> to invoke when an entity system is unloaded
|
|
/// Entity System Type -> (UIController, Caller)
|
|
/// </summary>
|
|
private readonly Dictionary<Type, Dictionary<UIController, SystemChangedCaller>> _onSystemUnloadedDelegates = new();
|
|
|
|
/// <summary>
|
|
/// Field -> Controller -> Field assigner delegate
|
|
/// </summary>
|
|
private readonly Dictionary<Type, Dictionary<Type, InternalReflectionUtils.AssignField<UIController, object?>>> _assignerRegistry = new();
|
|
|
|
private delegate void StateChangedCaller(object controller, State.State state);
|
|
|
|
private delegate void SystemChangedCaller(object controller, IEntitySystem system);
|
|
|
|
private StateChangedCaller EmitStateChangedCaller(Type controller, Type state, bool entered)
|
|
{
|
|
if (controller.IsValueType)
|
|
{
|
|
throw new ArgumentException($"Value type controllers are not supported. Controller: {controller}");
|
|
}
|
|
|
|
if (state.IsValueType)
|
|
{
|
|
throw new ArgumentException($"Value type states are not supported. State: {state}");
|
|
}
|
|
|
|
var method = new DynamicMethod(
|
|
"StateChangedCaller",
|
|
typeof(void),
|
|
new[] {typeof(object), typeof(State.State)},
|
|
true
|
|
);
|
|
|
|
var generator = method.GetILGenerator();
|
|
|
|
Type onStateChangedType;
|
|
MethodInfo onStateChangedMethod;
|
|
if (entered)
|
|
{
|
|
onStateChangedType = typeof(IOnStateEntered<>).MakeGenericType(state);
|
|
onStateChangedMethod =
|
|
controller.GetMethod(nameof(IOnStateEntered<State.State>.OnStateEntered), new[] {state})
|
|
?? throw new NullReferenceException();
|
|
}
|
|
else
|
|
{
|
|
onStateChangedType = typeof(IOnStateExited<>).MakeGenericType(state);
|
|
onStateChangedMethod =
|
|
controller.GetMethod(nameof(IOnStateExited<State.State>.OnStateExited), new[] {state})
|
|
?? throw new NullReferenceException();
|
|
}
|
|
|
|
generator.Emit(OpCodes.Ldarg_0); // controller
|
|
generator.Emit(OpCodes.Castclass, onStateChangedType);
|
|
|
|
generator.Emit(OpCodes.Ldarg_1); // state
|
|
generator.Emit(OpCodes.Castclass, state);
|
|
|
|
generator.Emit(OpCodes.Callvirt, onStateChangedMethod);
|
|
generator.Emit(OpCodes.Ret);
|
|
|
|
return method.CreateDelegate<StateChangedCaller>();
|
|
}
|
|
|
|
private SystemChangedCaller EmitSystemChangedCaller(Type controller, Type system, bool loaded)
|
|
{
|
|
if (controller.IsValueType)
|
|
{
|
|
throw new ArgumentException($"Value type controllers are not supported. Controller: {controller}");
|
|
}
|
|
|
|
if (system.IsValueType)
|
|
{
|
|
throw new ArgumentException($"Value type systems are not supported. System: {system}");
|
|
}
|
|
|
|
var method = new DynamicMethod(
|
|
"SystemChangedCaller",
|
|
typeof(void),
|
|
new[] {typeof(object), typeof(IEntitySystem)},
|
|
true
|
|
);
|
|
|
|
var generator = method.GetILGenerator();
|
|
|
|
Type onSystemChangedType;
|
|
MethodInfo onSystemChangedMethod;
|
|
if (loaded)
|
|
{
|
|
onSystemChangedType = typeof(IOnSystemLoaded<>).MakeGenericType(system);
|
|
onSystemChangedMethod =
|
|
controller.GetMethod(nameof(IOnSystemLoaded<IEntitySystem>.OnSystemLoaded), new[] {system})
|
|
?? throw new NullReferenceException();
|
|
}
|
|
else
|
|
{
|
|
onSystemChangedType = typeof(IOnSystemUnloaded<>).MakeGenericType(system);
|
|
onSystemChangedMethod =
|
|
controller.GetMethod(nameof(IOnSystemUnloaded<IEntitySystem>.OnSystemUnloaded), new[] {system})
|
|
?? throw new NullReferenceException();
|
|
}
|
|
|
|
generator.Emit(OpCodes.Ldarg_0); // controller
|
|
generator.Emit(OpCodes.Castclass, onSystemChangedType);
|
|
|
|
generator.Emit(OpCodes.Ldarg_1); // system
|
|
generator.Emit(OpCodes.Castclass, system);
|
|
|
|
generator.Emit(OpCodes.Callvirt, onSystemChangedMethod);
|
|
generator.Emit(OpCodes.Ret);
|
|
|
|
return method.CreateDelegate<SystemChangedCaller>();
|
|
}
|
|
|
|
private ref UIController GetUIControllerRef(Type type)
|
|
{
|
|
return ref CollectionsMarshal.GetValueRefOrNullRef(_uiControllers, type);
|
|
}
|
|
|
|
private UIController GetUIController(Type type)
|
|
{
|
|
return _uiControllers[type];
|
|
}
|
|
|
|
public T GetUIController<T>() where T : UIController, new()
|
|
{
|
|
return (T) GetUIController(typeof(T));
|
|
}
|
|
|
|
private void SetupControllers()
|
|
{
|
|
foreach (var uiControllerType in _reflectionManager.GetAllChildren<UIController>())
|
|
{
|
|
if (uiControllerType.IsAbstract)
|
|
continue;
|
|
|
|
_dependencies.Register(uiControllerType);
|
|
}
|
|
|
|
_dependencies.BuildGraph();
|
|
|
|
foreach (var controllerType in _reflectionManager.GetAllChildren<UIController>())
|
|
{
|
|
var controller = (UIController) _dependencies.ResolveType(controllerType);
|
|
_uiControllers[controllerType] = controller;
|
|
|
|
foreach (var fieldInfo in controllerType.GetAllPropertiesAndFields())
|
|
{
|
|
if (!fieldInfo.HasAttribute<UISystemDependencyAttribute>())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var backingField = fieldInfo;
|
|
if (fieldInfo is SpecificPropertyInfo property)
|
|
{
|
|
if (property.TryGetBackingField(out var field))
|
|
{
|
|
backingField = field;
|
|
}
|
|
else
|
|
{
|
|
var setter = property.PropertyInfo.GetSetMethod(true);
|
|
if (setter == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Property with {nameof(UISystemDependencyAttribute)} attribute did not have a backing field nor setter");
|
|
}
|
|
}
|
|
}
|
|
|
|
//Do not do anything if the field isn't an entity system
|
|
if (!backingField.FieldType.IsAssignableTo(typeof(IEntitySystem)))
|
|
continue;
|
|
|
|
var typeDict = _assignerRegistry.GetOrNew(fieldInfo.FieldType);
|
|
var assigner = (InternalReflectionUtils.AssignField<UIController, object?>)InternalReflectionUtils.EmitFieldAssigner(typeof(UIController), backingField, true);
|
|
typeDict.Add(controllerType, assigner);
|
|
}
|
|
|
|
foreach (var @interface in controllerType.GetInterfaces())
|
|
{
|
|
if (!@interface.IsGenericType)
|
|
continue;
|
|
|
|
var typeDefinition = @interface.GetGenericTypeDefinition();
|
|
var genericType = @interface.GetGenericArguments()[0];
|
|
if (typeDefinition == typeof(IOnStateEntered<>))
|
|
{
|
|
var enteredCaller = EmitStateChangedCaller(controllerType, genericType, true);
|
|
_onStateEnteredDelegates.GetOrNew(genericType).Add(controller, enteredCaller);
|
|
}
|
|
else if (typeDefinition == typeof(IOnStateExited<>))
|
|
{
|
|
var exitedCaller = EmitStateChangedCaller(controllerType, genericType, false);
|
|
_onStateExitedDelegates.GetOrNew(genericType).Add(controller, exitedCaller);
|
|
}
|
|
else if (typeDefinition == typeof(IOnSystemLoaded<>))
|
|
{
|
|
DebugTools.Assert(!genericType.IsAbstract, "IOnSystemLoaded<> does not support abstract shared systems.");
|
|
var loadedCaller = EmitSystemChangedCaller(controllerType, genericType, true);
|
|
_onSystemLoadedDelegates.GetOrNew(genericType).Add(controller, loadedCaller);
|
|
}
|
|
else if (typeDefinition == typeof(IOnSystemUnloaded<>))
|
|
{
|
|
DebugTools.Assert(!genericType.IsAbstract, "IOnSystemLoaded<> does not support abstract shared systems.");
|
|
var unloadedCaller = EmitSystemChangedCaller(controllerType, genericType, false);
|
|
_onSystemUnloadedDelegates.GetOrNew(genericType).Add(controller, unloadedCaller);
|
|
}
|
|
}
|
|
}
|
|
|
|
_systemManager.SystemLoaded += OnSystemLoaded;
|
|
_systemManager.SystemUnloaded += OnSystemUnloaded;
|
|
|
|
_stateManager.OnStateChanged += OnStateChanged;
|
|
|
|
_dependencies.BuildGraph();
|
|
}
|
|
|
|
private void InitializeControllers()
|
|
{
|
|
foreach (var controller in _uiControllers.Values)
|
|
{
|
|
controller.Initialize();
|
|
}
|
|
}
|
|
|
|
private void UpdateControllers(FrameEventArgs args)
|
|
{
|
|
foreach (var controller in _uiControllers.Values)
|
|
{
|
|
// TODO hud refactor run frame update only if it has been overriden
|
|
controller.FrameUpdate(args);
|
|
}
|
|
}
|
|
|
|
// TODO hud refactor optimize this to use an array
|
|
// TODO hud refactor BEFORE MERGE cleanup subscriptions for all implementations when switching out of gameplay state
|
|
private void OnStateChanged(StateChangedEventArgs args)
|
|
{
|
|
Type? oldType = args.OldState.GetType();
|
|
while (oldType != null)
|
|
{
|
|
if (_onStateExitedDelegates.TryGetValue(oldType, out var exitedDelegates))
|
|
{
|
|
foreach (var (controller, caller) in exitedDelegates)
|
|
{
|
|
caller(controller, args.OldState);
|
|
}
|
|
}
|
|
oldType = oldType.BaseType;
|
|
}
|
|
|
|
Type? newType = args.NewState.GetType();
|
|
while (newType != null)
|
|
{
|
|
if (_onStateEnteredDelegates.TryGetValue(newType, out var enteredDelegates))
|
|
{
|
|
foreach (var (controller, caller) in enteredDelegates)
|
|
{
|
|
caller(controller, args.NewState);
|
|
}
|
|
}
|
|
newType = newType.BaseType;
|
|
}
|
|
}
|
|
|
|
private void OnSystemLoaded(object? sender, SystemChangedArgs args)
|
|
{
|
|
var systemType = args.System.GetType();
|
|
|
|
if (_assignerRegistry.TryGetValue(systemType, out var assigners))
|
|
{
|
|
foreach (var (controllerType, assigner) in assigners)
|
|
{
|
|
assigner(ref GetUIControllerRef(controllerType), args.System);
|
|
}
|
|
}
|
|
|
|
if (_onSystemLoadedDelegates.TryGetValue(systemType, out var delegates))
|
|
{
|
|
foreach (var (controller, caller) in delegates)
|
|
{
|
|
caller(controller, args.System);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnSystemUnloaded(object? system, SystemChangedArgs args)
|
|
{
|
|
var systemType = args.System.GetType();
|
|
|
|
if (_onSystemUnloadedDelegates.TryGetValue(systemType, out var delegates))
|
|
{
|
|
foreach (var (controller, caller) in delegates)
|
|
{
|
|
caller(controller, args.System);
|
|
}
|
|
}
|
|
|
|
if (_assignerRegistry.TryGetValue(systemType, out var assigners))
|
|
{
|
|
foreach (var (controllerType, assigner) in assigners)
|
|
{
|
|
assigner(ref GetUIControllerRef(controllerType), null);
|
|
}
|
|
}
|
|
}
|
|
}
|