GML Programming Patterns: Introducing RousrSuite - sr_array

Part 3 of GML Programming Patterns Series

Introducing RousrSuite

Some of you may be familiar with RousrSuite - the ill documented extension that is included with the other Rousr extensions. RousrSuite is a collection of small GML Extensions, generally each with a singular purpose, that are used by Rousr projects.

The current make-up of RousrSuite is:

  • RousrCore - a grab-bag set of functions used at the core of most Rousr code. This includes much of the basic functionality we’ve wrapped up… such as ensure_color and ensure_font which store a global for the current draw_set_* variable and when passed a color or font, they’ll do a draw_set call if its not already that. Of course, there’s things like math and debugging functions in here as well!
  • RousrDs - a set of new data structures that are based on arrays, as well as some helper functions when working with the native ds types.
  • RousrInput - an (unfinished) set of functions to wrap up the paradigm of named actions being mapped to controller inputs in an attempt to add a layer of abstraction to input.
  • RousrUI - an (unfinished) set of UI widgets and basic GUI functionality you’d expect. This is intended for in-game use, which differs from the ImGuiGML debug-use case.

It’s available to be downloaded on the YYMP, but mainly is intended to be used internally by our extensions. I do hope one day it’s an extensively usable library of functions for anyone!

Today’s Topic: sr_array

In GML, I find the use of arrays to be very convenient, especially once I had gotten used to the “Create on Write” vs using references to arrays to ensure we’re operating on the same one when accessing them from other scopes than they were created from. The clear benefit over the other ds_ types is that there’s no need to call a destroy on them, they’re seemingly auto-garbage collected. This makes some of the more OOP paradigms I’ve talked about much cleaner to implement - we’re not relying on a destroy / clean up hook and can just let these objects fall out of scope when we’re finished… or potentially pool them.

Motivation

So, arrays are useful and convenient, but I wanted them to be slightly more reliable as a container. For one, native arrays can’t be shrunk - they can grow larger and the pointer will remain the same, but if you want to shrink one, you’re out of luck and you need to create an entirely new array, copying the data to it. Also, with the blackbox of array_length_1d and not being aware of what its doing, I also like having the ability to store alongside the array simply how large it is.

Enter sr_array

sr_array is an Array Object (as I call them) that wraps the normal 1D array that is native to GML. It’s a fairly simple construct, where sr_array_create() returns one, and you can optionally pass it an existing array (and size) for convenience (and micro-optimizing):

1
2
3
var _arr = [ ];
var _arr_len = 0;
_arr = sr_array_create(_arr, _arr_len);

Generally though, I just use the default version and allow it to create its own new array:

1
var _arr = sr_array_create();

So what does this return? An array with this layout:

1
2
3
4
var _sr_array_under_the_hood = [
size_of_actual_array,
[ actual array ],
]

At this point, its highly likely you’re still asking yourself: But Why?!

Simply-put: the primary purpose of doing this is that anything that holds a reference to this array can retain the reference to this array, even if the [ actual array ] is destroyed and recreated.

(Note: The reason that in the debugger Arrays show up with a hex value is that they’re actually pointers, rather than an id or index like the ds types or even resources store)

The reason that we want to do this in the first place is so that we can freely resize the array, grow or shrink it, and not worry that all instances with variables holding the array now need to know that the old array is invalid and they need a new reference to the new array. As I mentioned before, you can grow an array and retain the same pointer to it, but if you want to shrink an array the only way to do this is to create a brand new array and array_copy to it.

So, how’s it work?

The size_of_actual_array is not literally the same value as what array_length_1d returns, but rather, it’s the size of the currently “in use” elements of actual_array. By doing this, we can subtract from that number to shrink the array as far as logic is concerned, but it’s still actually holding a pointer to the same memory as before.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var _arr = sr_array_create();
sr_array_push_back(_arr, 5);
sr_array_push_back(_arr, 10);
sr_array_push_back(_arr, 15);
// The internal sr_array now should be:
// [
// 3
// [ 5, 10, 15 ],
// ]
sr_array_remove(_arr, 2); // remove the last element
// We are now:
// [
// 2
// [ 5, 10, 15 ],
// ]

