mirror of
https://github.com/NuclearPowered/Reactor.git
synced 2026-10-19 15:51:54 +00:00
add CompilerGeneratedObjectWrapper.cs and StateMachineWrapper.cs
This commit is contained in:
1 parent
9bd370bfc1
commit
a1f6164ca0
2 files changed
+187
No files matched your search
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// A safe wrapper around compiler generated classes like DisplayClass or IEnumerator state machines.
|
||||
/// </summary>
|
||||
public class CompilerGeneratedObjectWrapper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a reference to the compiler generated object.
|
||||
/// </summary>
|
||||
public object GeneratedObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the compiler generated object.
|
||||
/// </summary>
|
||||
protected Type GeneratedType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property info cache for faster lookups.
|
||||
/// </summary>
|
||||
protected Dictionary<string, PropertyInfo> PropertyCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CompilerGeneratedObjectWrapper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="generatedObject">An instance of the compiler generated object.</param>
|
||||
public CompilerGeneratedObjectWrapper(object generatedObject)
|
||||
{
|
||||
GeneratedObject = generatedObject;
|
||||
GeneratedType = generatedObject.GetType();
|
||||
|
||||
PropertyCache = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of a field in the compiler generated object.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">The name of the field to get.</param>
|
||||
/// <typeparam name="TField">The type of the field.</typeparam>
|
||||
/// <returns>>The value of the field.</returns>
|
||||
/// <exception cref="MissingMemberException">Thrown if the field does not exist.</exception>
|
||||
public TField GetField<TField>(string fieldName)
|
||||
{
|
||||
if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
|
||||
{
|
||||
propertyInfo = AccessTools.Property(GeneratedType, fieldName);
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
throw new MissingMemberException($"Could not find field '{fieldName}' in type '{GeneratedType}'.");
|
||||
}
|
||||
PropertyCache[fieldName] = propertyInfo;
|
||||
}
|
||||
|
||||
return (TField) propertyInfo.GetValue(GeneratedObject)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of a field in the compiler generated object.
|
||||
/// </summary>
|
||||
/// <param name="fieldName">The name of the field to set.</param>
|
||||
/// <param name="value">The value to set.</param>
|
||||
/// <typeparam name="TField">The type of the field.</typeparam>
|
||||
/// <exception cref="MissingMemberException">Thrown if the field does not exist.</exception>
|
||||
public void SetField<TField>(string fieldName, TField value)
|
||||
{
|
||||
if (!PropertyCache.TryGetValue(fieldName, out var propertyInfo))
|
||||
{
|
||||
propertyInfo = AccessTools.Property(GeneratedType, fieldName);
|
||||
if (propertyInfo == null)
|
||||
{
|
||||
throw new MissingMemberException($"Could not find field '{fieldName}' in type '{GeneratedType}'.");
|
||||
}
|
||||
PropertyCache[fieldName] = propertyInfo;
|
||||
}
|
||||
|
||||
propertyInfo.SetValue(GeneratedObject, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Reactor.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper for state machine objects to access their parent instance and state.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the parent class that owns the state machine.</typeparam>
|
||||
public class StateMachineWrapper<T> : CompilerGeneratedObjectWrapper
|
||||
{
|
||||
// normally it is fields, but IL2CPP turns them into properties
|
||||
private readonly PropertyInfo _thisProperty;
|
||||
private readonly PropertyInfo _stateProperty;
|
||||
|
||||
private T? _parentInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the parent class that owns the state machine.
|
||||
/// </summary>
|
||||
public T Instance => _parentInstance ??= (T) _thisProperty.GetValue(GeneratedObject)!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current state of the state machine.
|
||||
/// </summary>
|
||||
/// <returns>The current state as an integer.</returns>
|
||||
public int State
|
||||
{
|
||||
get => (int) _stateProperty.GetValue(GeneratedObject)!;
|
||||
set => _stateProperty.SetValue(GeneratedObject, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StateMachineWrapper{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="stateMachine">The state machine instance to wrap.</param>
|
||||
public StateMachineWrapper(object stateMachine) : base(stateMachine)
|
||||
{
|
||||
_thisProperty = AccessTools.Property(GeneratedType, "__4__this");
|
||||
_stateProperty = AccessTools.Property(GeneratedType, "__1__state");
|
||||
|
||||
if (_thisProperty == null || _stateProperty == null)
|
||||
{
|
||||
throw new MissingMemberException($"Could not find required properties in type '{GeneratedType}'.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a parameter from the state machine by its name.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter to retrieve.</param>
|
||||
/// <typeparam name="TField">The type of the parameter to retrieve.</typeparam>
|
||||
/// <returns>>The value of the specified parameter.</returns>
|
||||
/// <exception cref="MissingFieldException">Thrown if the specified parameter does not exist.</exception>
|
||||
public TField GetParameter<TField>(string parameterName)
|
||||
{
|
||||
return GetField<TField>(parameterName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a parameter in the state machine by its name.
|
||||
/// </summary>
|
||||
/// <param name="parameterName">The name of the parameter to set.</param>
|
||||
/// <param name="value">The value to set for the parameter.</param>
|
||||
/// <typeparam name="TField">The type of the parameter to set.</typeparam>
|
||||
/// <exception cref="MissingFieldException">Thrown if the specified parameter does not exist.</exception>
|
||||
public void SetParameter<TField>(string parameterName, TField value)
|
||||
{
|
||||
SetField(parameterName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the MoveNext method of a state machine for the specified method name in type T.
|
||||
/// </summary>
|
||||
/// <param name="methodName">The name of the method whose state machine MoveNext method is to be retrieved.</param>
|
||||
/// <typeparam name="T">The type containing the state machine.</typeparam>
|
||||
/// <returns>The MoveNext <see cref="MethodBase"/> if found; otherwise, null.</returns>
|
||||
public static MethodBase? GetStateMachineMoveNext(string methodName)
|
||||
{
|
||||
var typeName = typeof(T).FullName;
|
||||
var showRoleStateMachine =
|
||||
typeof(T)
|
||||
.GetNestedTypes()
|
||||
.FirstOrDefault(x => x.Name.Contains(methodName));
|
||||
|
||||
if (showRoleStateMachine == null)
|
||||
{
|
||||
Error($"Failed to find {methodName} state machine for {typeName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var moveNext = AccessTools.Method(showRoleStateMachine, "MoveNext");
|
||||
if (moveNext == null)
|
||||
{
|
||||
Error($"Failed to find MoveNext method for {typeName}.{methodName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
Info($"Found {methodName}.MoveNext");
|
||||
return moveNext;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user