Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b555775
Start working on Blackholio for Godot
lisandroct Apr 20, 2026
ceafb16
Wrap up part 1
lisandroct Apr 22, 2026
fbdb89d
Wrap up demo
lisandroct Apr 23, 2026
f2ebf12
Update tutorial up to part 3
lisandroct Apr 24, 2026
87ab753
Update Godot docs
lisandroct Apr 27, 2026
d3d1d66
Fix and polish part 1 and part 2 of the Godot tutorial
lisandroct Apr 28, 2026
85b32dc
Fix and polish part 3 and part 4 of the Godot tutorial
lisandroct Apr 28, 2026
3b8719c
Cleanup Unity tutorial part 2
lisandroct Apr 28, 2026
012658b
Fix and polish part 3 and part 4 of the Unity tutorial
lisandroct Apr 28, 2026
bdac0fc
Add Godot SDK
lisandroct Apr 29, 2026
a776043
Add notes about auth tokens and splitting into multiple circles
lisandroct Apr 30, 2026
17bd708
Update demo files
lisandroct Apr 30, 2026
a44196c
Add Godot SDK csproj
lisandroct May 5, 2026
bc71137
Update demo project
lisandroct May 5, 2026
2b6e733
Fix tutorial
lisandroct May 5, 2026
a77d288
Use ZIndex in the Arena
lisandroct May 5, 2026
ea41db6
Use ZIndex in code
lisandroct May 5, 2026
8a68c3c
Update csproj to 2.2.0
lisandroct May 6, 2026
60f3d44
Add Scheduled Table attribute to MoveAllPlayersTimer
lisandroct May 6, 2026
408c4b0
Fix images
lisandroct May 7, 2026
b2404b3
Fix images urls
lisandroct May 7, 2026
47e0be3
Fix ci problems
lisandroct May 8, 2026
7959290
Add Godot SDK to regen dlls tool
lisandroct May 8, 2026
0f7e9af
Adding .meta files for new .cs scripts
rekhoff May 8, 2026
9fa6554
Fix .NET test
lisandroct May 8, 2026
40883aa
Fix smoketest
lisandroct May 8, 2026
614c9cd
Clean
lisandroct May 12, 2026
4df0bf0
[lisandro/godot-blackholio]: use Directory.Build.props for godot outp…
bfops May 12, 2026
f42fb04
[lisandro/godot-blackholio]: Revert "[lisandro/godot-blackholio]: use…
bfops May 14, 2026
1adfddc
[lisandro/godot-blackholio]: comments
bfops May 14, 2026
2b9e0d2
Add missing features to demo
lisandroct May 15, 2026
9f021d4
Add last comment to tutorial
lisandroct May 5, 2026
2c9d185
Small change to last message in Unity's tutorial
lisandroct May 5, 2026
96b308b
Update final result image
lisandroct May 5, 2026
3b62ef3
Add tests
lisandroct May 14, 2026
a440269
Clean
lisandroct May 15, 2026
af82eb5
Clean test
lisandroct May 15, 2026
d858547
Fix .sln in godot-testsuite
lisandroct May 22, 2026
46b44ad
Make verbose build
lisandroct May 22, 2026
e37eaec
Try to force using local BSATN
lisandroct May 22, 2026
107cd3d
Try Codex suggestion
lisandroct May 22, 2026
19f48b3
Try Codex suggestion to fix unity-testsuite
lisandroct May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 133 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,138 @@ jobs:
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}

godot-testsuite:
needs: [lints]
permissions:
contents: read
runs-on: spacetimedb-new-runner-2
timeout-minutes: 30
env:
CARGO_TARGET_DIR: ${{ github.workspace }}/target
UseLocalBsatnRuntime: true
steps:
- name: Checkout repository
id: checkout-stdb
uses: actions/checkout@v4

# Run cheap .NET setup first. If that fails, no need to run expensive Godot tests.

- name: Setup dotnet
uses: actions/setup-dotnet@v3
with:
global-json-file: global.json

- name: Override NuGet packages
run: |
dotnet pack -c Release crates/bindings-csharp/BSATN.Runtime
dotnet pack -c Release crates/bindings-csharp/Runtime

