GML Updates in 2019

As you may have seen in our GMS2 2019 roadmap we have ‘GML Updates’ listed for Q4. When we polled our community last year, the extension of GML received overwhelming support. In anticipation of these improvements, I would like to share with you an overview of the features we’re introducing and the thinking behind our approach. Updates to GameMaker Language and the other changes coming in version 2.3 will be available later this year. We will be going into more depth on the rest of version 2.3 in future tech blogs.
CHAINED ACCESSORS
Historically array accesses in GML have only been 1 deep; a variable can only be dereferenced once in an expression. This has meant that an array
foo
foo[index]
This could not be expanded to multiple dimensions (as many users have desired over the years), and we are changing this to allow any number of dimensions to be added so that it will be possible to do:
foo[index1][index2][index3]
This will perform as expected (if the multiple levels are arrays) and will cause errors if any part of the chain is not an array.
We will be expanding this to include general accessors, and not just the array accessor, so that mixed accessors can be chained together, e.g.:
foo[ index1 ][# index2, index3 ][? “key” ]
This would dereference the array
foo
A side effect of this feature is that all arrays in GML will now be 1-dimensional, and the 2-dimensional syntax of:
foo[ index1, index2 ]
Will be the equivalent of doing:
foo[index1][index2]
This should be transparent for older projects and everything will just work as normal.
As an implementation detail, all GML arrays have been 2-dimensional at runtime, and the runtime has jumped through hoops to hide that from users. This change should speed up all array accesses as it does not need to index two arrays anymore – it should also help memory fragmentation as an array now only has one axis to be allocated.
The only projects that will have issues here are projects that have taken advantage of implementation details in historic GML and have hardcoded any of the following:
- Mixing use of 1-dimensional and 2-dimensional accesses on the same array
- Using the internal 32,000 axis limit in some way when accessing the array (this limit has been removed)
- Using functions (these will need to be changed to get the correct 1-dimensional size)
array_length_2d
We will also be adding an
array_length
array_length_1d
array_length_2d
array_height_2d
METHOD VARIABLES
The ability to declare a function in an expression is being added to GML. This function will be bound with the
self
This means that it will be possible to do:
var log = function(a) { show_debug_message( a ) } log( “Hello World” );`
The important things to note here are that the function is anonymous and the arguments have user-defined names (not just argument0…argument15). All functions can have properly named arguments now!
A named function works similarly, and you could write the above like this:
function log(a) { show_debug_message( a ); }
In this case, the function
log
The contents of these functions can be anything that could be inside a normal script as they exist now. The important thing is that the
self
self
with( objEnemy ) { function foo( a ) { //…. } }
This would declare a variable
foo
objEnemy
Functions declared this way do not have access to the local variables (declared with
var
Methods can be assigned to any variable and all rules for variables apply to them: they can be passed around as arguments and can be used as callbacks easily for library functions. For example:
my_load_async( “filename”, function( success, error ) { if (success) { show_debug_message( “filename successfully loaded” ); else show_debug_message( “error loading filename - “ + string(error) ); });
This calls a library function
my_load_async
Methods can also be used in preference to User Events and effectively gives the ability to create interfaces that can be inherited through the normal parent-child relationships of objects.
A new function
method( <instance>, <original-method> )
self
MULTIPLE SCRIPTS IN A SINGLE SOURCE FILE
As we now have a syntax which requires function declaration, we will be changing script resources. Instead of a script being a single body of code, it can contain multiple named functions. Any existing projects will be automatically updated for this new format.
For example, a project that contains two script resources
Foo
Bar
In the script
Foo
function Foo( argument0 ) { /// contents of the Foo script in here... }
In the script
Bar
function Bar( argument0, argument1, argument2 ) { // contents of the Bar script in here... }
This change will mean that the name of the script resource does not need to be the same as the functions inside it, and many functions can be present inside a single script resource (it is more of a source file resource now).
The script resource is now executed at global scope, so the
Foo
Bar
Foo
Bar
global.Foo
global.Bar
gml_pragma( “global”, … )
LIGHTWEIGHT OBJECTS
We are introducing the concept of a lightweight object into GML. Lightweight objects are accessed like an instance but have no built-in variables or behaviours associated with them, like the events of an object resource. Because of this, they can be used to create your own data structures. They can be viewed as a JavaScript object, a C# Dictionary or a C++ std::map.
An example of a lightweight object in GML:
var a = { foo : “Hello”, bar : “World”, func : function( a, b ) { // insert function code here. } ex : global.my_variable, num : 10 * argument0, // in general the syntax is // <variable-name> : <expr-to-assign-to-variable> };
Any expression is evaluated in the current context of when the object literal is executed – i.e. when the local variable
a
This is an object literal syntax, and, like the array literal syntax (introduced early on in GMS2), it is relatively simple and allows the user to declare an object in line with the code.
Lightweight objects are similar to instances in that they can hold variables (and these are accessed and used in the same way), but unlike instances they cannot be added into rooms – they are pure data and not instances of object resources. Lightweight objects can be passed around like any other variable in GML and can be used as a data structure with methods and essentially act as a
class
The
typeof
“Static” variables on a lightweight object will be supported.
‘NEW’ OPERATOR
By introducing a
new
new
new
Given this
Vector3
function Vector3( _x, _y, _z ) { x = _x; y = _y; z = _z; add = function( _v ) { x += _v.x; y += _v.y; z += _v.z; } } var vec = new Vector3( 10, 20, 30 );
We can create an empty lightweight object, pass that as the
self
Vector3
add
vec
This gives a simple and consistent method for the creation of lightweight "class"-like objects that can be provided through simple libraries.
We will also be providing a
delete
EXCEPTION SUPPORT
Runtime errors in GML have traditionally stopped an executing game abruptly and shown a corresponding error dialog to users. In this update we are promoting runtime errors in GMS2 to be exceptions and providing new functionality to allow users to create
try-catch-finally
throw
Our current error dialog will be used for any uncaught exceptions that are created when running a game, and if no code catches them then you will continue to see the current dialogs at runtime.
You can use try-catch-finally as follows.
try { // code to execute while monitoring exceptions } catch ex { // do something if an exception is caught // note the variable ex will hold the exception that has been caught } finally { // this code in here will always be executed after the try (or the catch) sections have executed }
This is similar to other languages and will provide a user mechanism to intelligently handle errors in a better way within their own code.
We will also be providing a mechanism to replace the
unhandled exception
It is important to note that some errors will still be catastrophic and cannot be handled as exceptions (e.g., some pathological “Out of Memory” errors and OS-level exceptions) as they do not always happen within a GML context.
GARBAGE COLLECTOR
Between method variables and lightweight objects, we are seeing more memory traffic and less obvious ways of manually managing it, so we have added a fully multi-threaded, multi-generational garbage collector that handles the lifetime of all variables and instances inside the GMS runtime.
This runs once a frame to collect the first generation and cleans up any variables and instances that have been forgotten about in the last frame. All the collection itself is done on another thread, so this frees the main thread to concentrate on the execution of the game.
The collector is very fast and has minimal impact on the running game, and additionally, we will be introducing a suite of functions for controlling some of the behaviour – but the specifics of that has not been finalised at this time.
CONCLUSION
With these new features, GML is becoming a more mature language, gaining features that should give our users a higher quality of life and allow for more and better GameMaker Studio games. Following the release of this blog we post a thread on the GameMaker Community forums that will aim to field questions about these GML improvements and this blog. The post will detail how to submit questions and later we will return with a follow-up post that answers some of these questions.
