Garbage Collection pains... (Running CSharp in Torque)
by Vince Gee · 01/30/2012 (9:44 am) · 6 comments
Talk about pain, working through the interop between T3d and CSharp. First I had to deal with CSharp's garbage collection issues; it wanted to throw away buffers I had sent to torque. This was painful, but solvable after exploring the Marshal class thoroughly.
Then, came the pseudo garbage collection inside of Torque in regards to the string table. It wanted to throw away things I wanted to return to CSharp. I started to get the feeling that these two languages were never intended to talk together, but I kept plugging away at the problem.
After cleaning up the above two issues, I came into a situation with the callbacks. I would call the function via an extern to register my delegate for callback from the console only to get a protected memory error (basically, a generic error that tells you nothing other than either Torque tried to read memory it wasn't allowed to, or csharp tried to read memory it wasn't supposed to).
In the end, I believe it was trying to cast my CSharp delegate to a SimObject and thus was reading memory it wasn't supposed to, cause, a CSharp delegate is not a SimObject.
Round and round I went, looking at the python implementation and getting very discouraged. I started to realize why they had created a pyObject and what its purpose was, but I didn't like that solution.
So!
I had wired into the engine prior, when I was experimenting, a callback system to test calling CSharp from Torque. It didn't use any of the console functions, but the syntax was quite ugly. You would have to type something like dnEval(some csharp code); and it would make a callback to the CSharp and return the result.
This function set has worked flawlessly during my frustrating adventure through using the standard engine callback mechanism.
I thought, why not just inject into the engine after it started up, a wrapper for the functions flagged in the CSharp as functions exported into Torque.
Ok,
Guess I need to give a little detail on this,
Any function you write in CSharp that you want exported to the console of Torque you decorate with:
The format for that string is:
With the csharp being
So, When the AIPlayer::IsTargetInView function was called, it would route the call to the function in the CSharp and return the result.
So, now instead of calling the dll function "torque_exportstringcallback" with its parameters, I generate a torque script in memory like (properly generated based off the Function Decoration)
And pass it to the dll's Con::Evaluate function to register it with the engine.
And presto-chango, no more memory errors, and I can call member functions off of objects that are written in CSharp. (Generic functions not tied to a namespace work the same way.)
So,assuming the AIObject ID is 9393, I can say in TorqueScript:
I'm not totally happy yet, cause I want a similar function with variables, but that will come, at least now, I have a model I can follow which I know won't screw up the memory buffers.
p.s.: If you saw that async param, basically, it will start a new thread to run your CSharp function in, and immediately return an empty string to the Torque Server. (You just call back to the Torque Engine from inside the CSharp function if the process changes something, an async function could be like saving a players location to a database, etc. You really don't care if it didn't work, and you don't want the server to hang while you do it.)
Then, came the pseudo garbage collection inside of Torque in regards to the string table. It wanted to throw away things I wanted to return to CSharp. I started to get the feeling that these two languages were never intended to talk together, but I kept plugging away at the problem.
After cleaning up the above two issues, I came into a situation with the callbacks. I would call the function via an extern to register my delegate for callback from the console only to get a protected memory error (basically, a generic error that tells you nothing other than either Torque tried to read memory it wasn't allowed to, or csharp tried to read memory it wasn't supposed to).
In the end, I believe it was trying to cast my CSharp delegate to a SimObject and thus was reading memory it wasn't supposed to, cause, a CSharp delegate is not a SimObject.
Round and round I went, looking at the python implementation and getting very discouraged. I started to realize why they had created a pyObject and what its purpose was, but I didn't like that solution.
So!
I had wired into the engine prior, when I was experimenting, a callback system to test calling CSharp from Torque. It didn't use any of the console functions, but the syntax was quite ugly. You would have to type something like dnEval(some csharp code); and it would make a callback to the CSharp and return the result.
This function set has worked flawlessly during my frustrating adventure through using the standard engine callback mechanism.
public static extern void torque_exportstringcallback(MyStringCallback cb, string nameSpace, string funcName, IntPtr usage, int minArgs, int maxArgs);
I thought, why not just inject into the engine after it started up, a wrapper for the functions flagged in the CSharp as functions exported into Torque.
Ok,
Guess I need to give a little detail on this,
Any function you write in CSharp that you want exported to the console of Torque you decorate with:
[Torque_Decorations.Torque_Call_Back("AIPlayer", "AIPlayer", "IsTargetInView", "(%this, %obj, %tgt, %fov)", false, 4, 4)]The format for that string is:
public Torque_Call_Back(string torque_namespace,
string torque_classname,
string torque_function,
string torque_usage,
bool torque_async,
int minargs,
int maxargs)With the csharp being
public string AIPlayer_AIPlayer_IsTargetInView(string thisai, string ai, string target, string fov)
{
double ang = check2DAngletoTarget(ai, target);
double visleft = 360 - (double.Parse(fov) / 2);
double visright = double.Parse(fov) / 2;
if (ang > visleft || ang < visright)
return "true";
else
return "false";
}So, When the AIPlayer::IsTargetInView function was called, it would route the call to the function in the CSharp and return the result.
So, now instead of calling the dll function "torque_exportstringcallback" with its parameters, I generate a torque script in memory like (properly generated based off the Function Decoration)
function AIPlayer::IsTargetInView(%a1, %a2, %a3, %a4)
{
%evalstring = "AIPlayer_AIPlayer_IsTargetInView("" @ %a1 @ "","" @ %a2 @ "","" @ %a3 @ "","" @ %a4 @ "")";
%result =dnEval(%evalstring);
echo("Result of Query '" @ %result @ "'");
return %result;
}And pass it to the dll's Con::Evaluate function to register it with the engine.
And presto-chango, no more memory errors, and I can call member functions off of objects that are written in CSharp. (Generic functions not tied to a namespace work the same way.)
So,assuming the AIObject ID is 9393, I can say in TorqueScript:
9393.IsTargetInView(%a1, %a2, %a3, %a4)
I'm not totally happy yet, cause I want a similar function with variables, but that will come, at least now, I have a model I can follow which I know won't screw up the memory buffers.
p.s.: If you saw that async param, basically, it will start a new thread to run your CSharp function in, and immediately return an empty string to the Torque Server. (You just call back to the Torque Engine from inside the CSharp function if the process changes something, an async function could be like saving a players location to a database, etc. You really don't care if it didn't work, and you don't want the server to hang while you do it.)
About the author
www.winterleafentertainment.com
#2
If you want the callback function to return something then it can't be async.
01/30/2012 (8:15 pm)
You would just make a seperate console call from the C# back to your torquescript. If you want the callback function to return something then it can't be async.
#3
01/30/2012 (10:37 pm)
Sweet! Handy stuff to have around.
#4
01/31/2012 (12:43 am)
Awesome blog! Keep em coming! They are very interesting. 
Torque Owner Richard Ranft
Roostertail Games