Skip to main content

Introduction to qScript

img-lightimg-dark

qScript is an abbreviation for Quantum Script, or code which runs in the context of Quantum non-linear events.

Quantum is a framework by XGASOFT which allows assigning IDs to blocks of code, thereby controlling when and how the code runs. Out of the box, Quantum provides a set of common practical functions to manipulate code behavior in groups called events. By designing custom actions based on the provided templates, it is possible to create entire 'meta-languages' which are largely cross-compatible with any programming language to which Quantum is ported. These custom meta-languages are broadly referred to as qScript.

The first publically-available implementation of qScript can be seen in VNgen by XGASOFT. For the rest of this introduction, we will refer to VNgen 1.0's GML implementation as an example, though infinite other possible implementations exist--including in newer versions of VNgen.

Event Structure#

qScript is structured as a hierarchy of events and actions, with some actions occasionally functioning as sub-events. Each event is self-contained, and will be executed in sequence by default. Any actions within an event are executed simultaneously when the containing event is active. Once all of an event's actions are complete, the current event will deactivate and the next event will be activated in its place. This process repeats itself until all events have been executed or the event timeline is terminated prematurely by the user.

Although Quantum itself does not require that events and actions be triggered by logical operators, for the sake of performance, Quantum events are usually designed to be preceded by an if statement, with actions following between brackets.

Example#

if (vngen_event()) {
//Action 1
//Action 2
//Action 3
}

This sequence creates a single Quantum event, which should always be executed every cycle (frame, step, update, tick, etc.). In GameMaker Studio, that means running your qScript in the Step Event of the current object. This allows Quantum to take over deciding when code is actually executed.

important

All qScript events must run in the same context (e.g. GameMaker object Step Event), and all qScript actions must run inside a qScript event.

It is also not advisable to run non-Quantum code inside qScript events. However, qScript actions do exist to 'wrap' other code, allowing it to behave as a qScript action without conversion.

While you may assume that running the same vngen_event function in an if statement multiple times will always return the same result (either true or false), Quantum adds a third, indeterminate state. This enables each if statement to return a unique result based on the corresponding event's internal ID, which is generated automatically. Like indeterminate states in quantum physics, this unique and powerful functionality is what earned this framework its name!

Initialization Process#

In GameMaker Studio, Quantum is initialized in three phases:

  1. The object Create Event
  2. The Quantum event target operation
  3. The first active frame of a Quantum event

The first phase handles initialization of properties and variables that must be available prior to qScript running for the first time. This is achieved by running a simple *_init script in the object Create Event. Each product built on the Quantum framework can add its own initialization process to this script to support additional functionality.

Example#

vngen_object_init();

The second phase occurs in the object's Step Event, where all qScript is located. Here, *_event_set_target and *_event_reset_target bookend the qScript and perform essential functions for managing the framework as a whole. The first time an object's Step Event is run, *_event_reset_target will analyze the qScript and record the number of events to perform. In future iterations of the Step Event, *_event_set_target will refresh properties from the previous step to prepare the qScript to run again.

Example#

vngen_event_set_target();
if (vngen_event()) {
//Action 1
//Action 2
}
if (vngen_event()) {
//Action 1
//Action 2
}
vngen_event_reset_target();

The third and final phase occurs when each individual Quantum event activates. For one step, any actions contained within the active event can run any code necessary to prepare for future behaviors.

Unlike the previous two phases, this phase does not occur immediately when the object is created. This is important, as it means qScript actions must be designed with no knowledge of when they will run. There is no limit to how long or brief an individual action may be, and until all actions are complete or skipped by the user, the next event will not be activated.

In short, Quantum initializes in a hierarchy, from Object to qScript to Quantum event. Object initialization occurs once for the entire lifetime of the object, while qScript initialization occurs once every frame, and Quantum event initialization occurs once for each event when it becomes active.

Understanding this process will aid greatly in your use and development of custom qScript.

Event Manipulation#

A simple way to visualize Quantum events is to think of them as nodes on a timeline. As such, it is often desirable to alter individual events' behavior in a few key ways, such as:

  • Timing
  • Persistence
  • Execution order

As it turns out, these three keys pertain directly to three arguments which can be optionally supplied to each Quantum event in your qScript. Those arguments are as follows:

if (vngen_event([pause], [noskip], [label])) { }

First, setting an event pause will delay execution of all further actions by the number of seconds supplied. As mentioned previously, events progress automatically when all actions inside are complete. Sometimes, stringing together multiple events seamlessly can feel too abrupt, or occur too quickly for the user to keep up. Skillfully employing event pauses is essential for creating a smooth sense of flow, and in the case of sequenced animations, precise timing is critical.

tip

You can also add pauses between actions within an event with the *_event_pause script

Second, enabling noskip will force an event to persist for its entire duration even if the user attempts to skip it. Normally, events can be ended prematurely by the user, in which case they will complete instantly. But many occasions exist where this behavior is undesirable, such as displaying important information or performing time-sensitive actions. However, removing control from the user is a powerful tool and should be used responsibly. Some actions may even override this setting to prevent scenarios where events become impossible to complete due to user input being required.

