This is a function "cheat sheet" for the Quality Structures extension by YellowAfterlife.
The extension can be found on itch.io or GM Marketplace.
A most up-to-date version of the manual is always available online.

Click on sections to expand/collapse them.
Quick display controls: Categories · Sections · Everything ·

Overview

This extension comes in two versions

  • quality_structures_one
    The extension itself, covering all features described here and in the blog post.
  • quality_structures_zero
    Has identical API to QS1, but forwards function calls to their respective built-in functions at compile time.

    For extremely performance-critical projects, you can swap QS1 for QS0 once you have established that there are no more data structure issues left (or use Configurations in GM to have debug/release options).

As you can guess, this scheme ensures that migrating your game code to use qs_ functions will not have any negative consequences.

See the blog post for more information.

Equivalent functions

The extension provides equivalents for basically every built-in data structure function.

There are no noticeable difference to how these work aside of added safety checks when using quality_structures_one.

Here's a list:

qs_grid_add(id,x,y,val)
qs_grid_add_disk(id,xm,ym,r,val)
qs_grid_add_grid_region(id,source,x1,y1,x2,y2,xpos,ypos)
qs_grid_add_region(id,x1,y1,x2,y2,val)
qs_grid_clear(id,val)
qs_grid_copy(id,source)
qs_grid_create(w,h)
qs_grid_destroy(grid)
qs_grid_get(id,x,y)
qs_grid_get_disk_max(id,xm,ym,r)
qs_grid_get_disk_mean(id,xm,ym,r)
qs_grid_get_disk_min(id,xm,ym,r)
qs_grid_get_disk_sum(id,xm,ym,r)
qs_grid_get_max(id,x1,y1,x2,y2)
qs_grid_get_mean(id,x1,y1,x2,y2)
qs_grid_get_min(id,x1,y1,x2,y2)
qs_grid_get_sum(id,x1,y1,x2,y2)
qs_grid_height(id)
qs_grid_multiply(id,x,y,val)
qs_grid_multiply_disk(id,xm,ym,r,val)
qs_grid_multiply_grid_region(id,source,x1,y1,x2,y2,xpos,ypos)
qs_grid_multiply_region(id,x1,y1,x2,y2,val)
qs_grid_resize(id,w,h)
qs_grid_set(id,x,y,val)
qs_grid_set_disk(id,xm,ym,r,val)
qs_grid_set_grid_region(id,source,x1,y1,x2,y2,xpos,ypos)
qs_grid_set_region(id,x1,y1,x2,y2,val)
qs_grid_shuffle(id)
qs_grid_sort(id,column,ascending)
qs_grid_value_disk_exists(id,xm,ym,r,val)
qs_grid_value_disk_x(id,xm,ym,r,val)
qs_grid_value_disk_y(id,xm,ym,r,val)
qs_grid_value_exists(id,x1,y1,x2,y2,val)
qs_grid_value_x(id,x1,y1,x2,y2,val)
qs_grid_value_y(id,x1,y1,x2,y2,val)
qs_grid_width(id)

qs_list_add(list, ...values)
qs_list_clear(id)
qs_list_copy(id,source)
qs_list_create()
qs_list_delete(id,pos)
qs_list_destroy(list)
qs_list_empty(id)
qs_list_find_index(id,value)
qs_list_find_value(id,pos)
qs_list_insert(id,pos,value)
qs_list_mark_as_list(id, pos)
qs_list_mark_as_map(id, pos)
qs_list_replace(id,pos,value)
qs_list_set(id,pos,value)
qs_list_shuffle(id)
qs_list_size(id)
qs_list_sort(id,ascending)

qs_map_add_list(id, key, list_value)
qs_map_add_map(id, key, map_value)
qs_map_clear(id)
qs_map_copy(id,source)
qs_map_create()
qs_map_delete(id,key)
qs_map_destroy(map)
qs_map_empty(id)
qs_map_exists(id,key)
qs_map_find_first(id)
qs_map_find_last(id)
qs_map_find_next(id,key)
qs_map_find_previous(id,key)
qs_map_find_value(id,key)
qs_map_replace_list(id, key, list_value)
qs_map_replace_map(id, key, map_value)
qs_map_set(id,key,value)
qs_map_size(id)

qs_priority_add(id,value,priority)
qs_priority_change_priority(id,value,priority)
qs_priority_clear(id)
qs_priority_copy(id,source)
qs_priority_create()
qs_priority_delete_max(id)
qs_priority_delete_min(id)
qs_priority_delete_value(id,value)
qs_priority_destroy(priority)
qs_priority_empty(id)
qs_priority_find_max(id)
qs_priority_find_min(id)
qs_priority_find_priority(id,value)
qs_priority_size(id)

qs_queue_clear(id)
qs_queue_copy(id,source)
qs_queue_create()
qs_queue_dequeue(id)
qs_queue_destroy(queue)
qs_queue_empty(id)
qs_queue_enqueue(queue, ...values)
qs_queue_head(id)
qs_queue_size(id)
qs_queue_tail(id)

qs_stack_clear(id)
qs_stack_copy(id,source)
qs_stack_create()
qs_stack_destroy(stack)
qs_stack_empty(id)
qs_stack_pop(id)
qs_stack_push(stack, ...values)
qs_stack_size(id)
qs_stack_top(id)
JSON

A few notes on JSON functions in QS.

qs_json_encode(map)string

Acts almost identically to the built-in json_encode, but takes a QS map and has a number of debug options.

qs_json_decode(json_string)map

Much like the built-in json_decode, except:

  • Returned values are qs_ structures.
  • If an error encountered, returns qs_json_error
var q = qs_json_decode(@'{"list":["one","two","three"]}');
show_debug_message(qs_get(q, "list", 1)); // "two"
qs_json_error

Returned by qs_json_decode if JSON is invalid.

A convenience macro.

var q = qs_json_decode(...);
if (q != qs_json_error) {
    // OK!
} else {
    // an error occurred
}
Chained accessors
qs_get(qs, ...indexes)value

Does a chained read on one or more data structures.

The way items are accessed depends on types of arguments:

  • string: uses qs_map_find_value
  • number: uses qs_list_find_value
  • qs_xy: uses qs_grid_get
  • (anything else): throws an error

So, for example,

var q = qs_json_decode(@'{"list":["zero", "one", "two"]}');
show_debug_message(qs_get(q, "list", 1)); // "one"

would be equivalent to

var q = qs_json_decode(@'{"list":["zero", "one", "two"]}');
var list = qs_map_find_value(q, "list");
show_debug_message(ds_list_find_value(list, 1)); // "one"
qs_set(qs, ...indexes, value

Akin to qs_get, but will change an item in the last structure.

So, for example,

var q = qs_json_decode(@'{"list":["zero", "one", "two"]}');
qs_set(q, "list", 1, "hi");
show_debug_message(qs_get(q, "list", 1)); // "hi"

would be equivalent to

var q = qs_json_decode(@'{"list":["zero", "one", "two"]}');
var list = qs_map_find_value(q, "list");
qs_list_set(list, 1, "hi");
show_debug_message(qs_get(q, "list", 1)); // "hi"
qs_xy(x, y)

Forms an XY pair for accessing grid items through qs_get/qs_set.

Note that these are actively pooled so you should not use them for any other purposes.

Options

The following is a set of variables that allow to customize the behaviour of quality_structures_one extension.

qs_debug_callstack

If set to true (default), creating and destroying data structures records where they have been created/destroyed from, which can be used to aid with discovering origins of memory leaks or unexpected behaviour.

qs_debug_json_encode

QS can alert you about a variety of common issues in qs_json_encode calls.

Unlike above, this works as an "error-level",

  • 0: doesn't do anything
  • 1: calls show_debug_message(...) (default)
  • 2: calls show_error(..., false)
  • 3: calls show_error(..., true)
qs_debug_active

If set to true, QS will keep track of data structures of each kind, and you'll be able to view them by inspecting global.g_qs_active_maps/_lists/etc. (all of which are ds_maps with key being the DS ID and value being the callstack for where it was made from) in the debugger.

Using this in conjunction with qs_debug_callstack is recommended and can be used to easily pinpoint data structure leaks.

qs_option_max_json_depth

Defines maximum depth for encoded JSON structures (default is 100), at which point a warning is shown and any deeper values are omitted.

Helpful for figuring out issues related to attempting to encode cyclic data structures.

qs_option_no_json_mark

If set to true, allows encoding nested data structures without marking them as such.

Note that relying on this flag means that your code will not work with quality_structures_zero extension.

Limitations
Accessor operators

Since GM accessors (list[|index], map[?key], grid[#x,y]) unconditionally call their according built-in ds_ functions, they will not work with quality_structures_one extension.

Chained accessor functions are recommended to be used in their place, where possible.

_read/_write functions

While GameMaker's built-in functions spot a set of functions to encode/decode data structures in base16 format (e.g. ds_map_write), the format is completely undocumented, does not support nested structures (unlike JSON), and had changed multiple times over years, making it not particularly safe to even attempt maintaining compatibility with them.

It is generally a good idea to use either JSON or a custom format.

ds_map_secure_*

These are not actually very secure (consisting of base64-encoded JSON and a machine-specific hash), meaning that you shouldn't generally be using them at all.

If you strongly desire, you can use qs_json_encode -> json_decode -> ds_map_secure_save on save and ds_map_secure_load -> json_encode -> qs_json_decode on load, but it is strongly recommended that you use a hash with a custom "salt" value instead to verify integrity of your save files.

Index comparisons

Doing if (map >= 0) will not work with quality_structures_one extension since the QS values are arrays.

Doing if (map != -1) will work as intended, however.

You can also take the opportunity to use undefined as a "no data structure" value, which will also help once data structures as a data type are implemented in base GM.