Skip to content

Instantly share code, notes, and snippets.

@Simie
Created September 30, 2014 21:18
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 Simie/ab4c23fbc91ea72909da to your computer and use it in GitHub Desktop.
Save Simie/ab4c23fbc91ea72909da to your computer and use it in GitHub Desktop.
Bolt Expression
using UnityEngine;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace ExpressionTest
{
public static class PathExtractor<T>
{
/// <summary>
/// Extract a path string from a lambda expression
/// </summary>
/// <param name="tree">Tree.</param>
/// <typeparam name="TProperty">The 1st type parameter.</typeparam>
public static string Extract<TProperty>(Expression<Func<T, TProperty>> tree)
{
var propString = "";
var lambda = (LambdaExpression)tree;
Extract(lambda.Body, ref propString);
return propString;
}
private static void Extract(Expression expr, ref string str)
{
// The root of the expression (whatever the argument name is) is not required
if (expr is ParameterExpression) {
return;
} else if (expr is MethodCallExpression) {
// If it's a method call, it's probably an index accessor
var method = (MethodCallExpression) expr;
// Ensure that it is actually an indexer
if(!method.Method.Name.Contains("get_Item"))
throw new InvalidOperationException();
// Extract whatever is before this in the expression
if (method.Object != null)
Extract(method.Object, ref str);
// Just add a []
str += "[]";
// TODO: Don't [] if at the end of the expression?
} else if (expr is MemberExpression) {
// Simple member accessor, just add the member name
var member = (MemberExpression) expr;
if (member.Expression != null)
Extract(member.Expression, ref str);
str += "." + member.Member.Name;
} else {
throw new NotImplementedException("Expression type is unhandled");
}
}
}
public class NestLevel1
{
public int Value;
public IList<NestLevel2> Nest { get; private set; }
}
public class NestLevel2
{
public bool Value;
}
public class SomeStateObject
{
public IList<NestLevel1> Embeded { get; private set; }
// TODO: Use another data stucture, as this doesn't allow multiple registrations for the same member path
private Dictionary<string, Delegate> _callbackDictionary = new Dictionary<string, Delegate>();
public void RegisterCallback<TEvent>(Expression<Func<SomeStateObject, TEvent>> expr, Action<SomeStateObject, TEvent> onChanged)
{
// Extract the member path
var path = PathExtractor<SomeStateObject>.Extract(expr);
// Add the event to the callback dictionary using the member path as a key
_callbackDictionary[path] = onChanged;
Debug.Log("Registered callback for " + path);
}
public void TriggerCallback<TEvent>(Expression<Func<SomeStateObject, TEvent>> expr, TEvent newValue)
{
// Extract the path
var path = PathExtractor<SomeStateObject>.Extract(expr);
// Cast the delegate to the expected type and invoke with the changed object and new value
((Action<SomeStateObject, TEvent>) _callbackDictionary[path]).Invoke(this, newValue);
}
}
}
using UnityEngine;
using System.Collections;
using ExpressionTest;
public class TestBehaviour : MonoBehaviour {
string result;
void Awake() {
Application.RegisterLogCallback(LogCallback);
}
void Start () {
var someStateObject = new SomeStateObject();
someStateObject.RegisterCallback(c => c.Embeded[0].Nest[0].Value, OnChanged);
someStateObject.TriggerCallback(c => c.Embeded[0].Nest[0].Value, true);
someStateObject.TriggerCallback(c => c.Embeded[0].Nest[0].Value, false);
}
private void OnChanged(SomeStateObject testClass, bool newValue)
{
Debug.Log("OnChanged: " + newValue);
}
void OnGUI() {
GUILayout.Label(result);
}
void LogCallback(string info, string stack, LogType type)
{
result += string.Format("{0}: {1}\n", type, info);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment