Skip to content

Latest commit

 

History

1,120 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Godot Sandbox Build

Website | Code Examples | Discord | Web Demo


Safe, low-latency and fast sandbox for the Godot game engine.

GodotCon 2024 Presentation


Godot Sandbox lets players run untrusted code safely. Write gameplay in SafeGDScript, a safe GDScript-dialect executed inside a memory-safe sandbox with limits and execution timeouts. Restricted programs cannot access the host beyond what you explicitly allow, making it safe to load mods, user-generated content and scripts from other players. All Godot platforms are supported.

Installation

  • Automatic (Recommended): Download the plugin from the official Godot Asset Store using the AssetLib tab in Godot by searching for Godot Sandbox.

  • Manual: Download the latest github release and move only the addons folder into your project addons folder.

SafeGDScript

SafeGDScript (.sgd) is the default language for sandboxed code. It is a safety-oriented GDScript-dialect with most of the same syntax, and some additions (like structs). Attach a .sgd file to any node the same way you would attach a .gd script:

VS Code users can install the SafeGDScript extension for syntax highlighting and basic editor support.

SafeGDScript supports reusable traits. A trait can contribute state, constants, enums, signals and concrete/static methods, while abstract methods state what the using class must provide:

uses Damageable

trait Damageable:
	var health: int = 100
	func take_damage(amount: int) -> void:
		health -= amount
	@abstract func on_death() -> void

func on_death() -> void:
	queue_free()

Traits are nominal for SafeGDScript classes (value is Damageable) and may be used as type hints. Foreign Godot objects can satisfy a trait structurally by providing every instance method declared by it. Disable that compatibility path with sandbox/safe_gdscript/trait_structural_fallback for strictly nominal matching.

struct Item:
	var name: String
	var value: int
	var dropped_at: Vector2?

	func try_stack(other: Item) -> bool:
		if self.name != other.name:
			return false
		self.value += other.value
		return true

	func drop(at: int | Vector2) -> void:
		if at is int:
			self.dropped_at = Vector2(at, 0)
		else:
			self.dropped_at = at

var inventory: Array[Item] = []

func add_item(item_name: String, item_value: int) -> bool:
	var item := Item(item_name, item_value)
	for stored in inventory:
		if stored.try_stack(item):
			return true
	inventory.append(item)
	return false

func _ready():
	add_item("coin", 1)
	add_item("gem", 5)
	add_item("coin", 1)
	inventory[0].drop(Vector2(64, 32))

SafeGDScript also supports await:

func cutscene(player_knocked : Signal) -> String:
	$Gate.text = "The gate is sealed."
	await get_tree().create_timer(0.8).timeout
	await player_knocked
	return "opened"

The host receives a Signal and awaits it like any other coroutine: var result = await $Director.cutscene(knocked). See examples/async for a complete example.

Modding and user-generated content

A restricted sandbox denies all host access by default: methods, properties, classes and resource loading. Gamedevs decide what a mod can reach by passing an explicit API:

var api : Dictionary = {}

func mod_init(granted : Dictionary) -> void:
	api = granted
	api["log"].call("hello from the mod")

func _physics_process(delta):
	api["report"].call("ticks", 1)

See examples/modding for a complete mod loader with a hostile-mod audit.

C++ and Rust

For maximum performance, you can also write sandboxed programs in C++ or Rust. They use the same sandbox and the same restrictions. See the code examples repository and the demo repository.

Performance

  • Sandboxed C++ is 2.5-10x faster than GDScript by default, 5-50x with binary translation
  • Enable full binary translation for maximum performance on all platforms (including locked down iOS, Web, Switch etc.)
  • JIT builds are available in the Releases section for Windows, macOS, Android and Linux

Using typed variables in SafeGDScript will help the compiler optimize:

--- logic CPU dispatch ---
case                               ns/emulated instr     vs ref    vs base
SafeGDScript (sandbox)                         115.4      5.36x      -4.1%
GDScript (engine)                              618.0      1.00x      -7.2%

In the modding example that implements a virtual CPU, we gained 5x over GDScript by using typed variables.

Usage

Module Build

In order to build as a module, add it to a godot repo:

git submodule add https://github.com/libriscv/godot-sandbox modules/sandbox
cd modules/sandbox
git submodule update --init --recursive

SafeGDScript and LLM assistance

The SafeGDScript language has been created with LLM assistance. It can be turned off by building with the ENABLE_SAFEGDSCRIPT option disabled in CMake like so:

cmake -S . -B .build -DENABLE_SAFEGDSCRIPT=OFF

Contributing

Requirements:

If you want to contribute to this repo, here are steps on how to build locally:

./build.sh

You can also use scons similar to how godot-cpp addons are built.

Contributors

Thanks goes to these wonderful people (emoji key):

Alf-André Walla
Alf-André Walla

💻
K. S. Ernest (iFire) Lee
K. S. Ernest (iFire) Lee

💻 🔬 ⚠️
Dragos Daian
Dragos Daian

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

Other Projects

The Jenova Framework has a built-in C++ compiler, and supports writing C++ in the Godot editor with hot-reloading support.

Releases

Packages

Used by

Contributors

Languages