Game Development Community

Scene Event Scheduler (C++)

by Justin Proffitt · in Torque Game Builder · 06/19/2012 (9:41 am) · 2 replies

Hi guys!

So, I ran into a problem that I am certain everyone's familiar with - Pausing schedules. When you want to pause the game, it isn't as easy as setting pause to true on the scenegraph. Events, running on Sim time rather than scene time, keep chugging right along, executing and causing headaches for you. The pain multiplies when you are using multiple scenegraphs at any given time (for instance, one for GUI and one for gameplay). I looked around the board and found this awesome resource by Drew, and I had quite a bit of fun with it. However once I had finished converting all my in-game events to the new system, I found it was just too slow for my game - Torque script couldn't handle more than a few events being created like that at a time without stalling (Another problem with Sim time based events - they seem to stack up if they don't finish in time, so the game freezes instead of slowing down). So I bit the bullet, took my bare minimum c knowledge to the source and plagiarized the hell out of the existing schedule system, forging a parallel system that works with individual scenegraphs using their scene time instead of sim time.

Usage:
It uses the method "sceneSchedule", and can be used either on t2dscenegraphs or t2dsceneobject in scenegraphs, 3 examples of its use follows:

// t2dSceneGraph.schedule(time, objID || 0, functionName, arg1, etc.)
// (objID can be any object, not just t2dSceneObjects)

SceneGraph.sceneSchedule(1, 0, "echo", "Event!"); // Call "echo" as a global function after 1 second

SceneGraph.sceneSchedule(1, enemy, "explode", "violently") // Call "explode" on enemy object after 1 second

// t2dSceneObject.sceneSchedule(time, methodName, arg1, etc.)

Player.sceneSchedule(1, "setAnimation", "run") // Call "setAnimation" on player object after 1 second 
// (the event is queued in whichever scenegraph the player object resides in)

These events tie into the ticking process of the scenegraph they're associated with, so they pause when the scenegraph pauses, and resume when it resumes. Should the scenegraph slow down, they'll slow down too. In short, they do exactly what you expect them to do, no fuss :)
All the other functions - isEventPending, cancel, getEventTimeLeft, etc. - work too, you simply call them on the scenegraph instead of globally. Each scenegraph has its own event queue created and destroyed automatically, so it should require minimal management on your end. Just keep track of the scenegraph as well as the event!

Limitations:
Let's see. Well for starters, this is my first foray into the source. I made it very much by trial and error, and this version initially won by virtue of not breaking immediately. Though I have been debugging it meticulously and believe it is now fairly safe, there might be ways still to turn it into a spiteful beast craving fatal exceptions. On the other hand through debugging it I have gotten to know most of the ways it can break, and thus far all of them involved simply a pointer that was either accidentally NULLed or should have been NULLed. Unfortunately I wasn't able to implement a timer (that is its own beast), but you can easily rig up a script-side timer function, while c does all the heavy lifting.

Anyways, without further ado, here are the files!

Handy Dandy Dropbox!

... And a few changes you need to make to get it to work, I just couldn't completely contain it (I am proud I even figured out classes):

In game/demoGame.cc
Add an include below the others:
#include "T2D/SceneEventManager.h"

Find this bit of code (In clientProcess(U32 timeDelta))
ITickable::advanceTime(timeDelta);

And add this above it:
// Execute scene events before
   // all scenegraphs are updated
   SceneEventManager::executeEvents();

In console/simManager.cc
Add an include below the others:
#include "T2D/SceneEventManager.h"

Find this bit of code (In SimObject::unregisterObject())
// Notify all objects that are waiting for delete
   // messages
   if (getGroup())
      getGroup()->removeObject(this);

And add this below it:
// Delete pending scene events
   SceneEventManager::cancelPendingEvents(this);

In T2D/t2dSceneGraph.cc
Add an include below the others:
#include "./SceneEventManager.h"

Find this line (In t2dSceneGraph::onAdd())
gLoadingSceneGraph = this;

And add this below it:
// Generate a scene event queue
    new SceneEventManager::SceneGraphEventQueue(this);

Finally find this bit of code (In t2dSceneGraph::processTick( void ))
// Update Graph Time.
    mSceneTime += ITickable::smTickSec;

And add this below it:
// Advance scene events
    SceneEventManager::advanceToTime(this, mSceneTime);

Edit: Rooted out some major bugs and fixed them, added a couple safe guards, and split up the queuing/executing processes to good effect (drastically simplified the issue of events deleting scenegraphs). :)

And you're probably wondering about my license. I have access to a pro version via my partner/parent, Collin Burton.

About the author

I am a college student majoring in physics. As it so happens I like programming, a hobby which extends my profession in web design.


#1
06/27/2012 (9:03 am)
Interesting changes :)
#2
06/30/2012 (3:41 pm)
Hmm. When I have some spare time I will have to play with this. It may be better than my solution. :P It sounds simpler, which is mostly better. Although it *only* pauses entire scenes which is really fine, I think.

My version added a pausing ability to the main schedule queue. It works on any object, such as a single player or enemy or bullet. (To pause the entire scene, write a loop that goes through the scene graph and pauses everything you want.)

The idea is to have a "suspended events" queue. Current scheduled events for an object are moved to the suspended events and any new schedules for that object go there, too. Then when you unpause the object, the events are moved back into the main schedule queue with appropriate future deadlines.