# Write out the nuget config file to `nuget.config`. This causes the spacetimedb-csharp-sdk repository
# to be aware of the local versions of the `bindings-csharp` packages in SpacetimeDB, and use them if
# available. Otherwise, `spacetimedb-csharp-sdk` will use the NuGet versions of the packages.
# This means that (if version numbers match) we will test the local versions of the C# packages, even
# if they're not pushed to NuGet.
# See https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file for more info on the config file.
cd sdks/csharp
./tools~/write-nuget-config.sh ../..

- name: Restore .NET solution
working-directory: sdks/csharp
run: dotnet restore --configfile NuGet.Config SpacetimeDB.ClientSDK.sln

# Now, setup the Godot tests.
- name: Patch spacetimedb dependency in Cargo.toml
working-directory: demo/Blackholio/server-rust
run: |
sed -i "s|spacetimedb *=.*|spacetimedb = \{ path = \"../../../crates/bindings\" \}|" Cargo.toml
cat Cargo.toml

- name: Install Rust toolchain
uses: dsherret/rust-toolchain-file@v1
- name: Set default rust toolchain
run: rustup default $(rustup show active-toolchain | cut -d' ' -f1)

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: ${{ github.workspace }}
shared-key: spacetimedb
# Let the main CI job save the cache since it builds the most things
save-if: false
prefix-key: v1

# This step shouldn't be needed, but somehow we end up with caches that are missing librusty_v8.a.
# ChatGPT suspects that this could be due to different build invocations using the same target dir,
# and this makes sense to me because we only see it in this job where we mix `cargo build -p` with
# `cargo build --manifest-path` (which apparently build different dependency trees).
# However, we've been unable to fix it so... /shrug
- name: Check v8 outputs
run: |
find "${CARGO_TARGET_DIR}"/ -type f | grep '[/_]v8' || true
if ! [ -f "${CARGO_TARGET_DIR}"/release/gn_out/obj/librusty_v8.a ]; then
echo "Could not find v8 output file librusty_v8.a; rebuilding manually."
cargo clean --release -p v8 || true
cargo build --release -p v8
fi

- name: Install SpacetimeDB CLI from the local checkout
run: |
export CARGO_HOME="$HOME/.cargo"
echo "$CARGO_HOME/bin" >> "$GITHUB_PATH"
cargo install --force --path crates/cli --locked --message-format=short
cargo install --force --path crates/standalone --locked --message-format=short
# Add a handy alias using the old binary name, so that we don't have to rewrite all scripts (incl. in submodules).
ln -sf $CARGO_HOME/bin/spacetimedb-cli $CARGO_HOME/bin/spacetime

- name: Generate client bindings
working-directory: demo/Blackholio/server-rust
run: bash ./generate.sh -y

- name: Check for changes
run: |
tools/check-diff.sh demo/Blackholio/client-godot/module_bindings || {
echo 'Error: Godot bindings are dirty. Please run `demo/Blackholio/server-rust/generate.sh`.'
exit 1
}

- name: Patch SpacetimeDB Godot SDK dependency
working-directory: demo/Blackholio/client-godot
run: |
dotnet remove package SpacetimeDB.ClientSDK.Godot
dotnet add reference ../../../sdks/csharp/SpacetimeDB.ClientSDK.Godot.csproj
cat blackholio.csproj

- name: Setup Godot
uses: chickensoft-games/setup-godot@v2
with:
version: 4.6.2
use-dotnet: true

- name: Restore Godot project
working-directory: demo/Blackholio/client-godot
run: dotnet restore --configfile ../../../NuGet.Config blackholio.csproj

- name: Build Godot .NET project
working-directory: demo/Blackholio/client-godot
run: dotnet build --no-restore -v normal blackholio.csproj

- name: Build Godot project
run: godot --headless --verbose --path demo/Blackholio/client-godot --build-solutions --quit

- name: Start SpacetimeDB
run: |
spacetime start &
disown

- name: Publish godot-tests module to SpacetimeDB
working-directory: demo/Blackholio/server-rust
run: |
spacetime logout && spacetime login --server-issued-login local
bash ./publish.sh

- name: Run Godot tests
run: godot --headless --path demo/Blackholio/client-godot --scene res://tests/GodotPlayModeTests.tscn

csharp-testsuite:
needs: [lints]
runs-on: spacetimedb-new-runner-2
Expand Down Expand Up @@ -747,7 +879,7 @@ jobs:

- name: Run .NET tests
working-directory: sdks/csharp
run: dotnet test -warnaserror --no-restore
run: dotnet test -warnaserror --no-restore SpacetimeDB.ClientSDK.csproj

