Oh, the joys of converting Starter.FPS to C# (Sample Code)
by Vince Gee · 02/09/2012 (7:16 am) · 29 comments
Hey guys,
I've been busy toiling away at converting the Start.FPS to CSharp. I always try to measure the complexity of what I'm doing by the number of "WTF"'s per minute. Let me tell you I was hitting a WTF per second there for a while, but now, once again things are moving along.
So, if your curious what the cSharp code will look like, here is the Cheetah.cs file converted :)
I've been busy toiling away at converting the Start.FPS to CSharp. I always try to measure the complexity of what I'm doing by the number of "WTF"'s per minute. Let me tell you I was hitting a WTF per second there for a while, but now, once again things are moving along.
So, if your curious what the cSharp code will look like, here is the Cheetah.cs file converted :)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WinterLeaf.Classes;
namespace DNT_FPS_Demo_Game_Dll
{
public partial class Main : WinterLeaf.Classes.TorqueScriptTemplate
{
[Torque_Decorations.Torque_Call_Back("", "", "CheetahCar", "onAdd", "(%this, %obj)", false, 2, 2, 2800, false, Torque_Decorations.CallParentType.Begin)]
public string CheetahCar_onAdd(string thisobj, string obj)
{
WheeledVehicle.setWheelTire(obj, 0, "CheetahCarTire");
WheeledVehicle.setWheelTire(obj, 1, "CheetahCarTire");
WheeledVehicle.setWheelTire(obj, 2, "CheetahCarTireRear");
WheeledVehicle.setWheelTire(obj, 3, "CheetahCarTireRear");
// Setup the car with some tires & springs
for (int i = WheeledVehicle.getWheelCount(obj) - 1; i >= 0; i--)
{
WheeledVehicle.setWheelPowered(obj, i, true);
WheeledVehicle.setWheelSpring(obj, i, "CheetahCarSpring");
}
// Steer with the front tires
WheeledVehicle.setWheelSteering(obj, 0, 1);
WheeledVehicle.setWheelSteering(obj, 1, 1);
// Add tail lights
Torque_Class_Helper tc = new Torque_Class_Helper("PointLight", "");
tc.Props.Add("radius", "1");
tc.Props.Add("isEnabled", "0");
tc.Props.Add("color", @"""1 0 0.141176 1""");
tc.Props.Add("brightness", "2");
tc.Props.Add("castShadows", "1");
tc.Props.Add("priority", "1");
tc.Props.Add("animate", "0");
tc.Props.Add("animationPeriod", "1");
tc.Props.Add("animationPhase", "1");
tc.Props.Add("flareScale", "1");
tc.Props.Add("attenuationRatio", @"""0 1 1""");
tc.Props.Add("shadowType", @"""DualParaboloidSinglePass""");
tc.Props.Add("texSize", "512");
tc.Props.Add("overDarkFactor", @"""2000 1000 500 100""");
tc.Props.Add("shadowDistance", "400");
tc.Props.Add("shadowSoftness", "0.15");
tc.Props.Add("numSplits", "1");
tc.Props.Add("logWeight", "0.91");
tc.Props.Add("fadeStartDistance", "0");
tc.Props.Add("lastSplitTerrainOnly", "0");
tc.Props.Add("representedInLightmap", "0");
tc.Props.Add("shadowDarkenColor", @"""0 0 0 -1""");
tc.Props.Add("includeLightmappedGeometryInShadow", "0");
tc.Props.Add("rotation", @"""1 0 0 0 """);
tc.Props.Add("canSave", "1");
tc.Props.Add("canSaveDynamicFields", "1");
tc.Props.Add("splitFadeDistances", @"""10 20 30 40""");
string rightbrakelight = tc.Create(m_ts).ToString();
Con.SetVar(obj + ".rightBrakeLight", rightbrakelight);
string leftbrakelight = tc.Create(m_ts).ToString();
Con.SetVar(obj + ".leftBrakeLight", leftbrakelight);
// Mount a ShapeBaseImageData
ShapeBase.mountImage(obj, "TurretImage", Con.GetVarInt(thisobj + ".turretSlot"), true, "");
// Mount the brake lights
Vehicle.mountObject(obj, rightbrakelight, Con.GetVarInt(thisobj + ".rightBrakeSlot"));
Vehicle.mountObject(obj, leftbrakelight, Con.GetVarInt(thisobj + ".leftBrakeSlot"));
return "";
}
[Torque_Decorations.Torque_Call_Back("", "", "CheetahCar", "onRemove", "(%this, %obj)", false, 2, 2, 2800, false, Torque_Decorations.CallParentType.Begin)]
public string CheetahCar_onRemove(string thisob, string obj)
{
if (Con.isObject(Con.GetVarString(obj + ".rightBrakeLight")))
Con.Eval(obj + ".rightBrakeLight.delete();");
if (Con.isObject(Con.GetVarString(obj + ".leftBrakeLight")))
Con.Eval(obj + ".leftBrakeLight.delete();");
if (Con.isObject(Con.GetVarString(obj + ".turret")))
Con.Eval(obj + ".turret.delete();");
return "";
}
[Torque_Decorations.Torque_Call_Back("", "", "", "serverCmdtoggleBrakeLights", "(%client)", false, 1, 1, 2800, false)]
public string CheetahCar_serverCmdtoggleBrakeLights(string client)
{
string player = Con.GetVarString(client + ".player");
//Remember to pay attention to what type of object your looking at.
string car = ShapeBase.getControlObject(player).ToString();
if (Con.GetClassName(car) == "WheeledVehicle")
{
if (Con.GetVarInt(car + ".rightBrakeLight.isEnabled") == 1)
{
Con.Eval(car + ".rightBrakeLight.setLightEnabled(0);");
Con.Eval(car + ".leftBrakeLight.setLightEnabled(0);");
}
else
{
Con.Eval(car + ".rightBrakeLight.setLightEnabled(1);");
Con.Eval(car + ".leftBrakeLight.setLightEnabled(1);");
}
}
return "";
}
// Callback invoked when an input move trigger state changes when the CheetahCar
// is the control object
[Torque_Decorations.Torque_Call_Back("", "", "CheetahCar", "onTrigger", "(%%this, %obj, %index, %state)", false, 4, 4, 2800, false)]
public string CheetahCar_onTrigger(string thisobj, string obj, string index, string state)
{
// Pass trigger states on to TurretImage (to fire weapon)
switch (int.Parse(index))
{
case 0:
ShapeBase.setImageTrigger(obj, Con.GetVarInt(thisobj + ".turretSlot"), (state == "1" ? true : false));
break;
case 1:
ShapeBase.setImageAltTrigger(obj, Con.GetVarInt(thisobj + ".turretSlot"), (state == "1" ? true : false));
break;
}
return "";
}
[Torque_Decorations.Torque_Call_Back("", "", "CheetahCar", "onMount", "(%this, %obj, %slot)", false, 3, 3, 2800, false)]
public string CheetahCar_onMount(string thisobj, string obj, string slot)
{
// Load the gun
ShapeBase.setImageAmmo(obj, int.Parse(slot), true);
return "";
}
}
}About the author
www.winterleafentertainment.com
#2
You say "What The Frank" too?
@Thomas,
I did not realize it would have the same extension until you mentioned it. That is really funny!
@Vince,
So are you writing code to do the conversion from TS to C#, or are you doing it by hand?
02/09/2012 (11:25 pm)
@Vince,You say "What The Frank" too?
@Thomas,
I did not realize it would have the same extension until you mentioned it. That is really funny!
@Vince,
So are you writing code to do the conversion from TS to C#, or are you doing it by hand?
#3
Ahh, hand conversion. We started at the bottom of the scriptexec and we are working our way up through the server side torquescript files one at a time. We convert it, then it goes to unit testing, any console externs added are tested, etc.
Yeah, our list of externs are growing. My idea was that if we did the starter.fps, we would nail 80 percent of the console commands and 100 percent of the most commonly used.
Once it hits alpha or beta, we will go back and finish the rest of the externs. It's quite time consuming, but you wouldn't believe the performance gains we have received. Our QA team (right now one guy... but we call him a team:)) keeps saying, "This is torque??? right?"
Once we are able to determine exactly why we are getting such performance gains (Quite noticeable) I'll put a blog up about it.
I know some of the gain is due to the fact that our code compiles to a native DLL, but the rest of the gains aren't explainable. Torque seems to really fly.
I've been so excited about how things have been progressing, that I'm finding I'm working 10 hours or more a day on this thing.
02/10/2012 (4:40 am)
@Frank,Ahh, hand conversion. We started at the bottom of the scriptexec and we are working our way up through the server side torquescript files one at a time. We convert it, then it goes to unit testing, any console externs added are tested, etc.
Yeah, our list of externs are growing. My idea was that if we did the starter.fps, we would nail 80 percent of the console commands and 100 percent of the most commonly used.
Once it hits alpha or beta, we will go back and finish the rest of the externs. It's quite time consuming, but you wouldn't believe the performance gains we have received. Our QA team (right now one guy... but we call him a team:)) keeps saying, "This is torque??? right?"
Once we are able to determine exactly why we are getting such performance gains (Quite noticeable) I'll put a blog up about it.
I know some of the gain is due to the fact that our code compiles to a native DLL, but the rest of the gains aren't explainable. Torque seems to really fly.
I've been so excited about how things have been progressing, that I'm finding I'm working 10 hours or more a day on this thing.
#4
I am very intrigued about the performance gains you mention ... please keep us updated.
Thanks
02/10/2012 (5:57 am)
Poor "frank" ... he always get blamed. I am very intrigued about the performance gains you mention ... please keep us updated.
Thanks
#5
*sigh* LOL
in all honesty, IMO this was a needed step for our development of a MMO platform, but we seem to be building a FPS monster.
the performance gains are huge... HUGE! Not only is Torque running smoother, its performing faster, the overhead seems to be goin down a bit and the load times are becoming insanely fast.
This is definitely something that we are VERY proud of.
Once this is done, we start unloading Torque from becoming such a work horse. Anyone feeling up to the task of keeping up? :)
02/10/2012 (6:04 am)
yeah, one of the many hats i wear... QA being one of them. *sigh* LOL
in all honesty, IMO this was a needed step for our development of a MMO platform, but we seem to be building a FPS monster.
the performance gains are huge... HUGE! Not only is Torque running smoother, its performing faster, the overhead seems to be goin down a bit and the load times are becoming insanely fast.
This is definitely something that we are VERY proud of.
Once this is done, we start unloading Torque from becoming such a work horse. Anyone feeling up to the task of keeping up? :)
#6
02/10/2012 (8:19 am)
Any way you could post said benchmarks?
#7
02/10/2012 (8:24 am)
absolutely - once this is completed. No use in posting a benchmark before then.
#8
02/10/2012 (9:27 am)
I'm now quite curious where the performance gains are coming from. The traditional view has been that scripting overhead should be fairly light, since performance critical portions of the code should already be implemented in C++, and I hadn't noticed significant overhead from scripting in my own collections of profiling data - but I hadn't been trying to profile scripting specifically either. If you're seeing these improvements even in Starter.FPS, it would seem to contradict that hypothesis. Does anyone know if the script interpreter even has the profiler attached to it?
#9
I'm suspecting that it has something to do with the stringtable, well, since I'm now starting to remove a great deal of torque script, that means less data stored in the string table,
which in turn means faster lookups?
I dunno, havn't dug in deep enough yet.
Does a functions local variables use space in the stringtable?
Does this fragment the stringtable?
dunno?
These are questions I'm trying to answer.
02/10/2012 (9:37 am)
@Thomas,I'm suspecting that it has something to do with the stringtable, well, since I'm now starting to remove a great deal of torque script, that means less data stored in the string table,
which in turn means faster lookups?
I dunno, havn't dug in deep enough yet.
Does a functions local variables use space in the stringtable?
Does this fragment the stringtable?
dunno?
These are questions I'm trying to answer.
#10
One thing I did to my codebase was simply find and replace every instance of StringTable->insert("") with a global variable that did the insertion once (and called it something like gEmptyStringTableEntry), which eliminated a large number of redundant calls. If we then assume a low redundancy factor in the number of insertions, the number of times StringTable->insert appears in the code base should provide a lower-bound on N. Find-in-files on my codebase reveals this count to be over 500. Further find-in-files reveal that the number of these which are inserting a constant expression (StringTable->insert(" or StringTable->insert( ") is less than 200, which leaves a remaining 300 that could be expected to have multiple non-redundant insertions.
This could be mitigated in several ways - simply increasing the capacity of the Hashtable to 65536 would probably go a long to alleviating capacity problems. Replacing the linked-list buckets with a red-black tree, or similar, would reduce our upper-bound dependence on N to O(log N).
Another strategy is to implement a multi-layer Hashtable based on an entropy-encoding scheme. The top-level table is still 256-bytes, mapped by the first character of the string to be stored. Null, alpha-numerics, and $ would be mapped to their respective ascii-valued slot in the table, which redirects to the second level tables, and everything else is hashed into the 43 slots between Null and the digit 0. I'm fairly sure local variables have their own storage/lookup mechanism in the StringStack (and who even knows what needs fixing there), but if they didn't we could % to the list of characters who get a second-level-table.
Since most of the StringTable consists of variable names and textual data, this ought to improve the performance of the most used portions of the table, particularly if the buckets in the second-level-tables are replaced with trees as I suggested earlier. But, as they say, premature optimization is the root of all evil, so profiling first would be in our best interests.
[edit]
But quite honestly, looking at your C# code, I don't see why it would improve on the StringTable usage of the existing TorqueScript code, given the frequency with which you rely on property names and Console executions that, ultimately, still have to go through the lookup.
02/10/2012 (10:28 am)
Ah, this is entirely possible. The StringTable is used for a lot of stuff, and I suspect the implementation could be improved significantly. For starters, the Hashtable only has 256 entries, and the globals used for $pref::* are enough to degrade the O(1) performance of that on their own. Secondarily, it appears the buckets put in place for dealing with overflow are linked-lists, which means the performance of the StringTable is really more like O(N), when N >> 256. One thing I did to my codebase was simply find and replace every instance of StringTable->insert("") with a global variable that did the insertion once (and called it something like gEmptyStringTableEntry), which eliminated a large number of redundant calls. If we then assume a low redundancy factor in the number of insertions, the number of times StringTable->insert appears in the code base should provide a lower-bound on N. Find-in-files on my codebase reveals this count to be over 500. Further find-in-files reveal that the number of these which are inserting a constant expression (StringTable->insert(" or StringTable->insert( ") is less than 200, which leaves a remaining 300 that could be expected to have multiple non-redundant insertions.
This could be mitigated in several ways - simply increasing the capacity of the Hashtable to 65536 would probably go a long to alleviating capacity problems. Replacing the linked-list buckets with a red-black tree, or similar, would reduce our upper-bound dependence on N to O(log N).
Another strategy is to implement a multi-layer Hashtable based on an entropy-encoding scheme. The top-level table is still 256-bytes, mapped by the first character of the string to be stored. Null, alpha-numerics, and $ would be mapped to their respective ascii-valued slot in the table, which redirects to the second level tables, and everything else is hashed into the 43 slots between Null and the digit 0. I'm fairly sure local variables have their own storage/lookup mechanism in the StringStack (and who even knows what needs fixing there), but if they didn't we could % to the list of characters who get a second-level-table.
Since most of the StringTable consists of variable names and textual data, this ought to improve the performance of the most used portions of the table, particularly if the buckets in the second-level-tables are replaced with trees as I suggested earlier. But, as they say, premature optimization is the root of all evil, so profiling first would be in our best interests.
[edit]
But quite honestly, looking at your C# code, I don't see why it would improve on the StringTable usage of the existing TorqueScript code, given the frequency with which you rely on property names and Console executions that, ultimately, still have to go through the lookup.
#11
---
are you planning on a full resource or maybe a product of some type that others could use? i would love to see something like that, it would give a nice and nifty (and better imho) game dev plateform for .net dev's instead of xna.
good job so far mate. looking forward to your next post update.
02/10/2012 (12:59 pm)
i have to say this is rather cool. Will be interesting to see a vid with a game running on it through C# instead of TScript. ---
are you planning on a full resource or maybe a product of some type that others could use? i would love to see something like that, it would give a nice and nifty (and better imho) game dev plateform for .net dev's instead of xna.
good job so far mate. looking forward to your next post update.
#12
02/10/2012 (1:07 pm)
@Roland - this and the MMO platform that we plan on building off of this will both be products that we are looking to make available for purchase
#13
02/10/2012 (1:32 pm)
Cool beans Paul. Will be watching your progress for sure.
#14
02/10/2012 (11:05 pm)
One of the big expenses of the TorqueScript system is that it has to convert everything to/from strings. This can really add up over time. If your C# bindings are using "native" types with no string conversions it can really boost performance =)
#15
Out of curiosity. Could there be performance gains due to multiple threads being implemented?
Another possibility is that the C# compiler can optimize things that TS could not. Perhaps there is an opportunity there for determining which functions could be sped up, converted to C++, or reworked.
This is really interesting Vince. Keep up the good work!
02/11/2012 (3:20 am)
Maybe there is an optimization brewing for T3D is all of this. I had understood from other posts that there was not much you could do about TS speed due to string table lookup. Perhaps there is a way to optimize that portion by looking at it a different way.Out of curiosity. Could there be performance gains due to multiple threads being implemented?
Another possibility is that the C# compiler can optimize things that TS could not. Perhaps there is an opportunity there for determining which functions could be sped up, converted to C++, or reworked.
This is really interesting Vince. Keep up the good work!
#16
02/11/2012 (10:41 am)
Interesting stuff Vince! Just out of curiosity, can you tell me what compiler you're using? Is it all about Visual Studio or are there any other C# compilers out there?
#17
The nice thing about Mono is that it will run on various OS's. I haven't gotten far enough along to experiment building on Linux yet, nor do I have a linux build of T3D which I could test with. So, once I get the Microsoft build done, I will go exploring into the Mono realm.
02/11/2012 (6:39 pm)
I'm aware of 3 compilers for dot.net, their is the Microsoft one (which is free) there is mono, and then there is another one like tiny dot.net. I'm using the the Microsoft one, but I'm not using any special commands that wouldn't work in Mono, not sure about the last compiler. The nice thing about Mono is that it will run on various OS's. I haven't gotten far enough along to experiment building on Linux yet, nor do I have a linux build of T3D which I could test with. So, once I get the Microsoft build done, I will go exploring into the Mono realm.
#18
02/11/2012 (8:27 pm)
Mono works on OS X as well, so if your changes will work with 1.0.1 it could be tested there
#19
Thomas - I was about to suggest implementing an auto-resizing string table, until I realised that all the StringTableEntries that objects store are just pointers :P. I guess it comes down to each particular project to resize the string table at compile-time to meet the requirements of their scripts, after they determine how many individual strings they typically use.
02/13/2012 (10:59 pm)
I haven't used C#, so I'm not sure what its benefits are over TS - but it looks like a staggering abount of work! Respect ;P.Thomas - I was about to suggest implementing an auto-resizing string table, until I realised that all the StringTableEntries that objects store are just pointers :P. I guess it comes down to each particular project to resize the string table at compile-time to meet the requirements of their scripts, after they determine how many individual strings they typically use.
#20
Daniel/everyone: on further investigation, it appears that the StringTable knows to resize itself, and I'm not seeing any significant footprint from it in my profiling dumps.
02/13/2012 (11:27 pm)
Vince, can you provide a set profiler dumps, after instrumenting the Torquescript exec function (and its equivalent in the C# version), the StringTable and StringTableStack, and the postEvent/advanceToTime functions in SimManager, comparing your performance gains between the Torquescript and C# versions? I'm not seeing much that would suggest any obvious source of performance improvements in those parts of the system. Far and away the bulk of my time is spent, in rendering, and what little I see of non-rendering code appearing above 1% of total time are all string comparisons taking place within GFXDevice::updateStates.Daniel/everyone: on further investigation, it appears that the StringTable knows to resize itself, and I'm not seeing any significant footprint from it in my profiling dumps.

Torque 3D Owner Thomas -elfprince13- Dickerson