Skip to content

Using @hint comments

Vadim Dyachenko edited this page Jan 24, 2021 · 7 revisions

GameMaker Studio 2.3 brings exciting changes including structures (lightweight, anonymous objects) and constructors.

This might make it tempting to structure your code in object-oriented way... until you realize that GML being a dynamic language means that you don't get contextual auto-completion when doing Something.¦ or myVar.¦. But, not to worry, GMEdit can help you with this.

This feature further builds upon local variable types along with its own syntax.

In and out of 2.3, @hint can also be used to mark fields of globalvars or macros.

General idea

If you tell GMEdit which variables/functions your type has, GMEdit will use that information to show you just that;

For general uses, you will not need to do much at all.

General use

For constructors with standard-issue static variable declarations and assignments, you only have to subsequently specify the type for your local variables to enjoy the features.

So, if you had

function Vec2(_x, _y) constructor {
	x = _x;
	y = _y;
	static add = function(v) {
		return new Vec2(x + v.x, y + v.y);
	}
	static toString = function() {
		return "Vec2(" + string(x) + ", " + string(y) + ")";
	}
}

and then wrote var v:Vec2;, saved (for GMEdit to take note of added type), and typed v., you would get an auto-completion list with only your 2 variables and 2 functions in it, along with correct arguments for those functions.

Syntax

Comment syntax is as following:

/// @hint [{type}] [new] [TypeName][.:][field][(...arguments)[->returnType]] [...description]
1         2        3     4         5   6      7              8               9
  1. Comment start and @hint meta. Spaces can vary.
  2. Optional type for fields.
    (note: when using with methods, this is the type of function itself, not the return type)
  3. new keyword if what you are declaring is a constructor.
  4. Type name.
    Can be omitted if your hints are inside a top-level function/constructor.
  5. . to declare a static field (accessed as Type.field) or : to declare an instance field (accessed as var v:Type and then v.field).
  6. Field/function name.
    Can be omitted (along with .) if you want to specify constructor/self-call arguments (more on this later)
  7. If the field is a function, you may also specify arguments.
  8. If the field is a function, you may specify a return type.
    This should contain no spaces; -> arrow will be converted to .
  9. Optional short description, which will be shown in auto-completion popups on a second line.

A few special versions are also available:

/// @hint TypeName extends ParentTypeName

Marks ParentTypeName as parent of TypeName, inheriting any non-static variables (much like object/struct inheritance would)

/// @hint TypeName implements InterfaceName

Adds InterfaceName to list of TypeName's interfaces, much like @implements would.

Examples

Suppose you have the aforementioned Vector2 constructor and decide that it'd be really nice to have some static helper functions.

You cannot assign variables to a top-level function (as it is compiled into a raw ID), but you could create a global variable and assign a constructor into it (which would now be a method-struct), subsequently also assigning your static methods:

globalvar Vector2;
Vector2 = function(_x, _y) constructor {
	x = _x;
	y = _y;
	static add = function(v) {
		return new Vec2(x + v.x, y + v.y);
	};
	static toString = function() {
		return "Vec2(" + string(x) + ", " + string(y) + ")";
	};
};
Vector2.Lerp = function(a, b, t) {
	return new Vector2(lerp(a.x, b.x, t), lerp(a.y, b.y, t));
}

Reasonably enough, this is the exact point where the normal auto-completion would poop the pants - there's no way of knowing for sure what you'll assign into Vector2 variable, even with complicated code analysis.

With @hint, you could then add the following after your declarations:

/// @hint new Vector2(x, y)
/// @hint Vector2:add(v)->Vector2
/// @hint Vector2:toString()->
/// @hint Vector2.Lerp(v1, v2, factor)->Vector2

which would suggest you the Lerp function when typing Vector2. and your instance functions when doing var v:Vector2 .. and then typing v..

self-call

It is possible to use @hint to annotate callable types, like so:

function Array() {
    var _arr = array_create(argument_count);
    for (var i = 0; i < argument_count; i++) _arr[i] = argument[i];
    //
    var this = method({ arr: _arr }, function() {
        if (argument_count > 1) {
            arr[@argument[0]] = argument[1];
        } else return arr[argument[0]];
    });
    with (this) {
        arr = _arr;
        function resize(newsize) {
            array_resize(arr, newsize);
        }
        function toString() {
            return string(arr);
        }
    }
    return this;
}
/// @hint Array(...values)
/// @hint Array:(index, ?newValue)
/// @hint Array:resize(newsize)
/// @hint Array:toString()->string
function scr_hello() {
    var a = array("a", "b", "c");
    trace(a(1)); // getter, returns "b"
    a(1, "hi!"); // setter, changes item 1
    a.resize(4); // 
    trace(a); // [ "a","hi!","c",0 ]
}

This is obviously fairly exotic, but can be handy for an occasion.

Templates

You can use templates both after types and after fields, e.g.

/// @hint new ArrayList<T>()
/// @hint ArrayList<T>:push(...values:T)
/// @hint ArrayList.create<T>(size:int, ?value:T)->ArrayList<T>

Notes and limitations

  • When using /// @hint Type(...) or /// @hint new Type(...) specifically, hint-comment should be after the globalvar or #macro declaration of the base field.