Saturday, June 7, 2014
I'm in the middle of some intense (but exciting) work and should be achieving my first results shortly...!
Exposing Internal Functions
I wrote a while back, when developing the reflection system for the LT engine, about 'function' reflection - giving the engine the ability to understand the properties of the very pieces of code that make up LT. Just as I am automatically able to expose data types in the data editor, so I can expose functions in a function editor (sound a bit like editing code?
).
It's a bit harder because the function reflection system is less mature than the data reflection system. I have to do some more legwork before I will actually be able to call functions from UDF.
I've opted to go with a reflection-based approach rather than building those 'expression' structures of which I spoke last time. With reflection, I can maintain a unified, simple interface for function data. Everything should just work automatically, rather than requiring me to do any real work to set up the function interfaces. Well, that's the theory at least..it will be a few more hours until I can test
UDF : Mixing Data and Function
Yesterday, I mentioned all the craziness related to function / data and their differences. I've decided today that I want to treat UDF (the format LT uses to specify moddable data) as a
functional data language. This means that, instead of specifying a data type, every UDF file actually specifies a
function that generates a given data type. This paradigm actually goes beautifully with the very essence of proceduralism!! Proceduralism is all about leveraging function to create data.
Now, it gets pretty awesome when you start thinking about the implications here. A few observations: first, constant data is still easily-specified. You can think of constant data as simply calling the constructor of a type with constant arguments. E.g. Vec3(1, 6, 7) is calling the constructor function of a Vec3, and passing in 3 constant integers. It's really just data, but we can easily think of it as a function. On the other hand, consider Vec3(Rand(1, 5), 6, 7). Now we have a constructor being called with one non-constant parameter. This is no longer constant data - it's a function, and we can call it repeatedly to generate new vectors.
One of the fantastic insights that I had today was to think of
every request for a T as being a function, rather than data. That applies recursively to every level of the data, not just the top-level. The above example demonstrates this: when you construct a Vec3, you require 3 numbers.
But! We will actually construct and call
3 functions to generate those three numbers. Another way to understand that is that, in the game code, if we have a function F that takes an X and a Y (X and Y are types), then in UDF we will actually call F using
functions that return an X and a Y. Again, those functions can be
constant, but the point remains that we are thinking of
everything as function. Very nice.
---
The flexibility afforded by this architecture should be insane. I'm almost foaming at the mouth just thinking about it. Best part is it should be working in a few hours, and I should be well on my way to content authorship