SCXML in Torque
by Greg Beauchesne · 07/20/2009 (11:38 pm) · 5 comments
Hello; I'm Greg Beauchesne. I guess you could say I'm new here, although I'm not new to Torque development. I've worked a great deal with TGE at work, so I'm quite familiar with the underlying code.
Having recently purchased TGB Pro for personal use, it occurred to me that there didn't seem to be a good state machine implementation for what I wanted to do.
At work, I also use Qt, and so I keep up on the various developments that go on there. One such development is this one, detailing their efforts to add support for SCXML to Qt. SCXML is the W3C's effort to come up with a standard state machine language using XML. This intrigued me, as it seems like it could be implemented for Torque to great effect.
SCXML is based on Harel statechart semantics. Harel statecharts are a visual representation of state machines that add some nice features to reduce the complexity of a traditional state machine, which normally requires that every combination of parameters gets its own unique state. Here are some of the things found in Harel statecharts and supported by SCXML:
SCXML is not yet an official W3C recommendation. The SCXML specification is still evolving, so some of the details are not nailed down. Caveat emptor.
So, with that in mind, I set off putting together an SCXML state machine for Torque.
The first problem was the XML part. Being a W3C project, it makes use of XML namespaces, and Torque's XML parser didn't appear to have namespace support. But furthermore, I didn't want to read XML in Torque anyway. There's a lot of overhead in that, and if I was reading Torque script code out of it, it would mean I would have to use eval(). I have a strong aversion to eval()-type constructs in any scripting language unless absolutely necessary, as it means we are wasting time compiling things that in many cases could be precompiled. As far as I know, Torque does not have a means of precompiling a piece of dynamic code and then saving it for later short of writing it out to a disk file and then exec()ing it.
So, what was the solution? Script code generation. The language? XSLT. XSLT is another W3C language, and this one's an official recommendation. XSLT is XML, and it specializes in transforming XML to XML or XML to text. It's supported by both Internet Explorer and Firefox for rendering XML documents as HTML. In fact, if you're running Windows and have one of Microsoft's more recent MSXML packages installed, you probably already have an XSLT processor installed without even realizing it (go to a command prompt and type "msxsl" -- this is the same engine Internet Explorer uses).
In any case, for this to work, I would need to write an XSLT script that could convert SCXML to TorqueScript. There were a few bumps, but overall it was pretty straightforward.
The next problem would be actually implementing SCXML properly. Thankfully, the specification has an implementation algorithm right at the bottom. The algorithm in the spec assumes, however, that the state machine is running in a standalone environment, which is no good for Torque, because there happens to be a game running at the same time which can't be held back. So I had to do some rearranging to make it suitable for in-game use.
Initially I created an implementation of SCXML using only TorqueScript. The XSLT script was responsible for dividing the state and transition data into many different scripted functions that had precalculated as much data as possible so that it wouldn't have to be processed by the script itself during the main execution phase. The scripted implementation worked wonderfully, with full support for compound and parallel states, except for the fact that it ended up being far too slow. It actually probably could have been fast enough to be useful, but it turns out that calling scripted functions in Torque has a severe overhead penalty (more on that in the future) and inlining a lot of functions via the magic of copy and paste did not help enough to overcome this.
Which brings me to today. I'm now working on a C++ version of the state machine implementation that I'm hoping will scream compared to the scripted one. The way it works is that you build up the state machine definition using Torque objects, and then when the first instance of the machine is created, the state machine is compiled into an optimized form. The C++ implementation currently supports parallel and compound states, as the scripted one did, and soon I'll add support for history states.
Initial results look promising. Having been burned above by the TorqueScript function call overhead, I've put additional effort into making sure the scripted callbacks aren't called upon more than they have to be. All of the multi-state logic and such is handled by the C++ code, and the only thing scripts do is handle the state entry and exit code, transition code, and transition guard conditions.
----
There are some other benefits to using SCXML. As I mentioned above, I use XSLT to translate an SCXML file into TorqueScript. Like many other scripted languages, XSLT has the ability for scripts to include one another. XSLT is also a rule-based language. That makes it very easy to add new constructs that might be useful. For example, in my implementation, I have a core XSLT script which only converts the SCXML into TorqueScript and does nothing else. But then I have a second script which includes the first and adds some rules that A) turn the script into a full-on behavior script that can be used in the TGB editor, and B) scan the file for all sprite animations used and automatically add behavior template entries for them.
Second, as I also mentioned above, SCXML is an ongoing W3C project. Although not a recommendation yet, when it does become a recommendation, you can probably expect many people to create support tools for it. The Apache Commons project would be a good example of what's happening now. I also understand that IBM has created an SCXML visual statechart editor for Rational Architect (though I don't think that's free), and that Eclipse may or may not have some SCXML support.
I have also examined the possibility of using a free UML tool with state chart support such as ArgoUML or StarUML to visually create state machines that could be used in Torque. If I'm not mistaken, they both use some form of XML as their project storage format, and that could mean XSLT to the rescue once again. Nothing yet, though -- the core stuff first.
----
I will say up front that my intention is to eventually sell this as an add-on in the GG store once I nail down some of the finer points, but either way, I'm looking to see what kind of interest others might have in this little project. Being in C++ does mean it will require a Torque source code license to use it, at least until/unless that SimComponent stuff I'm hearing about comes to fruition. I have written it for TGB, but I'm hoping it will be usable with any of the Torque variants.
Having recently purchased TGB Pro for personal use, it occurred to me that there didn't seem to be a good state machine implementation for what I wanted to do.
At work, I also use Qt, and so I keep up on the various developments that go on there. One such development is this one, detailing their efforts to add support for SCXML to Qt. SCXML is the W3C's effort to come up with a standard state machine language using XML. This intrigued me, as it seems like it could be implemented for Torque to great effect.
SCXML is based on Harel statechart semantics. Harel statecharts are a visual representation of state machines that add some nice features to reduce the complexity of a traditional state machine, which normally requires that every combination of parameters gets its own unique state. Here are some of the things found in Harel statecharts and supported by SCXML:
- Compound states - States within states. Transitions out from a parent state also apply to all its children. For example, consider most platformer games: Characters can only jump when they're on the ground, and not while they're in the air. But on the ground, a character could be walking, running, crouching, or attacking, and in the air, a character could be rising, falling, or performing a mid-air attack. With a compound state, all of these can be grouped into an "on the ground" or "in the air" state, and then if a "jump" transition were placed in the "on the ground" state, it would mean that the character could jump from on the ground regardless of what he or she was doing.
- Parallel states - Not parallel in the sense of multithreaded, but parallel in that it's basically like multiple state machines running at the same time and reacting to the same events. This is good for splitting the logic from the animation in the same state machine, or controlling multiple animated sprites and having them act as one.
- History pseudo-states - The state machine itself can automatically keep track of the state or states it was in in a specific sub-section of the machine and then return to it later.
SCXML is not yet an official W3C recommendation. The SCXML specification is still evolving, so some of the details are not nailed down. Caveat emptor.
So, with that in mind, I set off putting together an SCXML state machine for Torque.
The first problem was the XML part. Being a W3C project, it makes use of XML namespaces, and Torque's XML parser didn't appear to have namespace support. But furthermore, I didn't want to read XML in Torque anyway. There's a lot of overhead in that, and if I was reading Torque script code out of it, it would mean I would have to use eval(). I have a strong aversion to eval()-type constructs in any scripting language unless absolutely necessary, as it means we are wasting time compiling things that in many cases could be precompiled. As far as I know, Torque does not have a means of precompiling a piece of dynamic code and then saving it for later short of writing it out to a disk file and then exec()ing it.
So, what was the solution? Script code generation. The language? XSLT. XSLT is another W3C language, and this one's an official recommendation. XSLT is XML, and it specializes in transforming XML to XML or XML to text. It's supported by both Internet Explorer and Firefox for rendering XML documents as HTML. In fact, if you're running Windows and have one of Microsoft's more recent MSXML packages installed, you probably already have an XSLT processor installed without even realizing it (go to a command prompt and type "msxsl" -- this is the same engine Internet Explorer uses).
In any case, for this to work, I would need to write an XSLT script that could convert SCXML to TorqueScript. There were a few bumps, but overall it was pretty straightforward.
The next problem would be actually implementing SCXML properly. Thankfully, the specification has an implementation algorithm right at the bottom. The algorithm in the spec assumes, however, that the state machine is running in a standalone environment, which is no good for Torque, because there happens to be a game running at the same time which can't be held back. So I had to do some rearranging to make it suitable for in-game use.
Initially I created an implementation of SCXML using only TorqueScript. The XSLT script was responsible for dividing the state and transition data into many different scripted functions that had precalculated as much data as possible so that it wouldn't have to be processed by the script itself during the main execution phase. The scripted implementation worked wonderfully, with full support for compound and parallel states, except for the fact that it ended up being far too slow. It actually probably could have been fast enough to be useful, but it turns out that calling scripted functions in Torque has a severe overhead penalty (more on that in the future) and inlining a lot of functions via the magic of copy and paste did not help enough to overcome this.
Which brings me to today. I'm now working on a C++ version of the state machine implementation that I'm hoping will scream compared to the scripted one. The way it works is that you build up the state machine definition using Torque objects, and then when the first instance of the machine is created, the state machine is compiled into an optimized form. The C++ implementation currently supports parallel and compound states, as the scripted one did, and soon I'll add support for history states.
Initial results look promising. Having been burned above by the TorqueScript function call overhead, I've put additional effort into making sure the scripted callbacks aren't called upon more than they have to be. All of the multi-state logic and such is handled by the C++ code, and the only thing scripts do is handle the state entry and exit code, transition code, and transition guard conditions.
----
There are some other benefits to using SCXML. As I mentioned above, I use XSLT to translate an SCXML file into TorqueScript. Like many other scripted languages, XSLT has the ability for scripts to include one another. XSLT is also a rule-based language. That makes it very easy to add new constructs that might be useful. For example, in my implementation, I have a core XSLT script which only converts the SCXML into TorqueScript and does nothing else. But then I have a second script which includes the first and adds some rules that A) turn the script into a full-on behavior script that can be used in the TGB editor, and B) scan the file for all sprite animations used and automatically add behavior template entries for them.
Second, as I also mentioned above, SCXML is an ongoing W3C project. Although not a recommendation yet, when it does become a recommendation, you can probably expect many people to create support tools for it. The Apache Commons project would be a good example of what's happening now. I also understand that IBM has created an SCXML visual statechart editor for Rational Architect (though I don't think that's free), and that Eclipse may or may not have some SCXML support.
I have also examined the possibility of using a free UML tool with state chart support such as ArgoUML or StarUML to visually create state machines that could be used in Torque. If I'm not mistaken, they both use some form of XML as their project storage format, and that could mean XSLT to the rescue once again. Nothing yet, though -- the core stuff first.
----
I will say up front that my intention is to eventually sell this as an add-on in the GG store once I nail down some of the finer points, but either way, I'm looking to see what kind of interest others might have in this little project. Being in C++ does mean it will require a Torque source code license to use it, at least until/unless that SimComponent stuff I'm hearing about comes to fruition. I have written it for TGB, but I'm hoping it will be usable with any of the Torque variants.
About the author
#2
If you pull it off I'm sure there will be plenty of interest.
07/21/2009 (3:02 am)
Interesting project. I chose SCXML for my state machine too. I've been using embedded Mono for my "scripting" platform in Torque, since TS is so slow, so it was fairly painless. Your XSLT process sounds like a good solution; probably the only one that really makes sense for a TS solution. Though the C++ solution will definitely be the way to go there. Anything complex enough to warrant a full-fledged state machine is probably too much for TS.If you pull it off I'm sure there will be plenty of interest.
#3
Gerald: Hmm, throwing in a different scripting engine (well, a CLR implementation) sounds like it would be a lot of work all on its own. You can correct me if I'm wrong on this (maybe you have a hybrid system), but even if it's faster, it seems like you're discarding more than a few existing scripts (and compatibility with the various TS-based editing tools). Still, that's great if you've done it; you at least get a wide codebase in exchange.
One of the things I had wanted for this (and which doesn't seem possible yet) was to have something that could work with the non-pro TGB. I've abandoned that part for now, but I still wanted to keep it TS-compatible.
07/21/2009 (4:02 pm)
William: That's cool. If you've already got something that does what you need (and put any significant amount of effort into) then you should stick with it. I don't expect anyone to change horses mid-stream if they're already in the middle of working on a project.Gerald: Hmm, throwing in a different scripting engine (well, a CLR implementation) sounds like it would be a lot of work all on its own. You can correct me if I'm wrong on this (maybe you have a hybrid system), but even if it's faster, it seems like you're discarding more than a few existing scripts (and compatibility with the various TS-based editing tools). Still, that's great if you've done it; you at least get a wide codebase in exchange.
One of the things I had wanted for this (and which doesn't seem possible yet) was to have something that could work with the non-pro TGB. I've abandoned that part for now, but I still wanted to keep it TS-compatible.
#4
Yes, it's a hybrid system. It still runs TorqueScript as well. Basically all ConsoleObjects have a TS interface and a .NET interface and they can be accessed from either. Integrating the Mono engine was fairly simple, porting over some of the existing scripts to .NET has been the hardest part, but we can do that selectively.
This is mostly for internal use, as we wanted a system for fast gameplay prototyping, and to be able to make the games as moddable as possible. TorqueScript is okay for simple stuff, but it's utility for anything non-trivial is severely limited due to the overall speed and the fact that everything is pass-by-string-value. Any meaningful AI scripting had to be done almost entirely in C++, which makes prototyping and tweaking quite time consuming.
It started out just as a "what if", but it surprised me how simple and intuitive the embedded Mono interface was, and how powerful it could be.
It took just a couple of days to implement an SCXML-based state machine framework that is fast and cross-platform out of the box, for instance.
We've also been leveraging .NET in developing a lot of custom tools, and I think that has been an even greater advantage.
07/21/2009 (6:19 pm)
@Greg,Yes, it's a hybrid system. It still runs TorqueScript as well. Basically all ConsoleObjects have a TS interface and a .NET interface and they can be accessed from either. Integrating the Mono engine was fairly simple, porting over some of the existing scripts to .NET has been the hardest part, but we can do that selectively.
This is mostly for internal use, as we wanted a system for fast gameplay prototyping, and to be able to make the games as moddable as possible. TorqueScript is okay for simple stuff, but it's utility for anything non-trivial is severely limited due to the overall speed and the fact that everything is pass-by-string-value. Any meaningful AI scripting had to be done almost entirely in C++, which makes prototyping and tweaking quite time consuming.
It started out just as a "what if", but it surprised me how simple and intuitive the embedded Mono interface was, and how powerful it could be.
It took just a couple of days to implement an SCXML-based state machine framework that is fast and cross-platform out of the box, for instance.
We've also been leveraging .NET in developing a lot of custom tools, and I think that has been an even greater advantage.
#5
Yeah, Torque's pass-everything-by-string approach isn't so hot. Having worked with the C API for Tcl in the past, it was initially kind of surprising to me that it didn't do much in the way of data type retention. Regarding the overall speed, though, I think I've found a few correctable bottlenecks...
07/21/2009 (7:52 pm)
Gerald: I see -- nice!Yeah, Torque's pass-everything-by-string approach isn't so hot. Having worked with the C API for Tcl in the past, it was initially kind of surprising to me that it didn't do much in the way of data type retention. Regarding the overall speed, though, I think I've found a few correctable bottlenecks...
Associate William Lee Sims
Machine Code Games