Mechanism
Let's see how Jenova transforms C/C++ into a Scripting Language and discover where the magic happens!
Anatomyβ
Jenova Framework is a complex ecosystem built from diverse components. All components work seamlessly together to deliver the best development experience. Letβs explore the anatomy of Jenova Framework.
Build Systemβ
Jenova has a complete build system similar to Visual Studio project files. You can Build, Clean, and Rebuild projects. The build system is responsible for collecting, preprocessing, compiling, and parsing C++ scripts in your Godot project.
-
Script Manager: Responsible for creating, removing, collecting, and identifying C++ Scripts within your project. Each C++ file in the project will create a C++ Script Object in the Script Manager. While compiling, the Script Manager assigns each script a Unique Identifier and detects if it is an Active Script or Passive Script.
InformationActive Script: An active script is a C++ script attached to a node present in the scene tree. It contains script events and will be executed like GDScript.
Passive Script: A passive script is a C++ script in the project but not attached to any node or the attached node is not present in the current scene tree. Passive scripts can contain Nested GDExtension, Boot Setup, Exported Functions, Utility Functions etc.
Hybrid Script: A script that includes both active and passive context. For more details, refer to Pipelines.
-
Compiler Interface : Jenova features a flexible compiler interface composed of various stages, Including the Preprocessor, Compiler, and Linker. This design allows for easy integration with any C/C++ build toolchain.
-
Symbol Parser : After a successful build, Jenova Symbol Parser analyzes the compiled binary and extracts the required data for the interpreter, known as "Metadata". Metadata contains information about Scripts, Offsets, Types etc.
-
Deployer: When you build your Godot game, Jenova Deployer will encrypt and compress the compiled code and its metadata, Adding it to the game build package file. It also removes all C++ script sources from the exported package to prevent your C++ script sources from being leaked. However, This is an option that can be customized.
π Learn more about Jenova Editor Settingsβ
Runtimeβ
Jenova Runtime is the main core of the Jenova Framework. It's responsible for the execution process of built C++ scripts.
-
Module Mapper: Responsible for allocation and mapping the Jenova Module compiled code to memory, Preparing it for execution. Jenova doesn't load executable files from disk instead it dynamically maps them into executable memory pages.
-
Interpreter: One of the most important and core components of the runtime, Jenova Interpreter uses the information stored in Metadata to Manage Properties, Call Functions and return results from the compiled code. Jenova Interpreter features different backends which can be changed from the settings.
π Learn more about Jenova Interpreterβ
Utilitiesβ
-
Integrated Development: Jenova enables the creation of C/C++ and header files offering seamless in-editor C++ code editing.
-
Template Manager: Manages C++ script templates. Jenova Template Manager provides templates both globally and on a class basis.
-
Asset Monitor: Responsible for tracking changes applied to C++ scripts and headers. This is useful for the automated Hot-Reload feature.
-
Task System: Jenova features a minimal yet powerful task system for multi-threading powered by
libpthread
. It is used in various parts of the Jenova editor plugin such as the package manager and it also exposes an API to JenovaSDK. -
Package Manager: A user-friendly package manager designed for Jenova users to easily download official packages prepared for the Jenova Framework. It includes Compilers, GodotKits, Libraries, Addons, etc.
π Learn more about Jenova Package Managerβ
-
Exporter: Jenova is capable of exporting projects to various outputs. It can currently generate and integrate your entire codebase to Visual Studio for ultimate code editing. It can also export projects to GDExtension.
Upcoming ComponentsAt the moment, Jenova Framework does not have a Debugger and Profiler implementation but these will be added in the future.
Development Kitβ
An optional component of Jenova Runtime, Providing helpers and APIs for Hot-Reloading, Global Allocation/Access, Exposing Properties and more. It's automatically linked to the compiled module, It offers various utilities to ease development in Godot.
π Learn more about Jenova Software Development Kitβ
Pipelinesβ
Now that you're familiar with the internal workflow of the Jenova Framework let's talk about the development pipeline and how you can put it to use. In Jenova Framework, There are two ways to develop your game logic and they work together seamlessly.
Interactive C++ Scriptingβ
After you install Jenova Framework on Godot you can add C++ Scripts just like you would with GDScript or C# Scripts. By attaching the C++ Script to a node in an active Scene
it will be executed per frame by Interpreter.
Simply use Create New > Script... in FileSystem
to create a new C++ script.
You can also right-click on Nodes inside SceneTree
and select Attach Script, then you can add new C++ Script using Create Script Window.
Jenova C++ Scripts require a specific way of writing. You need to place your script code inside a Script Block which can be utilized using the following macros:
- Script Block Begin Macro (
JENOVA_SCRIPT_BEGIN
) - Script Block End Macro (
JENOVA_SCRIPT_END
)
Every function and property defined within this block will be parsed and serialized into Metadata by the Jenova Symbol Parser and will be accessible to the game engine.
// Start Jenova Script
JENOVA_SCRIPT_BEGIN
// Events
void OnReady()
{
Alert("Hello from Jenova C++ Script!");
}
// End Jenova Script
JENOVA_SCRIPT_END
You can place any function and value outside of the Script Block. The only difference is that they won't be serialized but can still be used anywhere in the code, Including inside the Script Block. It's recommended to place utility and helper functions outside of the Script Block.
Also, You can make a C++ Script run inside Editor by adding JENOVA_TOOL_SCRIPT
somewhere in your C++ Script.
// Run C++ Script In Editor
JENOVA_TOOL_SCRIPT
We will explore C++ scripting in greater detail in subsequent chapters.
Nested Extension Developmentβ
In addition to C++ Scripting, Jenova provides support for nested GDExtension development with a Hot-Reloading feature. This allows you to develop Custom Nodes, Editor Plugins, Editor Tools etc. in real-time without needing to compile any additional code.
// Custom Node Implementation
class CustomNode : public Node3D
{
GDCLASS(CustomNode, Node3D);
protected:
static void _bind_methods() { }
}
// Register/Unregister
void RegisterCustomNode()
{
// Register Class
ClassDB::register_class<CustomNode>();
// Finish Reload
sakura::FinishReload("CustomNode");
}
void UnregisterCustomNode()
{
// Prepare for Reload
sakura::PrepareReload("CustomNode");
// Release Class
sakura::Dispose("CustomNode");
}
Register and unregister functions must be manually called in the Boot Script. We will cover this process in detail as well.
It is possible to mix C++ Scripting with Nested Extensions. However, You must be careful with your coding approach. Ensure that the extension code is placed outside the Script Block, Allowing you to utilize both methods effectively.
Performanceβ
You might be curious about the performance of Jenova Runtime compared to GDExtension, GDScript and C#. Let's delve into this topic. By design C++ is one of the fastest languages in the world as it interacts with hardware at a low level and compiles to machine code. Now the real question is "Does it maintain this level of performance when used as a scripting language and interpreted?"
The short answer is No, But let's take a closer look at the statistics.
Just-in-Time Compilationβ
Jenova Interpreter uses a Just-in-Time compiler internally but not in the way that's common. Unlike C#, Python, and interpreted languages, Jenova doesn't compile your source code at runtime. Instead, It only compiles a small "Caller" code and uses the byte codes to call functions inside your C++ Script compiled machine code dynamically. Although this operation is very fast and high-performance there is still a small overhead.
Currently, There are two available Interpreter backends :
-
NitroJIT : Powered by AsmJIT and designed to call script functions at the highest speed possible. This backend uses Assembly Language to create caller code.
Pros- Very small overhead around 5 Microseconds (~0.005ms)
- Executed in parallel using multiple JIT environments
Cons- All functions inside Script Blocks must define parameters and return types as
godot::Variant*
-
Meteora : Powered by TinyCC and designed for better code implementation. It uses the C Language to generate caller code to invoke functions.
Pros- Functions inside Script Blocks can define parameters and return types as supported C++ and Godot Types
- Supports all sub-types of
godot::Variant
Cons- 10x higher overhead than NitroJIT around 50 Microseconds (~0.05ms)
- Cannot execute in parallel with multiple JIT environments
Evidently, The performance overhead of the Jenova Interpreter even while using the Meteora backend is is way too small to affect your game performance. However, this overhead can be eliminated entirely by using Nested Extensions which have the same performance as GDExtension modules.
Engine Interaction Layerβ
Jenova uses GDExtension (godot-cpp) library to interact with the Godot Engine. Unlike other software that provides direct access to their APIs in their SDKs, Godot Engine employs a different approach by wrapping its engine API. This makes direct calls less straightforward. While this design introduces a small overhead, it is inherent to the GDExtension design and cannot be mitigated by Jenova.
Callback vs Functionβ
Note that Jenova's overhead only occurs when interacting with the engine backend and functions (Callbacks) defined within Script Blocks. Otherwise, all functions defined outside and not directly interacting with the Interpreter maintain the ground truth performance level of C++.
Overall despite the mentioned overheads, Jenova outperforms C#, GDScript, Python and any other interpreted languages.