GameMonkey Script

GameMonkey Script Forums
It is currently Wed Aug 15, 2018 1:37 am

All times are UTC




Post new topic Reply to topic  [ 7 posts ] 
Author Message
PostPosted: Wed Jul 20, 2011 5:00 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
I recently added support for a new type of GM scripts to my current project, and since then I have been plagued by inconsistent GM crashes in random places. Some of the more common crash locations are in gmGCColorSet::FollowPointers(), random spots in gmCodeGen, and also in gmThread::Sys_Execute(). When it crashes in Sys_Execute, it fails because 'instruction32' has somehow become NULL, and the dereference of NULL makes it explode.

So I went back and looked at the changes that I made when I added the new script type (UI scripts). There are two major pieces, which I'm worried I may have set up wrong, though I can't tell.


First:
In order to script the UI, I set up a global table called 'UI'. When a form is loaded from disc, which is only done once, I create a table in C++ and push it into the global UI table thusly,

Code:
   gmMachine* gmm = CGameMonkeyManager::GetInstance()->m_machine;

   //Allocate the form as a gmUserObject.
   gmUserObject* gmu = gmm->AllocUserObject(this,CUI2Form::s_gameMonkeyTypeID);
   gmVariable gmv; gmv.SetUser(gmu);

   //Allocate a table, and set its 'Form' element to the gmUO we created above.
   gmTableObject* formTable = gmm->AllocTableObject();
   formTable->Set(gmm,"Form",gmv); //Store the Form pointer in a table.
   gmv.SetTable(formTable);

   //Grab the global 'UI' table
   gmTableObject* globals = gmm->GetGlobals();
   gmTableObject* gmui = globals->Get(gmm,"UI").GetTableObjectSafe();

   //And save all of my changes into global GM space
   gmui->Set(gmm,m_scriptName,gmv); //Store the table with the Form pointer into the (soon-to-be) global UI table.
   gmv.SetTable(gmui);
   globals->Set(gmm,"UI",gmv); //Set the global UI table.

Am I going about this correctly? Trying to create and set table values, and add things to the global UI table?


Second:
After setting up the tables for a loaded form, I run an arbitrary GM script file associated with that form using ExecuteString(), and pass it the "formTable" from the above code as 'this'. The script adds any necessary functions or variables directly to formTable, which may include event handlers that the C++ could call. An "OnActivated" script, for example, that would get run when the form is displayed. I call these event handlers from C++ in the following manner:

Code:
   char* formName = "HUD";
   char* eventName = "OnActivated";

   gmMachine* gmm = CGameMonkeyManager::GetInstance()->m_machine;
   gmTableObject* globals = gmm->GetGlobals();
   gmTableObject* gmui = globals->Get(gmm,"UI").GeTableObjectSafe();
   gmTableObject* formTable = gmui->Get(gmm,formName).GetTableObjectSafe();
   gmVariable gmv; gmv.SetTable(formTable);

   static char buffer[128];
   sprintf(buffer,"if (this && .%s){ .%s(); }",eventName,eventName);

   //Fire the event, if there is one in the script for this form.
   gmm->ExecuteString(buffer,NULL,false,NULL,&gmv);

Which checks if the optional event handler is there, then runs it if it is.


So again, all this direct manipulation of tables and globals... am I doing something terribly wrong? Would any of this mess up garbage collection or break any internal pointers? The errors are ONLY occurring inside GameMonkey system code. I've never seen it break any of the rest of my program.


EDIT: I turned off garbage collection completely, and things are still exploding randomly.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jul 20, 2011 10:27 pm 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 703
Just a quick response for now, I'll try to have another look later and comment further...

At face value, the way you are doing things is normal and fine.
Skimming through your code it appears you are connecting the gmObjects / variables as you intend, though I'm not completely sure.

It is sometimes handy to test the structure with plain gmTableObjects instead of gmUserObjects just so you can run a script and log/dump the structure. You could also assemble a mockup structure in script, test with GME and then query it from script/native code.

As a side note, you would normally want to wrap GM object creation (all the code you show) inside disable/enable GC, although the current implementation makes this unnecessary unless you've enabled GC on every alloc.

I have no idea why you are experiencing such levels of instability, it should be rock solid and generally assert earlier than crash in most places. Certainly user objects are a source of garbage collection crashes when miss-used, but you say you still have problems even with GC off.

I see at the moment you appear to be using a raw gmUserObject containing a native pointer. To make a fully functional user type which allows table like attachment of member data/functions you would need to implement get/set dot operators, table style containment, and garbage collection support functions. I presume you have done this elsewhere as we only see the 'this' pointer of your native type.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jul 20, 2011 10:49 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
I've been a little lazy with the UserType bindings. I haven't written a single GetDot or SetDot. I've just been using raw pointers, registering functions to their types, and wrapping the pointers in ordinary gmTables when I need to add to what the C++ classes provide.

