"lastlogin">2006-02-12 5:46:38 PM
<Double name="this is a really long ass data variable name lol">3.14158Double>
"name">Homer Jay Simpson
The rules are:
The tag names will be type names, so we know how to decode everything under the tag, be it simple data, or an entire tree of sub-tags. This will eventually be integrated into my engine by way of a registry.
XmlDecoderRegistry.Register( "Account", new XmlDecoderDelegate( AccountDecoder ) );
or something like that.
The second rule is that attributes are to be used for figuring out how to create the data element. IE: entities will have ID attributes, because they are required by the entity constructors. Base data elements will have the name of the element in the attributes, because we need to know where to put that element when it's read.
I like it, it's smaller than the previous version, and it seems to work well.
Another big development over the weekend is the scripting engine. It's freaking cool.
CodeLoader loader = new CodeLoader();
loader.LoadCode( "hello.cs" );
MessageScriptAdapter h = loader.CreateScript
( "BetterMUDScript.Hello" );
MessageScriptAdapter g = loader.CreateScript
( "BetterMUDScript.GoodBye" );
message = h.Message(); // "Hello!"
message = g.Message(); // "Good Bye!"
loader.LoadCode( "hello2.cs" );
message = h.Message(); // "Hello, dudes!"
This simple example demonstrates the ease of the engine. Firstly, all you need to do is send it a file to load, and it will load the file for you. At the moment, there is no mechanism for reporting errors or other fancy things.
The engine will load the .cs file, and put it in its very own assembly (in memory). It goes through every exported type in the script, and takes note of every class that inherits from "Script", and puts them into a registry. The code engine will only allow you to instantiate classes that inherit from "Script", though it will still compile classes that aren't scripts.
The scripting engine works using an adapter pattern for each script. When you request a script from the engine, it instantiates a new script, and packages that script up into an adapter object (MessageAdapter in this example). For all intents and purposes, you will interface with that adapter object.
Now here's the cool part.
See the last two lines? It loads a file "hello2.cs", which contains a newly updated version of the BetterMUDScript.Hello class, which returns a different string.
When the code engine loads that file, it notices that the class "Hello" has been loaded before, so it looks to see if there were any adapters registered for the old version. If so, it goes through every adapter and updates its script object, so the next time you use the adapter, it will use the newly updated version.
Another cool thing is that when a new script is loaded, the engine will go through the old script and copy over any data that may exist.
This was an issue that was never resolved in BetterMUD 1.0. I use reflection to do this, which can be slow, but I'm not that concerned, because loading new versions of scripts isn't going to be something that is going to be happening often.
The coolest part is that it even tries to coerce data into a new type if the type changes.
For example, if the old version of Hello has an int named "x", and the new version has a float named "x", it will copy the int value of x into the float x. It works the other way around too, but you'll lose precision.
At the moment, the engine only copies datatypes that are considered "basic types", which include all integral types, chars, floats, doubles, decimals, strings, datetimes, and timespans. For some odd reason, .NET doesn't think a TimeSpan is a basic type, so I had to go through some trouble to create a flexible converter class, which essentially does everything that System.Convert does, except it works with TimeSpans too.
On the plus side, Tangential.Utilities.Converter is extensible. You can add custom converters to it as you see fit:
Converter.RegisterConverter( typeof( string ), typeof( TimeSpan ), new Converter.ConvertDelegate( StringToTimeSpan );
Converter.RegisterConverter( typeof( DateTime ), typeof( TimeSpan ), new Converter.ConvertDelegate( DateTimeToTimeSpan ) );
Converter.RegisterConverter( typeof( TimeSpan ), typeof( TimeSpan ), new ConverterConvertDelegate( Identity ) );
ts = (TimeSpan)Converter.Convert( "00:10:15", typeof( TimeSpan ) );
ts = (TimeSpan)Converter.Convert( "01:10:00", "TimeSpan" );
ts = Converter.ConvertTo
( "20:20:06" );
The system automatically handles ways to convert types based on
1) dynamic typing
2) dynamic typing based on a string name (grin)
3) static typing using generics at compile-time
So it's pretty cool.
And it works.
The only caveat with the system that I have is that coding adapters for each script type could be a pain in the butt. I can't think of any simple way to generate an adapter class in C# that automatically wraps around another class with the same interface. I could use reflection, I suppose, but I think that's a little overkill.