Tuesday 5 November 2013

Dynamic Compilation (Part 2)

Finally I have a full windows 8.1 installed on all my machines, so I'm pretty excited to be able to now concentrate fully on dx11.2 development. That also means that I have my CopyToHtml tool working again (it got fucked up somehow in my previous studio).

I spoke about how I was reflecting hlsl functions using latest DirectX features in previous post.

Now one interesting thing, is my automaton has (a lot) of boilerplate. Building a node is fairly easy, but generally quite tedious.

Here is an example (that builds a color from hsl parameters):

Code Snippet
  1. using System.Text;
  2. using SharpDX;
  3.  
  4. using FlareTic.Graph.Interface;
  5. using FlareTic.Graph.Interface.Automaton;
  6.  
  7. namespace FlareTic.Nodes.Automaton
  8. {
  9.     [AutomatonNode(Name = "HSL", Category = "Color", SystemName = "flt.automaton.Color.HSL")]
  10.     public class ColorHSLNode : IAutomatonNodeInstance
  11.     {
  12.         private ScalarSinkPin h;
  13.         private ScalarSinkPin s;
  14.         private ScalarSinkPin l;
  15.         private ScalarSinkPin a;
  16.  
  17.         private ColorSourcePin output;
  18.  
  19.         public void AssignContainer(IAutomatonNodeContainer container)
  20.         {
  21.             this.h = container.PinFactory.CreateScalarSinkPin("Hue");
  22.             this.s = container.PinFactory.CreateScalarSinkPin("Saturation");
  23.             this.l = container.PinFactory.CreateScalarSinkPin("Brightness");
  24.             this.a = container.PinFactory.CreateScalarSinkPin("Alpha");
  25.  
  26.             this.output = container.PinFactory.CreateColorSourcePin("Output");
  27.         }
  28.  
  29.         public void Evaluate()
  30.         {
  31.             this.output.Value = FlareTic.Core.Maths.ColorSpaces.FromHSL(h.Value, s.Value, l.Value, a.Value);
  32.         }
  33.  
  34.         public void Dispose() { }
  35.  
  36.     }
  37. }

Finally from what we see here, we have a static function, a few parameters and some attributes.

For each node we need to endlessly repeat all that build code, all this just to build a function...
But, thinking about it (and I remember 4v people trying to do something like that at some point), I can easily reflect a function too.
And c# allows to compile an assembly on the fly.
So wouldn't it be better to just reflect the function, generate code on the fly and compile into an assembly?

It would look somehow like this:

Code Snippet
  1. [FunctionBank()]
  2. public static class Arithmetic
  3. {
  4.     [Function(Name = "Add",Category="Value", OutputName="Output")]
  5.     public static float Add([Parameter(Name = "Input 1")] float v1, [Parameter(Name = "Input 2")] float v2)
  6.     {
  7.         return v1 + v2;
  8.     }
  9.  
  10.     [Function(Name = "Substract", Category = "Value", OutputName = "Output")]
  11.     public static float Substract([Parameter(Name = "Input 1")] float v1, [Parameter(Name = "Input 2")] float v2)
  12.     {
  13.         return v1 - v2;
  14.     }
  15.  
  16.     [Function(Name = "Multiply", Category = "Value", OutputName = "Output")]
  17.     public static float Multiply([Parameter(Name = "Input 1")] float v1, [Parameter(Name = "Input 2")] float v2)
  18.     {
  19.         return v1 * v2;
  20.     }
  21.  
  22.  
  23.     [Function(Name = "Equals", Category = "Value", OutputName = "Output")]
  24.     public static bool Equals([Parameter(Name = "Input 1")] float v1, [Parameter(Name = "Input 2")] float v2, [Parameter(Name = "Espilon")] float eps)
  25.     {
  26.         return Math.Abs(v1 - v2) <= eps;
  27.     }
  28. }

Now from this, we can indicate that our class has functions (to speed up processing and not scan every method of every class).

Every export function has a little description, same for parameters.

Try number 1, build a little code builder, small template node, and inject code depending on parameters.

Simple and easy, works.

Now as you see you have a lot of attributes, and I find them a bit intrusive. The main problem is also that sometimes I might just want to use a function in a compiled dll.

So instead of reflecting the function directly, you can simply create a delegate, this looks like this:

Code Snippet
  1. [NodeDelegateAttribute(Name = "Add", Category = "Value", OutputName = "Output", FunctionName = "CodeLib.Arithmetic.Add")]
  2.     public delegate float AddDelegate([Parameter(Name = "Input 1")] float v1, [Parameter(Name = "Input 2")] float v2);

Now the nice thing is I can rename the function, give proper naming to parameters...

So from those 2 lines of code I now have a nice nodes.

Please note that this is also cumbersome, when you have an API like SharpDX you have thousands of functions.

So instead, we can also do the following:

  • Scan the datatypes I want to import.
  • Reflect functions.
  • Do a pre-test to check that I want/can import this function.
  • Use function reflection directly to build my code.
This is really simple and works really well, the only little issue is that I can't really name parameters the way I want (unless I start to build some ruleset, but it might be painful).
In case of standard Math types, it's really not important at all tho ;)

So here we go, on startup I specify what types I want to scan, it builds a function list, selecting a function simply generate node code on the fly, compiles it and create a node.

How to have 5000 nodes in few lines of code, one weekend well spent ;)



Of course, another great thing is that it also helps a lot for exporting an exe at the end, since you can just compile all functions used in a single assembly (as nodes), or export the generated code in a c# project, you can easily cherry pick which function get used and avoid to deploy all nodes.

Here we are, most of my tool is now heading towards that, simpler, cleaner, all that I love ;)

Stay tuned for next section, which is going into something a bit more complex ;)

No comments:

Post a Comment