STAGING is a GameMaker Library for game initialization. It helps you split startup tasks into discrete chunks to be run in sequence, rather than trying to run your entire initialization script in a single frame (which can cause very bad things to happen, especially on console platforms).
It also provides support for asynchronous tasks, by waiting for the appropriate async event to trigger and executing a callback function when it does.
More detailed instructions are below, but basic usage looks like this:
sm = instance_create_depth(0, 0, 0, oSTAGING_Manager);
sm.add("Doing basic stage...", function(){
show_debug_message("This will print first")
})
sm.add_iter("Doing iteration...", function(){
show_debug_message($"This function will run 5 times. Currently doing iteration {iter+1} of {iter_limit}")
}, [], 5)
sm.add_async(STAGING_ASYNC_TYPE.HTTP, "Starting Async...",
function() {return http_get("https://gamemaker.io")}, [],
function() {
switch(async_load[? "status"]) {
case 1: return STAGING_STATUS.WAIT;
case 0:
show_debug_message("This will print if/when the async HTTP Get succeeds.");
return STAGING_STATUS.DONE;
default:
show_debug_message("This will print if/when the async HTTP Get fails.");
return STAGING_STATUS.DONE;
}
}
)
sm.add("Doing basic stage during async...", function() {
show_debug_message("This will print while async happens in the background.")
})
sm.on_finish = function() {
show_debug_message("This will print when all stages are finished, including waiting for async tasks.")
room_goto_next();
}
sm.run()
You can look at the create event of the oSTAGING_Test object for additional usage examples.
Create an instance of the oSTAGING_Manager object by calling:
sm = instance_create_depth(0, 0, 0, oSTAGING_Manager);
A standard stage creation looks like:
var _stage = sm.add(_label, _f, _args);
This will create a new instance of the STAGING_Stage() constructor and add it to the manager's queue. Note that storing the returned reference to the stage is not necessary unless you want to adjust it as discussed below.
The arguments are as follows:
_labelis a string that will be displayed on the top-left of the screen while the stage is running. You can adjust the font/color/alpha/alignment/position of this text by calling_stage.draw_conf(vars)wherevarsis a struct containing any of the following keys with relevant values:"draw_font", "draw_color", "draw_alpha", "draw_halign", "draw_valign", "draw_x", "draw_y". You can also redefine the stage'sdrawmethod if you'd like to display something entirely different while it runs, or display nothing by defining it as_stage.draw = function(){}_fis a function that will be called when the stage is run._argsis an optional array of arguments that will be passed to the function_f. In most cases this is unnecessary, but it can be useful for passing the function a reference to the stage that is calling it (among other things).
In addition to the standard STAGING_Stage created by sm.add(), the manager has several other functions to create specialized stages:
This is designed to perform some task repeatedly every step until some condition is met. It takes the same three arguments as the regular add(). The only difference is that its function _f must return STAGING_STATUS.WAIT to continue running, or STAGING_STATUS.DONE once finished.
This simply waits a fixed number of frames. A good use case for this is waiting the recommended 10 frames after changing from fullscreen to windowed mode before resizing the window. For example:
sm.add("Switching to windowed mode...", function() {
window_set_fullscreen(false);
});
sm.add_pause("Waiting to exit fullscreen...", 10);
sm.add("Resizing window...", function() {
window_set_size(1920, 1080);
});
This is designed to run tasks which trigger asynchronous events. It takes a different set of arguments, and is constructed as follows:
var _async_stage = sm.add_async(_async_type, _label, _f, _args, _callback, _callback_args);
The arguments are as follows:
_async_typeis a member of theSTAGING_ASYNC_TYPESenum, indicating which type of async event will be triggered to handle the callback._labelis the same as for the standardadd()_fis the same as for the standardadd(), except that it must return the id value output by the async function being run. For example, if_fincludes a call tohttp_get, its definition might look like:
function() {
var _async_id = http_get("https://gamemaker.io");
return _async_id;
}
_argsis the same as for the standardadd()._callbackis a function that will be executed when the corresponding async event triggers for the async id value returned by_f. This is where you should handle data found in theasync_loadDS_Map, such as the status of the operation. In some cases, your_callbackfunction may need to trigger multiple times before you're ready to stop waiting on it. For example, when downloading a file withhttp_get, you may see a status of1(downloading) several times before finally seeing a status of0(complete) or something less than0(error). Your_callbackfunction must returnSTAGING_STATUS.WAITas long as the task is still in progress, and returnSTAGING_STATUS.DONEonce it's finished._callback_argsis an optional array of arguments that will be passed to_callback.
This waits until some earlier (generally async) stage has completed before allowing subsequent stages to begin. Takes a reference to the stage to wait on as its first argument, and a label as its second.
This runs the same function a fixed number of times, incrementing the stage's iter variable by one each time (starting from zero). In addition to the standard _label, _f, and _args arguments, it also takes an _n argument to set the number of iterations to perform. This will be accessible as iter_limit from within the function.
Make sure to define the manager's on_finish() function. This will be run once all stages are complete and all async tasks triggered by stages have finished. By default it simply calls room_goto_next(), which might be enough, but you can do something more complicated like:
sm.on_finish = function() {
show_debug_message("STAGING finished, hooray!");
room_goto(rMenuMain);
}
Note that you cannot simply define this behavior as a final stage, since that will cause it to execute before the manager has finished waiting on async tasks.
Start executing stages by calling sm.start()
When calling add_async(), you'll need to pass it an _async_type value, from the ASYNC_EVENT_TYPES enum. Enum members are: AUDIO_PLAYBACK, AUDIO_PLAYBACK_ENDED, AUDIO_RECORDING, CLOUD, DIALOG, HTTP, IN_APP_PURCHASE, IMAGE_LOADED, NETWORKING, PUSH_NOTIFICATION, SAVE_LOAD, SOCIAL, STEAM, SYSTEM
Note that certain async functions will require some additional effort to handle. These include functions related to the Async - System Event, as well as any other async functions that do not return some sort of unique ID value. In these cases, the function you pass to add_async() must create some other ID value to return. And you must modify the relevant event definition of oSTAGING_Manager by defining and passing some stage_finder function as a second argument to sm.async_event. This stage_finder function should accept an _async_type as an argument (a member of ASYNC_EVENT_TYPES), and should return a reference to the instance of add_async() whose callback function is to be triggered. This reference can be found in sm.async[_async_type], which holds a list of tuples in which the first element is an ID and the second is a corresponding STAGING_Stage_Async instance. Determining a good way to locate the correct ID from your stage_finder function is left as an exercise for the developer. If you feel confident that only a single event of the given type will be triggered, or that events will always trigger in the order they are initialized, this could be as simple as:
stage_finder = function(_async_type) {
return async[_async_type][0][1];
}
async_event(STAGING_ASYNC_TYPE.SYSTEM, stage_finder);
A slightly more complicated approach would be something like:
stage_finder = function(_async_type) {
var _n_waiting = array_length(async[_async_type])
for (var i=0; i<_n_waitng; i++) {
var _id = async[_async_type][0][0]
var _stage = async[_async_type][0][1]
if some_magic_id_checker_function(_id) return _stage;
}
return undefined;
}
async_event(STAGING_ASYNC_TYPE.SYSTEM, stage_finder);
This is what's currently implemented for the Async - System event, but relying on it is not recommended.
When constructing a new stage with sm.add() or one of the related functions for specialized stages described above, it will automatically be added to the end of the manager instance's stages array, to be executed after all other stages that have been added so far. In certain cases, you may want to prioritize stages differently. For example, if the callback function of an asynchronous buffer save stage creates a new buffer load stage, you may want to do that immediately.
In this case, you can call sm.prioritize(_from, _to) after constructing the new stage, and it will move the stage in slot number _from to slot number _to. If no arguments are passed, it will move the stage from the last slot to the first slot.