jwatte:System.Reflection is your friend!
You probably want to have a look at
the XNA Path code to get inspiration. That code evaluates expressions that follow properties and call functions, but doesn't (currently) have conditionals or math. You can have a function called "AddMoney()" and call it, though.
The technique described is also the one I'm using for expressions currently. There are slight garbage generation concerns, but you can work around them by doing some intelligent caching. The real advantage here is that this lets you add a hint of dynamic scripting to your games without having to go to the trouble of embedding a complex runtime. You can implement basic ternary conditionals ( condition ? true : false style ) and arithmetic without much effort or prior experience, which gets you 90% of the way there. In some cases that may be all you need for your game, since anything beyond that level of complexity should probably just be compiled into your C# anyway.
The main concerns if you're using reflection is that each reflection call on a given type has a fairly large initial cost, and further calls tend to generate a large amount of garbage. Aggressively caching calls to things like GetMethod and GetField will help tremendously, but you're still pretty much guaranteed to generate a small amount of garbage from every call, since invoking methods and accessing fields via reflection requires* boxing value types.
I described my approach in minimal detail
here and the source code for the lexer is
here. If it'd be of use to you I can move the expression compiler to Google Code as well (right now it's part of the source tree for my game since it's tightly integrated, but if it would be of help to you I don't mind moving it over.) I currently use it for basic conditionals and linking the states of objects, so things like:
<SpriteAnimation typeId="1">
<Name>Grapple</Name>
<Group name="idle" />
<Frames delay="50" loop="PingPong" />
<Branches>
<Branch name="Stand to Crouch" if="!Grappling and (Crouching)" />
<Branch name="Walk" if="!Grappling and (Acceleration != 0)" />
<Branch name="Stand" if="!Grappling" />
</Branches>
</SpriteAnimation>
and
<LevelMovingPlatform typeId="8">
<Name>BottomMovingPlatform</Name>
<Speed>BottomEndingZone.State ? 4.0 : BottomCrank.State</Speed>
are handled by the expression parser so that I can tweak and adjust things in real-time without dealing with the compiler. (This is obviously a very slippery slope, though; when building a scripting system like this you're in continual danger of reinventing an entire wheel when you've already got a good one at your disposal.)
Where possible I strongly encourage making your expression syntax very C#-like, if not exactly equivalent to C#. Doing so will save you a lot of stress later on if you decide that the overhead of runtime parsing/delegate combining/expression trees is too much, and you want to compile all your game's expressions at build time, because that will allow you to generate .cs files from them and build them into assemblies that you can ship with your game.
* Note that in some cases you can bypass the need for boxing if you're willing to jump through some extremely elaborate hoops involving generic methods and the Delegate constructor:
private static IValueProxy<T> WrapPropertyGetter<T> (Func<T> fn) {
if (fn == null)
return null;
return new TypedValueProxy<T>(fn);
}
public static IValueProxy MakePropertyGetter (object obj, PropertyInfo property) {
var baseType = typeof(Func<>);
var delegateType = baseType.MakeGenericType(property.PropertyType);
var del = Delegate.CreateDelegate(delegateType, obj, property.GetGetMethod());
var baseMethod = typeof(ExpressionCompiler).GetMethod("WrapPropertyGetter", BindingFlags.Static | BindingFlags.NonPublic);
var wrapperMethod = baseMethod.MakeGenericMethod(property.PropertyType);
var result = (IValueProxy)wrapperMethod.Invoke(null, new object[] { del });
return result;
}
I love reflection. :D
Kevin Gadd, Squared Interactive
Development Blog |
TwitterHelp playtest my game,
Inferus!