GMS 2.3+ [TEXT TUTORIAL] Types Explained (Memory Leaks)

GM Version: Studio 2.3+
Target Platform: Windows / All
Download: n/a
Links: n/a

[THEME]
Today we will be talking about types, this will be a short tutorial that I hope will help you understand how GMS2 handles types.
We will also talk about memory leak problems that can appear if you are not careful using data structures and other elements.

I'll do my best to keep it clean and simple to follow ;)

[EXPLANATION]
Lets start our tutorial, first we will talk about types, as it will prepare you to understand the rest.

NOTE: The code examples throughout this tutorial can and should be followed so you can better understand the concepts.​


[TYPES]

(1) WHAT ARE TYPES?
This will look like a very common-sense talk but believe me, that there are a LOT of people that actually don't know what those are.
But it's okay, part of the community is here to learn so we've got you covered. Let's start simple and with some "game talk".
When we think "games" we most often think about:
  • Gun with ammo
  • NPC with name
  • Player with a bunch of stats (health, age, weight, attack, ...)
  • Inventory with some items
The examples above will immediately make you think that gun ammo will be represented by a number, the NPC name is a text and the inventory is actually a container with some items,
player stats need to be another kind of container but with each stat is given a certain value.
When we use the words number, text, container we are actually talking about types, in this case respectively: reals/int, string, array/struct (I won't use the term list right now... I'll explain later)

(2) GMS 2.3 TYPES
So now lets look at what types are there in GMS2.3+, for that we will look at he code below and debugger information:
GML:
a_real = 1; // this is a number or 'real'

a_string = "Hello"; // this is text or 'string'

an_array = []; // this is a collection or 'array' (*)

a_struct = {}; // this is a key:value group or 'struct' (GMS2.3 only) (*)

a_function = function() {}; // this is a 'function' (GMS2.3 only) (*)
types.PNG

So, a_real and a_string are pretty straight forward. You look at their value and you know the first is a number and the second is text.
You might, however, notice that I placed a (*) in the code above for the last three variables and looking into the debugger they are actually kind of strange.
The values you see in the Value column for those, are actually memory addresses. So as an analogy:

memory is kind of a big city where your variables are in and that strange looking number is... well... it's the address of the house where they live

NOTE: Don't get fooled by the first two types (reals/strings) they are actually also stored inside an address however to simplify things GML doesn't​
allow us to know the address they are stored in. Also it is actually more readable to see a value than it is to see an address for those types.​
NOTE: You will read the word real, int and string from now on. So I'll explain them better: a real is a number as we talk in mathematics real numbers​
so it includes all numbers whole/fractions; on the other hand int is also a number but refers to a integer/whole number without fraction part. The​
word string refers to a variable that contains text.​


(2.1) COMPOSED TYPES (CONTAINERS)
One of the type examples we used in the "let's talk games" list was the container type and this subsection is related to those.
On the debugger image above we can spot a [+] sign next to both the an_array and a_struct variables
This simple indicator means that they are containers in other words they are boxes where you can store other variables.
Let's look deep into the array/struct type with another code example and subsequent debugger image:

GML:
a_struct = {
    name: "player",
    age: 30,
    attack: 10
}

an_array = [
    "apples",
    "oranges",
    "rocks"
];
array-struct.PNG

In the examples above we just populated both our array and struct with some variables, making it possible to expand both and look at their contents.
One thing to notice right away is that the type of the array or struct (memory address) is independent from the type of the contents.
The main difference you can clearly see between both is that:
  • the struct has variables within itself and those variables have values.
  • the array is a pure ordered container where each entry is represented by a number (index).
NOTE: Inside the a_struct we can see a variable we didn't defined ourselves - toString. This variable is a function​
and is added by default to all defined structs. This function is responsible for converting the struct to a string when we use the​
string() or show_debug_message() functions for example.​
NOTE: You can see that structs can hold values of different types, but the same can happen with arrays even though in the​
example above I only provided an example with strings, arrays can hold any other type and also mixtures of types.​


(3) PSEUDO-TYPES (RESOURCES)
Now that we have looked at the main types in GMS2 you might be asking yourself:

What about objects/sprites/sound?
Some of you with more advanced programming skills might even add more stuff to the question above like buffers/surfaces.
I'm sorry to be the one giving you the bad news (if it is news at all), but those aren't actually types on GML.
To explain that we will look into another code example with yet another cute debugger image.
GML:
a_buffer = buffer_create(1, buffer_fixed, 1);

a_surface = surface_create(1, 1);

a_sprite = sprEnemy;

an_object = objGame;
types2.PNG

Has you can see above all of the examples we have buffers/surfaces/sprite/objects are actually numbers (or int to be specific).
I call these pseudo types, we kind of work with them as if they were types even though they are not. You might be asking

How can three different things (buffer/surface and an object) all be 0?! How does it work?
Well to explain this we will need to look at the next topic as we will be talking about "the hidden table".

(3) THE HIDDEN TABLE
In order to explain and solve the problem above we will need to imagine an excel spreadsheet.
Hmmm.. well I'll actually place one below no need to imagine :p

table.PNG

So how GMS2 handles these pseudo-types is by using something similar to an hidden table.
This table has a column for each corresponding pseudo-type and as you might have guessed each column has rows and the values we get in the debugger are actually the row number where the actual buffer/surface/sprite is.
Let's fill in the blanks for the data we have created:

table1.PNG

Above we can see that the types actually exist and there are also addresses associated with them, but GML doesn't allow us access to them.
We are only given the handle (number of the row) for the respective element we are creating.

NOTE: The addresses you see above were randomly created by me with a purpose of explaining how GMS2 handles pseudo-types under the hood.​
There is actually no way for us to know the address where the data is stored.​
NOTE: I'll be calling the "returning numbers" from the functions above - handles - since, even though they are not the actual element, they allow us to manipulate it.​

(4) IMPLICATIONS
There are some good and bad things about this approach now we will be looking at some:

PROS:
  • Prevents memory corruption (by inexperienced users)
  • Light on the engine (no need to automatically track and collect the unused elements)
CONS:
  • There is no way to differentiate between elements (in the example above a_buffer and a_surface are indistinguishable by the engine)
  • You need to manually destroy the element (setting a handle to undefined will not destroy it's representation)

Let's look at this code below for a simple example:
GML:
buffer_read(a_buffer, buffer_u8); // This WORKS reads buffer
buffer_read(a_surface, buffer_u8); // This also WORKS reads buffer
buffer_read(0, buffer_u8); // This also WORKS read the buffer
As you can see a_buffer and a_surface are interchangeable and this can lead to messy code and bugs.


[MEMORY LEAKS]


(1) WHAT ARE THOSE?
People usually get frighten with the term memory leak even though sometimes they do not know what it actually is.

A memory leak is leak on resources (lists/grids/buffers/...) that result in an continuous increase of memory usage that leads to a decrease in system performance and eventually crash. These leaks are often related with incorrect memory management.​
But what does that big sentence means? Let's look at the spreadsheet example again, if we keep creating resources one after the other eventually the spreadsheet will have hundreds or thousands of entries. This entries need memory space so we will reach a point where the system memory is not enough leading to a crash. The solution would be correctly managing the memory and that means removing resources from that hidden table as they cease to be used.

If you look at the CONS list in the section above you can read:
  • You need to manually destroy the element
This sentence is actually the memory management we are talking about. This happens because the handle for these resources is just a number so the element will be kept inside the hidden table until you tell GMS you no longer need it.

NOTE: When an resource needs to be destroyed we say that it is NOT garbage-collected (on the other hand arrays & structs ARE garbage-collected)​

(2) WHAT RESOURCES CAUSE MEMORY LEAKS?
Here is the list of all the resources you need to manually manage and delete upon finishing using them.
I'll try to keep the list as complete as possible:
  • Buffers
  • Surfaces
  • Data Structures (list/map/grid/stack/queue/priority)
  • Cameras
  • Particles (part_emitter/part_system/part_type)
  • Dynamically Created Assets (rooms/sprites/timelines/paths/fonts/sequences/animcurves) (created through code)
  • Audio Emitters
The list above doesn't mean that we need to create and destroy these right away every step. Because if you need something during the step event
of some instance you probably better of creating it in the [create event] and destroying in the [destroy/cleanup event].

NOTE: Instances are not in the list above, but you still need to destroy them when no long in use. However, differently from the ones on the​
list above, when leaving a room, all active instance get automatically destroyed. There is an exception for the deactivate instances since these​
will not be destroyed and can lead to memory leaks (extra management is needed if you use instance deactivation).​

(3) SOLUTION
We already understand that to deal with this problem we need to manual remove dynamic resources from memory but how can we do that?
As well as GameMaker provides function to create resources it also provides built in functions to destroy those same resources when we are finished with them.

For example if you take a look at the autocomplete while trying to create a list, one or the suggestions that come up in the list is ds_list_destroy.
This will require a handle (row number) of the list and will tell GameMaker Studio to remove the resource from memory.

These *_destroy, *_free (surfaces) and *_delete (sprites, paths, timelines) are the ones to look for :)
Keep an eye on them.

