Skip to content

Make scripting runtime engine bytecode-based #1141

@Naros

Description

@Naros

Description

The virtual machine used by Orchestrator up to this point has worked well, but it has left the plugin in a position where key components such as the script, data model, and runtime are too tightly coupled. This coupling makes it challenging to introduce specific features or changes. To address this problem and several performance issues we've identified, the proposal is to rewrite the scripting virtual machine to use a fully bytecode-based solution, similar to GDScript.

This process will require multiple changes to achieve the goal.

Decouple Orchestration from OScript

One of the challenges with the user interface is that Orchestration is an interface implemented by OScript. This means the script implementation and the data model are identical. This also introduces challenges with reloading content and performing actions independent of the Script lifecycle.

Moving toward composition rather than inheritance enables reusing Orchestration in macro libraries.

Compile-Once Strategy

This is where the bulk of the changes come in. Up until now, when an OScriptInstance (the runtime object for a script) is created, a series of passes occur that generate dozens to hundreds of subobjects to represent the lifecycle of that script for a single node in your scene. If a script is used by more than one node, it is evaluated N times (where N is the number of nodes that use it).

This model doesn't scale well for large scenes or games that want to harness the power of composition and reuse.

Orchestrator will shift to a model that aligns with GDScript, where the script is evaluated when its source is loaded off disk or as a reload is triggered. The script's executable details are managed statelessly, enabling a compile-once strategy and reusing the compiled objects across all nodes that use the script in the scene. This would make the OScriptInstance (the runtime instance object for a script per node) extremely lightweight, storing only variable state. This too mimics how GDScript works.

Multi-pass Compilation

The current compilation uses a single pass to transform the Orchestration and its nodes to the runtime representation. This results in a very tight coupling between the data model and the runtime. Moving forward, we'd like to shift to a paradigm where the runtime is independent of the data model, and to that end, we'll leverage a multi-pass compilation process.

First, the Orchestration and its sub-resources will be transformed into an Abstract Syntax Tree (AST). This conversion provides several key advantages, most notably enabling us to migrate from the data model to an intermediate, entirely agnostic model. In addition, ASTs are extremely good at representing code, particularly in text-based languages.

We'll use the AST model to perform several passes over the tree. These passes use standard techniques to evaluate the tree, identify dead code, and perform expression reduction. The goal of these passes is to reduce code footprint while improving script performance at runtime.

In addition, the AST representation can serve as a long-term stretch goal: displaying the compiled graph(s) in GDScript or C# code.

The compiler will use the AST to generate a set of data structures for bytecode execution. This is a compact set of instruction-based sequences that represent the nodes and their behavior in the graph(s).

What comes after?

This change lays the groundwork for a long list of features that the community has been asking for, e.g.:

Implementation ideas

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions