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);
}
}
}
}