I've made some progress, but no total solution as of yet. Several of the random crashes seem to not be happening anymore, and in the past few dozen trials I've seen only the NULL instruction32 pointer error and an infinite garbage collection loop (but not a GC crash). I wrapped a mutex around all my ExecuteString calls just to make sure there are no conflicting script compilations. Execute() itself is only ever called from the main thread.

If I'm reading correctly, the instruction32 pointer increments itself when you switch on it, so it could increment to a null... though I thought the byte-code had some sort of protection against that. Other than that, there are the branching bytecodes. Am I missing anything else that alters the instruction32 pointer?

As for the infinite loop... it sort of looks like the "work to do" counter might be messed up. It's showing values in the millions. Admittedly it's in Execute's full garbage collection run.


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 21, 2011 2:57 am 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 703
A few more thoughts then...

Yes the 'instruction32' pointer does increment itself, and occasionally rewind for specific operations. You should not need to care about this and I hope you are not trying to modify the Execute routine for preemptive multi gm-threading, that would introduce a world of pain.
You have put a mutex around your machine execute, but are other OS threads accessing gmObjects?

If you've been lazy with user type bindings I can't vouch for their correctness or stability. It may be worth looking at the GameObject example, or other available sample code to see what is being done and if anything essential is missing from your implementation. For complete robustness I recommend not using raw native pointers in script objects at all, though it can be convenient and work well. (The alternative is to store handles or smart pointers which can be validated etc.)

The GC is intended to mostly be a black box that just works but it does rely on the user types implementing gc finalizer functionality and tracing connected/contained objects to ensure correct operation and stability. Certainly disabling GC can help isolate an issue, perhaps you can log the creation and destruction of your types to make sure they are not prematurely being deleted or such?


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 21, 2011 3:51 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
No, I'm not trying to do any crazy mods to the GM engine, just to get my multi-threaded program to treat GameMonkey nicely. I always set "runNow" to false, for instance, so everybody waits to be run in the single, per-frame Execute() call, no matter what OS thread created the GM thread. GC is *not* run every allocate, only as normal at the end of the Execute() function.

If I'm understanding correctly, then the goal of the GameObject example is to have a single, persistent gmTable associated with each instance of the C++ class. Under the hood, the gmVariable for any instance pushed into GM would still just be a properly typed pointer. All of the GetDot, SetDot, and garbage collection is just to deal with the gmTableObject that you wrapped into your C++ class. The gmVariable itself should already be handling all of that for the pointer, which is all that lives in GM in this scenario.

So the lazy method of pushing raw pointers should never cause garbage collection or memory issues, since I'm not trying to keep any persistent GM objects hidden in C++. It should be no worse than pushing an Int to GM, since that's all there is under the hood. Even for my Forms, I'm creating a single table that is linked back to root by way of globalRoot.UI.theTable. The table just happens to contain a single gmVariable with a raw pointer in it.

A look at some of the performance data in gmMachine shows that there is no apparent memory leak. With GarbageCollection turned on, the memory usage stays constant if I just sit there. Even though I am creating an OnUpdate thread every frame and passing it one of the global formTables in the above manner, the data from that gets used and then collected (I turned down the limits such that FullCollect() runs every frame for this test). Also, the Execute() call returns the same number of surviving threads.

In any case, GM is not handling any of my C++ objects' creation or destruction. I am using the raw pointers exactly like C++ pointers : throwaway ints that can expire whenever scope says that they should, not as actual object instances.

Since the errors are so inconsistent, sometimes occurring and sometimes not, I can only guess that it has to be threading related, so that's where I'll be looking today. *crosses fingers for luck*


Top
 Profile  
Reply with quote  
PostPosted: Thu Jul 21, 2011 10:02 pm 
Offline

Joined: Mon Dec 15, 2003 1:38 pm
Posts: 703
Your description is accurate, I think you are understanding completely what is going on and what the Object example does. The way you are using script without native owned script objects and without script owned native data should be safe and simple. I agree the issue is almost certainly related to application threading issues, so I'm keen to hear what progress you make.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jul 26, 2011 2:52 pm 
Offline

Joined: Mon Feb 28, 2011 8:06 pm
Posts: 12
Several days' worth of testing by the team seems to indicate that we've squashed that particular bug. Assuming we're not just having a lucky streak, then it was indeed a threading issue. Particularly, I was running the form script when the form was loaded from disc... but the loader is an asynchronous beasty that runs "loaded" callbacks in an entirely separate thread. So we'd be mid-Execute() when the form script came along in that other thread (sometimes) and tried to compile and add itself to the thread queue.

So, a mutex in one additional place and things resolved themselves. Now I just have to fix an infinite unique string issue and I'm golden! At least I already know what to do about that one. ;)


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

All times are UTC


Who is online

Users browsing this forum: No registered users 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