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 { /// /// All registered instances indexed by type /// private readonly Dictionary _uiControllers = new(); /// /// Dependency collection holding UI controllers and IoC services /// private DependencyCollection _dependencies = default!; /// /// Implementations of to invoke when a state is entered /// State Type -> (UIController, Caller) /// private readonly Dictionary> _onStateEnteredDelegates = new(); /// /// Implementations of to invoke when a state is exited /// State Type -> (UIController, Caller) /// private readonly Dictionary> _onStateExitedDelegates = new(); /// /// Implementations of to invoke when an entity system is loaded /// Entity System Type -> (UIController, Caller) /// private readonly Dictionary> _onSystemLoadedDelegates = new(); /// /// Implementations of to invoke when an entity system is unloaded /// Entity System Type -> (UIController, Caller) /// private readonly Dictionary> _onSystemUnloadedDelegates = new(); /// /// Field -> Controller -> Field assigner delegate /// private readonly Dictionary>> _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.OnStateEntered), new[] {state}) ?? throw new NullReferenceException(); } else { onStateChangedType = typeof(IOnStateExited<>).MakeGenericType(state); onStateChangedMethod = controller.GetMethod(nameof(IOnStateExited.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(); } 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.OnSystemLoaded), new[] {system}) ?? throw new NullReferenceException(); } else { onSystemChangedType = typeof(IOnSystemUnloaded<>).MakeGenericType(system); onSystemChangedMethod = controller.GetMethod(nameof(IOnSystemUnloaded.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(); } private ref UIController GetUIControllerRef(Type type) { return ref CollectionsMarshal.GetValueRefOrNullRef(_uiControllers, type); } private UIController GetUIController(Type type) { return _uiControllers[type]; } public T GetUIController() where T : UIController, new() { return (T) GetUIController(typeof(T)); } private void SetupControllers() { foreach (var uiControllerType in _reflectionManager.GetAllChildren()) { if (uiControllerType.IsAbstract) continue; _dependencies.Register(uiControllerType); } _dependencies.BuildGraph(); foreach (var controllerType in _reflectionManager.GetAllChildren()) { var controller = (UIController) _dependencies.ResolveType(controllerType); _uiControllers[controllerType] = controller; foreach (var fieldInfo in controllerType.GetAllPropertiesAndFields()) { if (!fieldInfo.HasAttribute()) { 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)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); } } } }