GML Programming Patterns: Introducing RousrSuite - sr_core

Part 4 of GML Programming Patterns Series

Today’s Topic: sr_core

While I didn’t start these patterns with the core library, it is actually a foundation to all of the rousr projects, though it’s more of a jack-of-all-trades than anything with a big heavy hitter. As a result, this entry is more of a “grab bag” of helpful scripts I’ve added to our core library to give you some concepts you may not have considered.

Motivation

I wanted to make sure I gathered everything I’d need for any project and had a very easy way to access it, so while they don’t follow much of a common theme in total - their common theme is “necessity.” You’ll notice a bit of it is heavy on the idea of “caching” and “re-using data” which are really the main tenets of what the sr_core library is attempting to abstract. In addition, it aims to eliminate as much project “boiler plate” code as it can.

(Boiler plate code is a term used for tidbits of code you have to add to each and every one of your projects, so why not make it small and as painless as possible?)

sr_ensure_color / sr_ensure_font

The functions sr_ensure_color and ensureFont make use of the gml_pragma("global", "script_name") functionality to set themselves some global, pre-cached data. Whenever you intend to call draw_set_color or draw_set_font you’d use ensure instead because it works by setting a global variable to whatever is passed to it. The next time it’s called, if its the same value, it just won’t do anything again. This way we can ensure we’re only switching “contexts” when necessary (no need to make a call if its going to return to the same value), but we can also always retrieve the current set value easily.

They’re small little guys, so here’s an example of sr_ensure_font‘s code… sr_ensure_color is very similar:

1
2
3
4
5
6
7
8
9
10
11
///@function sr_ensure_font(_font_index)
///@desc cache the current font
///@param _font_index font resource index
gml_pragma("global", "global.__rousr_set_font = undefined;");
gml_pragma("forceinline");
var _font_index = argument0;
if (_font_index != global.__rousr_set_font) {
draw_set_font(_font_index);
global.__rousr_set_font = _font_index;
}

sr_color_hex / sr_color_glsl

The two color functions serve as pretty basic helpers - sr_color_hex(_hex) takes a hex value (as a number, not a string), and converts it to the BGR format that we all know and love that GMS2 uses. This is enormously helpful when you’re trying to match your mockups from aseprite and need to color some text or use a shader…

But speaking of shaders, there’s also sr_color_glsl(_gml_color) which returns an array of values from 0-1 that represent the passed color.

sr_execute / sr_callback_create / sr_callback_execute

These 3 functions help you call variable amounts of arguments in a script_execute call. sr_execute(_func, _params) takes a an array of parameters, up to 32, and calls _func passing each one as another argument (essentially, it’s an unpack operation).

More advanced use exists with sr_callback in that the create allows you to creat a single array object that contains the name of a script and arguments to pass to it - but only executes that little bundle of functionality when you sr_callback_execute it.

sr_outline_text / sr_shadow_text

These two are more just ‘fun’ little functions I tend to use with much of my text. They’re not doing anything fancy, they’re actually rendering the same block of text multiple times, and laying them out according to an outline or shadow pattern. You can pass offsets to customize it a bit, but really, they’re just helpers for me incessantly using these text techniques!

Use Your Core

Since sr_core is included with all of the rousr extensions (some extensions only include the pieces of rousrSuite they need), you may already have it if you use any of our extensions! Of course, the most up-to-date version is up on the marketplace, and it always remains compatible with latest versions of each other extension.

Advanced Topic: Singletons

Also included is sr_ensure_singleton(_singleton_name) which implements an older method I used to implement singletons. It expects that the passed name is actually the name of a global variable, checks if it’s set, and if the calling instance is the same id as the current set value of the global - destroying the old one if it exists.

Some notes here are that this is a pretty bad way to follow the singleton pattern, but it does actually work. Notably, it destroys an existing singleton if one is created - this is probably more of a bug than anything because in use, you’d never want your existing data blown away… or you’d at least want to expect that it is going to happen.

Instead, now what I tend to do is something along the lines of this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
///@function test_singleton_event_create()
///@desc create event for `test_singleton` object
// set the variable so we don't ever need an exists check
gml_pragma("global", "global.__testsingleton = undefined");
// make an "easy accessible" macro for the singleton, users need something pretty!
#macro TestSingleton (global.__testsingleton)
if (instance_number(object_index) == 0) {
instance_destroy(id);
// optionally, post a log message here if you'd like,
// otherwise, we can accept this one is just going to be deleted
return;
}
with (TestSingleton) {
// actually init
};

Users of your singleton can always use the with(TestSingleton) syntax. It gives them the benefit of an automatic “null check” (checking if an instance actually is valid), and also ensures they’re accessing the one true singleton. Since we destroy any duplicates, the with syntax will still refer to the existing one, and users will be none-the-wiser (unless you want them to be).

One benefit of the singleton pattern is that you can use objects as a “namespace” rather than loading up the global namespace with temps - but you can also manage the lifetime of globals such as data structures that might need to be cleaned up…

But of course, that’s just one use!