Introduction to GameMonkey Script Part 2
Embedding GameMonkey
Garbage CollectionGameMonkey Script, like many other scripting languages, has an built-in garbage collector (GC). A garbage collector will periodically examine the data within the machine and determine whether an object is being referenced or not - if an object is deemed to have no referencing objects, the machine can assume it is no longer in use and will delete it to free memory. The GameMonkey machine uses an incremental mark & sweep garbage collector which has two main phases of operation. The first stage will examine all of the objects in the machine and mark the unreferenced objects for deletion. Once this phase is complete, the sweep stage is executed which calls the destructors (or more correctly finalizers) on all the marked objects and removed them from the machine. The GC is said to be incremental as it performs a little work on each execution cycle to reduce the amount of time the script execution cycle of the machine is paused for a full collection sweep. Garbage collection is important when binding your own types as you can often create a memory leak if an object is garbage collected without cleaning up any native objects it creates. Even more dangerously, if your object references other objects internally the GM machine may delete them and leave you with a bad pointer which could cause your game to crash. Both of these scenarios are undesirable so you need to make the garbage collector aware of your new object and tell it how to handle it. In order to handle the garbage collection on your bound type you must provide two things to the GameMonkey machine; a trace callback and a destruct callback. The trace callback is used during the mark phase of the GC and is used to tell the garbage collector which objects your native type is referencing. If you examine the code for the gmTableObject you will notice that its trace method will trace each of the objects the table holds; without doing this, the GC may remove some of the items from the machine erroneously. The destruct callback is used when the sweep stage is operational; it is used to free up any memory used by your bound type. I will complete my simple Vector type example by providing it with full garbage collection callbacks: /// Effectively the destructor for the object void GM_CDECL gc_destruct(gmMachine * a_machine, gmUserObject* a_object) { std::cout << "Vector: destructing\n"; GM_ASSERT(a_object->m_userType == Type); Vector* object = static_cast<Vector*>(a_object->m_user); delete object; } // Trace the object in the mark phase bool GM_CDECL gc_trace( gmMachine * a_machine, gmUserObject* a_object, gmGarbageCollector* a_gc, const int a_workLeftToGo, int& a_workDone ) { // Make sure this is the correct type GM_ASSERT(a_object->m_userType == m_gmType); // If your object immediately references other gmObjects, you should call GetNextObject for each member here // mark 'me' as done a_workDone++; return true; }
The gc_destruct destructor is simple; it merely calls delete on the memory we allocated in the constructor. The trace callback, gc_trace, is also simple for this particular type, but can get complicated as your type begins to reference more objects. Each immediate member object your type references should be pointed to using the GC method GetNextObject, which will allow GM to build the tree of referenced objects. You should also update the a_workDone parameter reference to indicate how many objects you are currently holding a reference to; this allows the incremental GC to effectively govern its own workload. Finally, we must register these callbacks in the BindLib function: a_machine->RegisterUserCallbacks(Type, gc_trace, gc_destruct, NULL); In this call we tell the gmMachine to register both a trace and destruct callback for this type. If you don't need them you can always pass NULL, but in the majority of times you will need to specify these callbacks. Further ExplorationThis article has provided you with enough information to begin exploring GameMonkey Script on your own. I have tried to cover as much of the important information as possible without getting too complicated or in depth and so there will invariably be areas that some people wish to expand upon. The topic of binding, for example, can fill many more pages and is a subject that many people will wish to explore. If you are such a person, I will set you the task of building on the simple vector example I provided here. Some example experiments may be:
You are urged to experiment further with the examples I have provided for you and begin looking further into what GameMonkey has to offer. Your game may wish to take advantage of scripted threads to remove many synchronous operations, a subject which is useful but has not been covered here in this introductory article. I wish you the best of luck in your future endeavours with GameMonkey Script. Thanks for reading and happy scripting! AcknowledgementsThe author would like to thank both Matthew Riek and Greg Douglas for their hard work in making GameMonkey Script what it is today. Thanks also to Howard Jeng for his hard work and eternal patience as my editor for this series and to Jack Hoxley for his help in making this article more accessible. References
|