- name: Verify C# formatting
working-directory: sdks/csharp
Expand Down
43 changes: 36 additions & 7 deletions crates/smoketests/tests/smoketests/quickstart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,32 @@ fn create_nuget_config(sources: &[(String, PathBuf)], mappings: &[(String, Strin

/// Override nuget config to use a local NuGet package on a .NET project.
fn override_nuget_package(project_dir: &Path, package: &str, source_dir: &Path, build_subdir: &str) -> Result<()> {
override_nuget_package_from_project(project_dir, package, source_dir, None, build_subdir)
}

/// Override nuget config to use a local NuGet package built from a specific .NET project.
fn override_nuget_package_from_project(
project_dir: &Path,
package: &str,
source_dir: &Path,
source_project: Option<&str>,
build_subdir: &str,
) -> Result<()> {
println!("Override {package}: {project_dir:?} with {source_dir:?}");

// Make sure the local package is built
let workspace = workspace_root();
let repo_nuget_config = workspace.join("NuGet.Config");
let source_project_path = source_project.map(|project| source_dir.join(project));
if repo_nuget_config.exists() {
let output = Command::new("dotnet")
.args(["restore", "--configfile", repo_nuget_config.to_str().unwrap()])
let mut command = Command::new("dotnet");
command.arg("restore");
if let Some(source_project_path) = &source_project_path {
command.arg(source_project_path);
}
let output = command
.arg("--configfile")
.arg(&repo_nuget_config)
.current_dir(source_dir)
.output()
.context("Failed to run dotnet restore")?;
Expand All @@ -152,8 +170,13 @@ fn override_nuget_package(project_dir: &Path, package: &str, source_dir: &Path,
);
}

let output = Command::new("dotnet")
.args(["pack", "-c", "Release", "--no-restore"])
let mut command = Command::new("dotnet");
command.arg("pack");
if let Some(source_project_path) = &source_project_path {
command.arg(source_project_path);
}
let output = command
.args(["-c", "Release", "--no-restore"])
.current_dir(source_dir)
.output()
.context("Failed to run dotnet pack")?;
Expand All @@ -165,8 +188,13 @@ fn override_nuget_package(project_dir: &Path, package: &str, source_dir: &Path,
);
}
} else {
let output = Command::new("dotnet")
.args(["pack", "-c", "Release"])
let mut command = Command::new("dotnet");
command.arg("pack");
if let Some(source_project_path) = &source_project_path {
command.arg(source_project_path);
}
let output = command
.args(["-c", "Release"])
.current_dir(source_dir)
.output()
.context("Failed to run dotnet pack")?;
Expand Down Expand Up @@ -637,10 +665,11 @@ log = "0.4"
&workspace.join("crates/bindings-csharp/BSATN.Runtime"),
"bin/Release",
)?;
override_nuget_package(
override_nuget_package_from_project(
client_path,
"SpacetimeDB.ClientSDK",
&workspace.join("sdks/csharp"),
Some("SpacetimeDB.ClientSDK.csproj"),
"bin~/Release",
)?;

Expand Down
4 changes: 4 additions & 0 deletions demo/Blackholio/client-godot/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
root = true

[*]
charset = utf-8
2 changes: 2 additions & 0 deletions demo/Blackholio/client-godot/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
3 changes: 3 additions & 0 deletions demo/Blackholio/client-godot/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/
49 changes: 49 additions & 0 deletions demo/Blackholio/client-godot/CameraController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Godot;

public partial class CameraController : Camera2D
{
[Export]
public float BaseVisibleRadius { get; set; } = 50.0f;

[Export]
public float FollowLerpSpeed { get; set; } = 8.0f;

[Export]
public float ZoomLerpSpeed { get; set; } = 2.0f;

private float WorldSize { get; }

public CameraController(float worldSize)
{
WorldSize = worldSize;
}

public override void _Process(double delta)
{
Vector2 targetPosition;
if (GameManager.IsConnected() && PlayerController.Local != null && PlayerController.Local.TryGetCenterOfMass(out var centerOfMass))
{
targetPosition = centerOfMass;
}
else
{
var hWorldSize = WorldSize * 0.5f;
targetPosition = new Vector2(hWorldSize, hWorldSize);
}

GlobalPosition = GlobalPosition.Lerp(targetPosition, (float)delta * FollowLerpSpeed);

if (PlayerController.Local == null)
{
return;
}

var targetCameraSize = CalculateCameraSize(PlayerController.Local);
var desiredZoom = Vector2.One * (BaseVisibleRadius / Mathf.Max(targetCameraSize, 1.0f));
Zoom = Zoom.Lerp(desiredZoom, (float)delta * ZoomLerpSpeed);
}

private static float CalculateCameraSize(PlayerController player) => 10.0f
+ Mathf.Min(10.0f, player.TotalMass() / 5.0f)
+ Mathf.Min(player.NumberOfOwnedCircles - 1, 1) * 30.0f;
}
1 change: 1 addition & 0 deletions demo/Blackholio/client-godot/CameraController.cs.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://c5uuweuf2vu0e
103 changes: 103 additions & 0 deletions demo/Blackholio/client-godot/Circle2D.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using Godot;

public enum CircleVisualStyle
{
Player,
Food
}

public abstract partial class Circle2D : Node2D
{
private float _radius = 10.0f;
[Export]
public float Radius
{
get => _radius;
set
{
if (Mathf.IsEqualApprox(_radius, value)) return;

_radius = value;
QueueRedraw();
}
}

private Color _color = Colors.Brown;
[Export]
public Color Color
{
get => _color;
set
{
if (_color == value) return;

_color = value;
QueueRedraw();
}
}

[Export]
public CircleVisualStyle VisualStyle { get; set; } = CircleVisualStyle.Player;

[Export]
public float AnimationSeed { get; set; }

public override void _Draw()
{
if (Radius <= 0.01f) return;

switch (VisualStyle)
{
case CircleVisualStyle.Player:
DrawPlayerCircle();
break;
case CircleVisualStyle.Food:
DrawFood();
break;
default:
throw new ArgumentOutOfRangeException();
}
}

protected void RedrawAnimatedVisuals() => QueueRedraw();

private void DrawPlayerCircle()
{
var time = Time.GetTicksMsec() / 1000.0f;
var pulse = 0.5f + 0.5f * Mathf.Sin(time * 2.2f + AnimationSeed);
DrawCircle(Vector2.Zero, Radius * (1.16f + pulse * 0.04f), WithAlpha(Color, 0.14f));
DrawCircle(Vector2.Zero, Radius, Shade(Color, 0.58f));
DrawCircle(Vector2.Zero, Radius * 0.82f, Color);
DrawCircle(new Vector2(-Radius * 0.22f, -Radius * 0.24f), Radius * 0.34f, WithAlpha(Shade(Color, 1.42f), 0.72f));

var outline = new Vector2[73];
for (var i = 0; i < outline.Length; i++)
{
var angle = Mathf.Tau * i / (outline.Length - 1);
var wave = Mathf.Sin(angle * 7.0f + time * 3.0f + AnimationSeed) * 0.035f;
outline[i] = Vector2.FromAngle(angle) * Radius * (1.015f + wave);
}

DrawPolyline(outline, WithAlpha(Shade(Color, 1.55f), 0.88f), Mathf.Clamp(Radius * 0.085f, 1.5f, 5.0f), true);
}

private void DrawFood()
{
var time = Time.GetTicksMsec() / 1000.0f;
var pulse = 0.5f + 0.5f * Mathf.Sin(time * 5.0f + AnimationSeed);
DrawCircle(Vector2.Zero, Radius * (1.32f + pulse * 0.09f), WithAlpha(Color, 0.1f));
DrawCircle(Vector2.Zero, Radius, Shade(Color, 0.72f));
DrawCircle(Vector2.Zero, Radius * 0.64f, Color);
DrawCircle(Vector2.Zero, Radius * 0.24f, WithAlpha(Shade(Color, 1.55f), 0.86f));
}

private static Color Shade(Color color, float multiplier) => new Color(
Mathf.Clamp(color.R * multiplier, 0.0f, 1.0f),
Mathf.Clamp(color.G * multiplier, 0.0f, 1.0f),
Mathf.Clamp(color.B * multiplier, 0.0f, 1.0f),
color.A
);

private static Color WithAlpha(Color color, float alpha) => new(color.R, color.G, color.B, alpha);
}
1 change: 1 addition & 0 deletions demo/Blackholio/client-godot/Circle2D.cs.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://7lmrsq2i7mi1
Loading
Loading