Overview

Class autoregistration is a mechanism that allows the users to leave GDNativeGo to register their classes in their behalf, considerable reducing the amount of boilerplate they have to write in order to register their code with in Godot as well as offering transparent handlers for also reducing overhead on method signatures.

Despite of class registration being extremely useful, it can also be less flexible in terms of in Go context struct values initialization, even if we provide of a constructor hook, the users need to note that they will not be able to control how new struct values are added into the classes hash map and that a hash map named <UserClassName>Instances will be automatically created and used in order to register their classes. So for example, if an user creates a class Temperature using class autoregistration, GDNativeGo will create a TemperatureInstances hash map in order to track them. Also a wrapper will be created with the name TemperatureWrapper, this struct is meant to be used internally by GDNativeGo so users shouldn’t need to worry about it.

Class autoregistration and manual class registration can not be used in the same NativeScript library file

Autoregistration Tool

GDNativeGo autoregistration works combining specific comment formatting and a command line tool called gogdc that you should have already installed in your $GOPATH/bin or your go env GOBIN if it is set.

Add your $GOPATH/bin or go env GOBIN path to your user $PATH environment variable so installed with go get or go install binaries will be available in your terminal automatically.

The gogdc application statically analyzes your Go files in a directory and creates additional autogenerated Go code in order to register your classes with in Godot, this is way more performant and clean than using the reflect package on runtime.

Comment Formatting

GDNativeGo defines just four comment formatting directives and they are very easy to remember and use.

We aren’t fans of using comments as code or triggers but we find it to be the only way to have autoregistered structs living together in the same code with structs that must remain unknown to the Godot engine

Class Autoregistration

To tell GDNativeGo to autoregister a Go struct we just have to add the comment //godot::register directive just above the struct definition, for example:

// SimpleClass is a structure that we can autoregister with Godot
//godot::register
type SimpleClass struct{}

When gogdc analyzes the file containing this struct, it will parse the directive and automatically register the struct in our behalf

Class aliases

GDNativeGo will always use the struct name as name for the new registered class in Godot, but sometimes is useful to register a class with an alias in Godot context. GDNativeGo offers a way to specify we want an struct to be referenced with a different name with in Godot so for example, and continue with our example above, if we wanted to register SimpleClass as SIMPLE and reference it like that in GDScript we could do it using the as directive:

// SimpleClass is a structure that we can autoregister with Godot
//godot::register as SIMPLE
type SimpleClass struct{}

Constructort and Destructor

When GDNativeGo autoregisters structs it always generates its own constructor and destructor. This is necessary for GDNativeGo be able to keep track of instanced values of our structures in memory and add or delete them from the registry when necessary, but we have to define and provide of a function that is responsible of creating a new value of our struct and return a pointer to it.

We don’t have to provide any kind of destuctor function for our structures, the garbage collector takes cares of it for us

We can define any function as our constructor, for example:

// New creates a new SimpleClass value and returns a pointer to it
//godot::constructor(SimpleClass)
func New() *SimpleClass {

	sc := SimpleClass{}
	return &sc
}

GDNativeGo will call the function above when Godot instructs it to instantiate a new object of the SIMPLE class.

Methods

Not all methods of an struct get automatically exported and made available into Godot, just those that are explicitely exported using a directive are taken into account by gogdc to made them available within Godot, in order to do so, one has to use the //godot::export directive just above the method definition, for example:

// GetData is automatically registered to SimpleClass on Godot
//godot::export
func (sc *SimpleClass) GetData() gdnative.Variant {

	gdnative.Log.Println("SIMPLE.get_data() called!")

	data := gdnative.NewStringWithWideString("Hello World from gdnative-go instance!")
	return gdnative.NewVariantWithString(data)
}

Unexported Go methods will not be used by gogdc thus not being available into Godot’s context even if they are decorated with the directive, this decission was taken in order to maintain a consistent behavior with Go’s semantic rules

Method aliases

As in the case of classes, methods are exported using the same name that they are defined with in Go code, if the user want to maintain GDScript naming guidelines or want to change how the function name looks in GDScript for any other reason, can do it using the as directive in the exact same way that classes, for example:

// GetData is automatically registered to SimpleClass on Godot
//godot::export as get_data
func (sc *SimpleClass) GetData() gdnative.Variant {
    ...

Properties autoregistration

Properties are a bit special as they use struct field tags instead of comment directives. This is more adequate than comments due the number of options one can configure for them. Any user that has used the JSON package in Go will be already familiar with the syntaxis of them.

Even if they are defined as field tags, these are not evaluated on runtime but parsed and analyzed by the gogdc tool on code generation time so they are as performant as other code generated using comment directives, the reflect package is not used at all on runtime by GDNativeGo

// SimpleClass is a structure that we can register with Godot.
//godot::register as SIMPLE
type SimpleClass struct {
	HP          gdnative.Int   `hint:"range" hint_string:"The player's Hit Points"`
	Mana 		gdnative.Int   `hint:"range" hint_string:"The player magic points to cast spells"`
	Blood 		gdnative.Int   `hint:"range" hint_string:"The player blood points to execute actions"`
	IgnoreMe    gdnative.Float `-` // this property will be ignored
}

Following our example from earlier we define a SimpleClass struct that will be registered (and seen by Godot’s scripts) as SIMPLE while defining some properties on it.

  • HP is a gdnative.Int variable that uses an int range as hint and the string “The player’s Hit Points” as hint text
  • Mana is also a gdnative.Int variable that uses an int range as hint and the string “The player magic points ot cast spells”
  • Blood is again a gdnative.Int variable also with a range as hint and the string “The player blood points to execute actions”
  • IgnoreMe finally is ignored using the _ operator and will not be available on Godot’s context

Signals autoregistration

Signals can also be auto-registered but they need to declared and assigned using literal syntax on the class constructor so there is not much boilerplate reduction in this case, following our example, one can define a signal for registration like the example below

// SimpleClass is a structure that we can register with Godot
//godot::register as SIMPLE
type SimpleClass struct {
	Hit gdnative.Signal
}

// New creates a new SimpleClass value and returns a pointer to it
//godot::constructor(SimpleClass)
func New() *SimpleClass {

	sc := SimpleClass{
		// Signals must be defined as literals or they will be ignored by the gogdc compiler
		Hit: gdnative.Signal{
			Name:           "hit",
			NumArgs:        gdnative.Int(1),
			NumDefaultArgs: gdnative.Int(1),
			Args: []gdnative.SignalArgument{
				{
					Name:         gdnative.String("power"),
					Type:         gdnative.Int(gdnative.VariantTypeInt),
					Hint:         gdnative.PropertyHintRange,
					HintString:   "Hit power value",
					Usage:        gdnative.PropertyUsageDefault,
					DefaultValue: gdnative.NewVariantInt(gdnative.Int64T(0)),
				},
			},
			DefaultArgs: []gdnative.Variant{
				gdnative.NewVariantInt(gdnative.Int64T(0)),
			},
		},
	}
	return &sc
}