The final argument, the event label, will assign a string to identify the Quantum event (in addition to the automatically-generated numeric ID). Though not required, labels are inherently more memorable and become quite useful in combination with *_goto. As the name implies, the *_goto function will go directly to a different Quantum event in the qScript, skipping any other events in-between. With Quantum, event execution is non-linear. Labels are a great way to simplify jumping anywhere in the timeline, forwards or backwards, at any time--even to other objects! You are in full control over the order your events are executed.

Example#

vngen_goto("my_event");

Event arguments can be supplied in any order and any combination, but be aware that numeric values will always be interpreted as 'pause' first, then 'noskip' (as true and false are interpreted as 1 and 0, respectively).

Example#

vngen_event_set_target();
if (vngen_event("first_event")) {
//Action
}
if (vngen_event(2, "delayed_event")) {
//Delayed action
}
if (vngen_event(0, true, "last_event")) {
//Action
vngen_event_pause(2);
//Delayed action
}
vngen_event_reset_target();

Action Structure & Manipulation#

Quantum is a framework. While the nature of Quantum events are clearly and carefully defined, Quantum actions are much less so. Actions are the heart of qScript, and very few limits are imposed on their syntax and capabilities. Actions can even behave as sub-events, with custom groups of actions of their own!

However, code contained in a Quantum event is not automatically a Quantum action! In fact, plain code should almost never be written inside Quantum events, as it will inherit none of the framework's properties and behaviors and may not perform as expected. To properly execute code as part of the Quantum framework, it must be written as a Quantum action, or 'wrapped' with the *_script_execute or *_code_execute actions.

Example#

if (vngen_event()) {
vngen_script_execute(my_script, my_first_argument, my_second_argument);
if (vngen_code_execute()) {
my_string = "Hello, world!";
}
}

Though useful for quickly integrating custom code into qScript, these actions should only be used as a fallback, not a replacement for proper Quantum actions. Wrappers will always have a duration of zero, and will execute only once when the event is active, after which they will be immediately complete. To give code duration and other more advanced behavior, it must become a Quantum action.

Although actions can only be loosely defined and always require bespoke code, they do follow a common template using system functions provided by the Quantum framework to simplify action development.

Like events, Quantum actions are performed in three phases:

  1. Initialization
  2. Response
  3. Finalization

As mentioned previously, Quantum's namesake feature is its use of indeterminate states. Each time an action is executed, it will be determined to be active, inactive, or neither. This is handled with the sys_action_init function. While all three states are accessible to Quantum action developers, most actions only require a response to the active, or true state. The false state should simply exit the action.

Example#

switch (sys_action_init()) {
case false: {
exit;
}
case true: {
//Initialization code
}
}

Initialization code declared here will only be executed once when the action is first activated. Any code outside and following this block will continue to run every step until terminated. This is where the bulk of the action code will live, referred to as the action's response--a response to code declared in the initialization phase, and also a response to other Quantum framework and global project behaviors.

The most common example here is a pause state. Quantum has built-in pause/resume support via sys_event_pause, which actions can obey as well. Therefore, it's highly recommended to begin the response phase with a check of the pause state, and exit if paused.

Example#

if (sys_event_pause()) {
exit;
}

It is also essential to properly support skipping the action. In Quantum, 'skipping' means completing the action early, not merely cancelling it. This is important, as an action will be skipped not only if the user requests it, but also if it falls between other events in a *_goto operation.

As with the pause state, checking the skip state is as simple as calling sys_action_skip in an if statement. However, in this case, writing skip code that produces the desired behavior is entirely up to you and what you want your action to achieve.

Example#

if (sys_action_skip()) {
//Skip code (complete early, don't cancel!)
}

Of course, once your action has achieved its goal, it must be properly terminated to allow Quantum to continue to the next event. Like skipping, much of the process is entirely dependent on you and your specific action code. However, finalization should generally consist of three parts:

  1. The completion condition
  2. Action termination
  3. Repeat prevention

Of these three, only the second part is handled by Quantum itself. Running sys_action_term will signal to the framework that the action is complete, but will not force the action to cease execution. If other actions in the event are still running, the completed action may become a 'zombie' and continue running despite being terminated. Avoiding this behavior is important, as running sys_action_term multiple times in the same action will break other actions in the current Quantum event.

This problem is easily solved by adding a completion condition to detect when the action's goal has been achieved. This will ensure the action is not terminated prematurely, and in some cases will also protect it from running after termination. But not all conditions are so straightforward, and many will require repeat prevention to stop the completion condition from returning true multiple times. Typically, this means changing the value used in the completion condition to an irrelevant value that will always fail the test after termination.

Example#

//Completion condition
if (my_variable != 0) {
//Action termination
sys_action_term();
//Repeat prevention
my_variable = 0;
}

Conclusion#

Altogether, these three phases comprise a complete template for Quantum actions. It might seem simultaneously simple and complex at first, but with practice, it's a powerful new way to develop clean, tightly-managed, non-linear code. With qScript, you will break free of existing programming language conventions and write your own rules, one action at a time.