Skip to content

Instantly share code, notes, and snippets.

@fdrobidoux
Created June 22, 2021 17:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fdrobidoux/202df0692322f9b79f3def23e79e04c3 to your computer and use it in GitHub Desktop.
Save fdrobidoux/202df0692322f9b79f3def23e79e04c3 to your computer and use it in GitHub Desktop.
ValueRestorer - Restores a value from within a IEnumerator-based Coroutine.
namespace Ada2.Scripting
{
public static class Usage
{
public static IEnumerator TestEnumeration()
{
var component = service.Entity.GetComponent<ValueComponent>();
// When enumerator destroyed, will be disposed, which will restore to the original value.
using var valueRestorer = new ValueRestorer()
.Assign(x => x.IntValue, component, 25) // Will assign that value instantly.
.Assign(x => x.FloatValue, component); // Will not assign any value.
// ... Do yields and coroutine logic ...
/* Once reachign here or a yield break, values are restored. */
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
namespace Ada2.Scripting
{
public class ValueRestorer : IDisposable
{
private List<ValueRestorationData> ValueRestorations = new List<ValueRestorationData>();
public ValueRestorer Assign<T, T2>(Expression<Func<T, T2>> propertExpression, T instance, T2 value)
{
ValueRestorations.Add(new ValueRestorationData<T, T2>(propertExpression, instance, value));
return this;
}
public ValueRestorer Assign<T, T2>(Expression<Func<T, T2>> propertExpression, T instance)
{
ValueRestorations.Add(new ValueRestorationData<T, T2>(propertExpression, instance));
return this;
}
public void Dispose()
{
foreach (var value in ValueRestorations)
value.Restore();
ValueRestorations.Clear();
}
private class ValueRestorationData<T, T2> : ValueRestorationData
{
protected T2 OriginalValue { get; set; }
protected Expression<Func<T, T2>> PropertyExpr { get; set; }
public ValueRestorationData(Expression<Func<T, T2>> propertyExpr, [NotNull]T instance) : base(instance!)
{
PropertyExpr = propertyExpr;
if (PropertyExpr.Body is MemberExpression)
{
OriginalValue = PropertyExpr.Compile().Invoke(instance);
}
else
{
throw new NotSupportedException("Can't use anything but a MemberExpression so far.");
}
}
public ValueRestorationData(Expression<Func<T, T2>> propertyExpr, T instance, T2 NewValue)
: this(propertyExpr, instance)
{
if (PropertyExpr.Body is MemberExpression memberExpression)
{
SetDeepValue(instance, PropertyExpr, NewValue);
}
else
{
throw new NotSupportedException("Can't use anything but a MemberExpression so far.");
}
}
public override void Restore()
{
SetDeepValue((T)Instance, PropertyExpr, OriginalValue);
}
/// All credits for this method goes to xanatos.
/// Source on StackOverflow : https://stackoverflow.com/a/29092675/4169987
private static void SetDeepValue(T target, Expression<Func<T, T2>> propertyToSet, T2 valueToSet)
{
List<MemberInfo> members = new List<MemberInfo>();
Expression exp = propertyToSet.Body;
// There is a chain of getters in propertyToSet, with at the
// beginning a ParameterExpression. We put the MemberInfo of
// these getters in members. We don't really need the
// ParameterExpression
while (exp != null)
{
MemberExpression mi = exp as MemberExpression;
if (mi != null)
{
members.Add(mi.Member);
exp = mi.Expression;
}
else
{
ParameterExpression pe = exp as ParameterExpression;
if (pe == null)
{
// We support only a ParameterExpression at the base
throw new NotSupportedException();
}
break;
}
}
if (members.Count == 0)
{
// We need at least a getter
throw new NotSupportedException();
}
// Now we must walk the getters (excluding the last).
object targetObject = target;
// We have to walk the getters from last (most inner) to second
// (the first one is the one we have to use as a setter)
for (int i = members.Count - 1; i >= 1; i--)
{
PropertyInfo pi = members[i] as PropertyInfo;
if (pi != null)
{
targetObject = pi.GetValue(targetObject);
}
else
{
FieldInfo fi = (FieldInfo)members[i];
targetObject = fi.GetValue(targetObject);
}
}
// The first one is the getter we treat as a setter
{
PropertyInfo pi = members[0] as PropertyInfo;
if (pi != null)
{
pi.SetValue(targetObject, valueToSet);
}
else
{
FieldInfo fi = (FieldInfo)members[0];
fi.SetValue(targetObject, valueToSet);
}
}
}
}
private abstract class ValueRestorationData
{
public object Instance { get; }
public bool isStruct;
public bool isProperty;
protected ValueRestorationData([NotNull] object instance)
{
Instance = instance;
}
public abstract void Restore();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment