Runtime scripts

Ashley's avatar
Medal
Construct Team Founder
Published 26 Apr, 2018
1,696 words
~7-11 mins

Plugin and behavior addons have separate scripts that run in the context of the runtime (the Construct game engine) rather than the editor.

The C2 and C3 runtimes

When Construct 3 was first released, it continued using the same runtime as Construct 2. This helped Construct 3 release sooner and made it easier to port existing C2 addons to C3.

Now Construct 3 has its own brand-new runtime, referred to as the C3 runtime. This has been rebuilt from the ground up. All the APIs are different, and therefore addons need to write their runtime scripts specifically for the C3 runtime in order to support it. If addons do not support the C3 runtime, projects using the addon cannot be switched to the C3 runtime unless the addon is removed, which impedes user's ability to use the latest features and discourages use of the addon.

The plan is to ultimately deprecate and remove the C2 runtime and only have the C3 runtime. This should simplify addon development since there will only be one runtime. However for compatibility reasons this is likely to take a long time. Addons originally developed for C2 will likely have to maintain both runtimes. If you are starting a new addon and don't want to have to write the runtime part twice, we recommend only supporting the C3 runtime, since this will be the only supported option in the long term. The C3 runtime also has a much better designed API.

The C2 runtime

Since the C2 runtime is derived from Construct 2, please refer to the C2 SDK documentation for more details.

The C3 runtime

For more details about the C3 runtime, please refer to the section on the Runtime API reference.

C3 runtime API calls

In the C3 runtime, much code is shared with the editor, such as the WebGL renderer class. However in the editor, API calls are behind a special SDK layer (usually within the SDK namespace), for security and compatibility reasons. On the other hand the runtime does not use a special SDK layer, for maximum performance and flexibility; these calls are usually within the C3 namespace. Be sure to use the right namespace depending on the context.

Supporting the runtimes

For backwards compatibility, if an addon does not indicate anything about which runtimes it supports, Construct 3 will assume it only supports the C2 runtime. Addons can use the SetSupportedRuntimes() method of IPluginInfo and IBehaviorInfo to specify which runtimes are supported.

For example the following line indicates an addon supports both runtimes:

	this._info.SetSupportedRuntimes(["c2", "c3"]);

Alternatively addons can opt-in to only supporting the C3 runtime:

	this._info.SetSupportedRuntimes(["c3"]);

If an addon does not support a runtime, Construct 3 will not display it when the user is creating an object or behavior. It will also prevent the user switching the runtime in use if the addon is not supported in the chosen runtime. In this case Construct 3 will show a message telling the user that they cannot switch the runtime because the addon is not supported there.

If an addon supports the C2 runtime, it must have a c2runtime subfolder, typically only containing a file named runtime.js which contains everything. If it supports the C3 runtime, it must have a c3runtime subfolder, which typically has several files since the C3 runtime splits code across separate files for better organisation.

The addons in the SDK downloads support both runtimes in order to demonstrate how to use both. However as mentioned previously for new addons it may be easier to only support the C3 runtime, saving you having to write the runtime code twice. In that case you can change the SetSupportedRuntimes() call and delete the c2runtime subfolder from the addon.

DOM calls in the C3 runtime

A major architectural feature of the C3 runtime is the ability to host the runtime in a dedicated worker, off the main thread. In this mode it renders using OffscreenCanvas. With the modern web platform, many functions and classes are available in dedicated workers, and more are being added over time. Refer to this MDN guide on available APIs in workers.

Providing your addon's runtime calls only use APIs available in a worker, such as fetch() or IndexedDB, then it will not need any changes to support a worker. However if it does use APIs not normally available in a worker, then it will need some changes.

The principle behind making calls to the main thread in the C3 runtime is to split the runtime scripts in to two halves: the runtime side (that runs in the worker), and the DOM side (that runs on the main thread where the document is). The DOM side has full access to all browser APIs. The runtime side can issue DOM calls using a specially-designed messaging API built in to the runtime. Essentially instead of making a call, you simply post a message with parameters for the call to the script on the DOM side, where the API call is really made. The DOM side can then send a message back with a result, or send messages to the runtime on its own, such as in response to events. The messaging APIs make this relatively straightforward. However one consequence to note is that a synchronous API call will become asynchronous, since the process of messaging to or from a worker is asynchronous.