(4) MEMORY MONITORING
You probably might be asking yourselves how can you monitor memory usage while your game is running. And that is actually pretty simple you can do so
using the debugger. Running the project in debug mode and then going to through the menu Debugger > Windows > Graph, will bring up a graphic where you
can control the memory being used.

If you spot an increase of the memory being used over time then your code is probably suffering from a memory leak problem :)


[OTHER PROBLEMS]

I considered adding this section because there are a lot of comments in the forums/discord stating that "x function is broke and not working for me".
This will be address some of those functions as you might now understand what the problem is. So I'm essentially talking about:
  • ds_exists
  • buffer_exists
  • surface_exists
  • ...pretty much all the exists functions...
The problem with these functions is that what they actually check is if a given line in that "hidden table" is occupied and that wouldn't be a problem unless...
row numbers got reused and that is exactly the case. When you destroy a ds_list for example that row in the "hidden table" is now free and available and
GMS2 will recycle those rows and reuse them for the next ds_list you create. Let's look how this leads to a problem in the code below:

GML:
var _list = ds_list_create(); // let's imagine the returned handle is 0.

ds_list_destroy(_list); // this will make row 0 available.

var _newList = ds_list_create(); // As row 0 is available GMS2 will return it.

// At this point _list == 0 and _newList == 0

ds_exists(_newList, ds_type_list); // This returns true
ds_exists(_list, ds_type_list); // This will also return true

// Because what you are actually doing is:
ds_exists(0, ds_type_list); // The line is occupied so the list exists.
The same applies to all the *exist* functions rendering them a little useless.. OR maybe not, completely!
We can actually overcome this particular problem with a little line of code:

GML:
ds_list_destroy(_variable); // after destroying a variable
_variable = -1; // Set it to an invalid row number
This is not ideal nor is 100% bullet proof as we still have a problem with variables being interchangeable but at least will help reducing the bugs.


[CONCLUSION]
And that's it, types/memory leaks in a nutshell!
  • GML true types are reals/int/string/array/struct/function
  • All of the other types are actual pseudo-types
  • When you create a pseudo-type resource you are given an handle to it.
  • The handle is actually a number that represents the row where GMS can find the resource inside an "hidden table".
  • Pseudo type resources need to be managed manually so you need to destroyed them to avoid memory leaks.
That was a good amount of information so remember to go through it a couple of times to understand it well.


Here xD from xDGameStudios,
Good coding to you all.
 
Last edited:

chance

predictably random
Forum Staff
Moderator
I think this general discussion could be useful, but a few sections could be improved. For example, in section (2) under Types, perhaps you could more carefully distinguish between the data type of an array's handle, and the data type of an array's contents (which might be strings, for example).

Likewise, the section on memory leaks is a bit vague. Despite saying what can cause memory leaks, you don't actually explain what a memory leak is (in terms of increasing memory usage and decreasing system performance). Also, it's worth mentioning the built-in functions for resource removal, and how to monitor memory usage when the game is running.

I realize you can't discuss each topic in complete detail. But a few extra sentences here and there would help.
 
Top