You’ll note that in this example, we haven’t removed 15, and that’s on purpose. Since we’ve decremented the 3 to a 2, the internal array can stay there - no need to allocate any new memory or clone the array - we now simply know “only the first 2 elements are valid.” One downside is that this does mean that the array always occupies the max element count its ever reached in memory, and I admittedly haven’t really benchmarked how much of an issue that is, but if it were ever to get out of control, the clear option does allow you to reset the internal array.

Digging In Deeper…

So now that we’ve got the basics of the sr_array down, let’s talk a little bit about how it works under the hood. sr_array includes a bunch of functions to manipulate the array, but the things that it can do that a normal array can’t are: inserting new elements in the middle of the array and removing elements from an array.

sr_array_insert creates a brand new array as the data array of the sr_array and use array_copy to copy the original array to the newer, 1-element larger array, making space to insert the new element. sr_array_remove also creates a brand new data array, but this one is 1-element smaller, with the element at the passed index omitted from copying. Generally, this means we’ll do two copies: copy the first portion of the array, add our element at the inserted index, and finally copy the remaining portion of the original array in the case of a insert, or we copy the portion before and portion after the removed index in the case of a remove.

sr_array is an accessor function that follows a pattern I tend to use: If you pass a value, then it acts as a setter, setting the value at _index to _val while if you omit the value, it acts as a getter, returning the value at _index.

In addition to these functions, we also have push/pop functionality for adding / removing elements to both the front and end of the array as well as some functionality for finding a value in the array, pushing unique elements to the array, or just clearing the data contained in the array.

While sr_array is designed to be fast to implement and convenient to use, the one last downside to mention is that it does mean the [] accessor will not play nicely with sr_array, and you’ve gotta remember to access your arrays using the sr_array accessor function. So if you do adopt using these guys, when you start debugging strange array accesses, make sure you’re using sr_array accessors if they’re sr_arrays. I still mess this up.

1
var _elem = sr_array(_arr, _index);

which is a little unfortunate, but without accessor chaining (i.e., var _thing = _array_of_arrays[_i][_thing_index])) it actually ends up being a bit convenient since you can nest the calls if necessary.

1
var _thing = sr_array( sr_array(_array_of_arrays, _i), _thing_index );

Advanced Topic: sr_stack_array

Ok, this guy isn’t as advanced as the mixins from the last article, but I liked the consistency of ending on a thing and calling it an “Advanced Topic.” (I just broke the fourth wall I think).

The sr_stack_array is a stack data structure built similar to the sr_array with some minor differences in API and what the values it stores mean. It exists as an easy drop-in and forget stack datastructure, without the need to do the ds_create/destroy pattern of a ds_stack since we have no need to destroy it.

1
2
3
4
var _stack_array_looks_like = [
[ stack of elements ],
Top
]

A Top value of -1 means that the stack is empty, and we don’t pay attention to whatever array_length_1d is of the stack of elements - Top is all that matters.

In addition to sr_stack_array_create the following functionality exists - it’s a bit smaller of an API than an sr_array though it has a much more specific use than the more general purpose sr_array.

Accessing Data

  • sr_stack_array_top(_rousr_stack_array)
  • sr_stack_array_empty(_rousr_stack_array)

These functions do exactly what you’d think - the first will tell you what the top index of the stack is, how large it is, while the second returns a boolean of whether or not the stack is empty.

  • sr_stack_array_peek(_rousr_stack_array, [_index])

Peek will return the value of the top of the stack, or at the _index from the Bottom of the stack if _index is a positive value, or from the Top of the stack if it’s a negative… but does not change the stack.

Manipulating the Stack

  • sr_stack_array_pop(_rousr_stack_array)
  • sr_stack_array_push(_rousr_stack_array, _val)

Pop will return the value at the Top of the stack and decrease it by one, while the push simply adds _val to the top of the stack.

Conclusion

That’s all I’ve got for you this update, but next update we’ll take a look at another RousrSuite extension: sr_core.

However, I’m looking to hear from you what you might want to learn more about, particularly:

  • Programming Patterns
  • Algorithms
  • General habits / standards

But really - if there’s a particular technique you’re interested in feel free to hit me up @babyj3ans or on the rousr discord!