Once this approach is used, there is no need to change anything to support the normal (non-Worker) mode. In this case both scripts will run in the same context and the messaging API will simply forward messages within the same context too. Therefore this one approach covers both cases, and ensures code works identically regardless of whether the runtime is hosted in the main thread or a worker.

Using a DOM script

By default Construct 3 assumes no DOM scripts are used. If you want to use one, use the following call on IPluginInfo to enable one:

this._info.SetDOMSideScripts(["c3runtime/domSide.js"]);

Since an array of script paths is used, if you have a lot of DOM code, you can split it across different files. Don't forget to add these files to the file list in addon.json.

For documentation on the DOM messaging APIs, refer to DOMElementHandler (used in domSide.js), SDKDOMPluginBase (used in plugin.js), and SDKDOMInstanceBase (used in instance.js).

For an example demonstrating how to get started, see the domElementPlugin template in the C3 plugin SDK download. This demonstrates using the above APIs to create a simple <button> element in the DOM with a custom button text, and firing an On clicked trigger, with support for running in a Web Worker.

Supporting the C3 runtime debugger

Plugins and behaviors can display custom properties in the C3 runtime debugger by overriding the GetDebuggerProperties() method of the instance class. It should return an array of property sections of the form { title, properties }, where title is a string of the section title and properties is an array of property objects. Each property object is of the form { name, value } with an optional onedit callback. The name must be a string, and the value can be a string, number or boolean.

Editing properties

If an onedit callback is omitted, the debugger displays the property as read-only. If it is provided, the debugger allows the property to be edited. If it is changed, the callback is run with the new value as a parameter.

In many cases, editing a property does the equivalent of an action. You can use the CallAction helper method to map property edits to calling actions. For example the Sprite plugin uses the following onedit callback to make edits call the "Set animation frame" action:

onedit: v => this.CallAction(C3.Plugins.Sprite.Acts.SetAnimFrame, v)

Translation

By default, property section titles and property names are interpreted as language string keys. This allows them to be translated by looking them up in your addon's language file. Note property values do not have any special treatment. You can bypass the language file lookup by prefixing the string with a dollar character $, e.g. the property name "plugins.sprite.debugger.foo" will look up a string in the language file, but "$foo" will simply display the string foo.

The debugger runs in a separate context to the editor, and as such not all language strings are available. The language keys available in the debugger are:

  • The addon name
  • All property names
  • All combo property items
  • Everything under the "debugger" key

In general, if you need a language string for the debugger, simply place it under the "debugger" key, e.g. at "plugins.sprite.debugger.foo".

Sample code

The following code is used by the Sprite plugin to display its animation-related debugger properties. Notice how it uses language keys and calls actions to update properties.

GetDebuggerProperties()
{
	const Acts = C3.Plugins.Sprite.Acts;
	const prefix = "plugins.sprite.debugger.animation-properties";
	return [{
		title: prefix + ".title",
		properties: [
			{name: prefix + ".current-animation",	value: this._currentAnimation.GetName(),	onedit: v => this.CallAction(Acts.SetAnim, v, 0)},
			{name: prefix + ".current-frame",		value: this._currentFrameIndex,				onedit: v => this.CallAction(Acts.SetAnimFrame, v)},
			{name: prefix + ".is-playing",			value: this._isAnimationPlaying,			onedit: v => (v ? this.CallAction(Acts.StartAnim, 0) : this.CallAction(Acts.StopAnim))},
			{name: prefix + ".speed",				value: this._currentAnimationSpeed,			onedit: v => this.CallAction(Acts.SetAnimSpeed, v)},
			{name: prefix + ".repeats",				value: this._animationRepeats,				onedit: v => this._animationRepeats = v}
		]
	}];
}