blazium-engine / blazium

Blazium Engine – Multi-platform 2D and 3D game engine
https://blazium.app/
MIT License
171 stars 20 forks source link

The GDExtension Story: Addressing Pain Points #100

Open Splizard opened 1 day ago

Splizard commented 1 day ago

Hoping to document the current story and pain points around using GDExtension for language bindings, so that decisions can be made on what are actually issues and what are things that users should just to live with.

I see GDExtension as a C header and accompanying .json file that enables an object-oriented graphics engine to expose a 'reflection' of the entire public API for the engine. I think of GD as standing for graphics/game development, (so not to couple this technology to a specific engine).

The engines that are currently released and in development that offer a GDExtension interface are all very capable graphics and game development runtimes. They include scene editors, hassle-free asset loading, rendering, audio and portable system interfaces that are designed to work cross-platform.

As such, GDExtension, in theory, should provide a nice way for other programming languages to leverage this expansive runtime to build projects upon it. Efforts already exist for Rust, Swift, Go, Zig and others.

I maintain Go language bindings for GDExtension. Whilst the interface mostly works, I've still got significant concerns around the overall memory model for GDExtension (as it is unspecified). Race-free Go is a memory safe language and I'd like to be able to ensure that Go extensions are safe by default.

This is difficult to achieve, as I have to assume that the GDExtension host may free any non-refcounted object instances passed as parameters to a function (invalidating these references). There aren't any clear ownership semantics, nor full type definitions for callables, signals and dictionaries, where possible I am maintaining these myself.

Another unfortunate aspect of GDExtension is the infectiousness of the basic collection types. Any time you want to pass packed arrays or large data to GDExtension (think meshes or images) you have to either make sure you construct that data using GDextension-specific collections (forgoing any native data stuctures you may be using) and/or incur the cost of copying the entire array over.

In order to provide a simple-to-use interface in Go, I would like to be able to setup some language-runtime specific routines after the engine (including all singletons and APIs) have been initialised and once per frame. There is no straightforward way to do this, all initialisation levels are incomplete and there is no builtin way to call a function once-per-frame.

Pain Points Recap

  1. Unspecified Ownership semantics for non-RefCounted objects.
  2. Unclear Memory Model for multi-threading.
  3. Unspecified Types for Signals and Callables
  4. All existing memory has to be copied between the engine and GDExtension.
  5. Infectious collection types that makes it difficult for GDExtensions to use their own collection, or string types.
  6. No global _ready/_process equivalant for GDExtension.

Ideas For Solutions

  1. Maintain ownership semantics for functions that use non-RefCounted objects (I'm open to contribute to this).
  2. Specify rules for what is allowed when multi-threading (I'm open to contribute to this).
  3. Specify composite types in the .json file for any use of Callable, Dictionaries, and/or Signals (I'm open to contribute to this).
  4. Enable the engine to borrow GDExtension buffers "as a PackedArray" for the lifetime of a function call, only making a copy if the data needs to be kept by the callee.
  5. Enable GDExtension to implement the String, StringName, Array, Dictionary, Signal and Variant classes, as is possible for callables.
  6. Add a new initialisation level and callback function that will run once per-frame.
heavymetalmixer commented 22 hours ago

Are those problems and solutions common for all the programming languages that have bindings? Or Go-exclusive?

Splizard commented 18 hours ago

@heavymetalmixer these are general pain points for safe type-safe languages, I've briefly spoken with the Rust folks and they also need to work around these sorts of issues.

3 in particular wouldn't affect dynamically-typed languages.