GameMonkey Script

GameMonkey Script Forums
It is currently Wed Jan 23, 2019 2:31 pm

All times are UTC




Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: Execution Recursion
PostPosted: Mon Apr 04, 2011 8:17 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
The Goal:
Alter my event and callback system to properly allow self-triggering script nodes while maintaining time sensitive event data.

The Problem:
In my program, a visual scripting engine generates GameMonkey code. In many cases, the generated code registers script callbacks with C++ objects. A simple example of this would be the following:
Code:
cppObj.registerForEvent(threadId(),someSignal);
block(someSignal);
... do more stuff ...


So the thread that this code is running in blocks to wait for the cppObj to send back the registered signal. At any point, there are several blocked "event handler" threads sitting around, waiting for C++ to get their attention. And each of their "do more stuff" sections includes more registrations, and more function calls that may trigger other events. All of the registrations spin up in their own threads so as not to impede program flow.

Now, inside this system it is occasionally necessary to handle time-sensitive event data. That is to say, a signal from C++ has to be handled IMMEDIATELY. You can't just wait for the next program loop to call Execute() on the gmMachine that I'm using, because perhaps the C++ will be signalling two different copies of the same event in a row, and so the time-sensitive data from the first would be lost if it wasn't handled right then (they would both show the second event's data).

As I see it, there are two different possible ways to resolve this issue:
1. Pass the time-sensitive data into the event handler thread (we do have the threadID, after all)
2. Force the gmMachine to awaken and run the signalled thread before the second event is set up and signalled.

If the first method is possible, it would resolve not only my current concern, but some other potential problems in the future. I have no clue how to do it, though. Is it possible to pass data (an unspecified gmVariable) into a blocked thread? How about a blocked thread that has just been signalled? How would I write the code for that thread to access / manipulate the newly passed data? All this keeping in mind that in THIS scenario the gmMachine might not be ticked for a while, and there might be multiple threads all needing their own unique piece of time-specific event data.

If there's a solution for method 1, then question over!

Otherwise...

I've taken a stab at implementing method 2, but it doesn't work perfectly. My C++ code looks kind of like this:
Code:
gmMachine.Signal(theThreadID,theSignal);
gmMachine.Execute(0);

This will work provided I enforce one specific condition: a C++ function called from GameMonkey cannot itself directly trigger another event. I don't know why this is, but thus far it has proven true. I may just not be seeing the errors.

I can't be 100% sure, but it looks like what happens is that the VM stack pointer gets messed up by calling Execute() a second time, while another copy of Execute() is already going. The GameMonkey function that called the script doesn't get a chance to exit, so its values get stomped on, and I end up hitting a NULL instruction that crashes the VM.

I've read the code inside Execute(), and what I'm seeing is that a signalled thread will never wake up until another call to Execute() is made. That is, Execute first wakes up threads, then runs active threads, then exits. My understanding of the yield() function is that it skips out of the current thread, which will also not be hit again until another Execute() call gets made. That makes perfect sense provided that you want to use yield() for its announced purpose, which is to keep GameMonkey from doing too much in any one cycle through a program loop. In my case, though, I want GameMonkey to keep running until all threads are dead or blocked. In my system, that means that all program events have been handled, and it's safe to run on to the next cycle of the program loop.

In order to make things work the way I want, I might change the whole threading system (yuck). I could change it to keep live threads in a queue. yield() would just push things to the back of the queue. And Execute() would just keep pulling the front item off of the queue until nothing was left. signal() would move threads to the back of the queue also. And then I'd have to add a function that gives the gmMachine permission to resume processing as though it had received a GM_OK, even before a bound C++ function had actually finished, so that it could handle multiple signals inside a single bound function call...

Or I could just make Execute() loop until there were no live threads left... but I'd still have to deal with handling a signal in the middle of a bound function.

Again, yuck.

So is #1 possible? If not, am I even vaguely going about #2 correctly?


Top
 Profile  
Reply with quote  
 Post subject: Re: Execution Recursion
PostPosted: Mon Apr 04, 2011 11:05 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
Thought of the moment on Method #1:

What if the function were declared with x+1 parameters, and called with x parameters? Would the memory space be reserved for the extra parameter such that I could name it (in the function declaration) and then manually fill it with data? Would that let me access it using the name reserved in the function declaration?


Top
 Profile  
Reply with quote  
 Post subject: Re: Execution Recursion
PostPosted: Wed Apr 13, 2011 8:29 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
Well well! The answer ended up being yes, it is possible to manipulate the stack just like I wanted to.

So I'm no longer using the recursive Execute() call, because it is possible to put the data I want into an existing thread exactly like I theorized.

For anyone who may wish to try a similar thing in the future, once you have the gmThread* that you want, you use GetBase() to get a pointer to the first parameter of the thread's function (remembering that all gmThreads have a thread function that they are running on, even if it is the unnamed "file" function). And then you increment that gmVariable pointer to the second param, and so on.

Example GameMonkey code:
Code:
local threadFunc = function(cppParam1,cppParam2)
{
    cppParam1 = 1;
    cppParam2 = 2;
    globalFuncThatAltersCPPParams();
    print("cppParam1 = "+cppParam1+"\n");
    print("cppParam2 = "+cppParam2+"\n");
};

thread(threadFunc);


Example CPP code:
Code:
int GameMonkey_AlterParams(gmThread* a_thread)
{
    if (!a_thread) return GM_EXCEPTION;

    gmVariable* param = a_thread->GetBase();
    param->SetInt(11); //Sets param 1
    param++;
    param->SetInt(22); //Sets param 2

    return GM_OK;
}

machine->RegisterLibraryFunction("globalFuncThatAltersCPPParams",GameMonkey_AlterParams);



And because this actually changes the param values in the live thread, the output is as follows!
Code:
cppParam1 = 11
cppParam2 = 22


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC


Who is online

Users browsing this forum: Google [Bot] and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB® Forum Software © phpBB Group