Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
132 changes: 132 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,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
73 changes: 71 additions & 2 deletions demo/Blackholio/client-godot/Circle2D.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
using System;
using Godot;

public partial class Circle2D : Node2D
public enum CircleVisualStyle
{
Player,
Food
}

public abstract partial class Circle2D : Node2D
{
private float _radius = 10.0f;
[Export]
Expand Down Expand Up @@ -29,6 +36,68 @@ public Color Color
QueueRedraw();
}
}

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

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

public override void _Draw() => DrawCircle(Vector2.Zero, Radius, Color);
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);
}
21 changes: 20 additions & 1 deletion demo/Blackholio/client-godot/CircleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ private Label Label
{
Name = $"{Name}_Label",
TopLevel = false,
MouseFilter = Control.MouseFilterEnum.Ignore
MouseFilter = Control.MouseFilterEnum.Ignore,
HorizontalAlignment = HorizontalAlignment.Center
};
_label.AddThemeFontSizeOverride("font_size", 13);
_label.AddThemeColorOverride("font_color", Colors.White);
_label.AddThemeColorOverride("font_shadow_color", new Color(0, 0, 0, 0.75f));
_label.AddThemeConstantOverride("shadow_offset_x", 1);
_label.AddThemeConstantOverride("shadow_offset_y", 1);
LabelRoot.AddChild(_label);
}
return _label;
Expand All @@ -90,6 +96,7 @@ public CircleController(Circle circle, PlayerController ownerPlayer) : base(circ
public override void _Process(double delta)
{
base._Process(delta);
Label.Text = OwnerPlayer?.Username ?? "";
UpdateScreenLabelPosition();
}

Expand All @@ -105,8 +112,20 @@ public override void OnDelete()
OwnerPlayer?.OnCircleDeleted(this);
}

public override void OnConsumed()
{
if (IsInstanceValid(Label))
{
Label.QueueFree();
}

OwnerPlayer?.OnCircleDeleted(this);
}

private void UpdateScreenLabelPosition()
{
if (!IsInstanceValid(Label)) return;

Label.Size = Label.GetCombinedMinimumSize();
var screenPosition = GetGlobalTransformWithCanvas().Origin;
var offset = new Vector2(0.0f, Radius + 8.0f);
Expand Down
38 changes: 38 additions & 0 deletions demo/Blackholio/client-godot/EntityController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,25 @@
public abstract partial class EntityController : Circle2D
{
private const float LerpDurationSec = 0.1f;
private const float DespawnDurationSec = 0.2f;

public int EntityId { get; private set; }

private float LerpTime { get; set; }
private Vector2 LerpStartPosition { get; set; }
private Vector2 TargetPosition { get; set; }
private float TargetRadius { get; set; }
private bool IsDespawning { get; set; }
private float DespawnTime { get; set; }
private Vector2 DespawnStartPosition { get; set; }
private float DespawnStartRadius { get; set; }
private Node2D DespawnTarget { get; set; }

protected EntityController(int entityId, Color color)
{
EntityId = entityId;
Color = color;
AnimationSeed = entityId * 0.73f;

var entity = GameManager.Conn.Db.Entity.EntityId.Find(entityId);
var position = (Vector2)entity.Position;
Expand All @@ -28,20 +35,51 @@ protected EntityController(int entityId, Color color)

public void OnEntityUpdated(Entity newRow)
{
if (IsDespawning) return;

LerpTime = 0.0f;
LerpStartPosition = GlobalPosition;
TargetPosition = (Vector2)newRow.Position;
TargetRadius = MassToRadius(newRow.Mass);
}

public virtual void OnDelete() => QueueFree();
public virtual void OnConsumed() { }

public void StartDespawn(Node2D target)
{
IsDespawning = true;
DespawnTime = 0.0f;
DespawnStartPosition = GlobalPosition;
DespawnStartRadius = Radius;
DespawnTarget = target;
ZIndex += 10;
}

public override void _Process(double delta)
{
var frameDelta = (float)delta;
if (IsDespawning)
{
DespawnTime = Mathf.Min(DespawnTime + frameDelta, DespawnDurationSec);
var t = DespawnTime / DespawnDurationSec;
var targetPosition = IsInstanceValid(DespawnTarget) ? DespawnTarget.GlobalPosition : TargetPosition;
GlobalPosition = DespawnStartPosition.Lerp(targetPosition, t);
Radius = Mathf.Lerp(DespawnStartRadius, 0.0f, t);
RedrawAnimatedVisuals();

if (DespawnTime >= DespawnDurationSec)
{
QueueFree();
}

return;
}

LerpTime = Mathf.Min(LerpTime + frameDelta, LerpDurationSec);
GlobalPosition = LerpStartPosition.Lerp(TargetPosition, LerpTime / LerpDurationSec);
Radius = Mathf.Lerp(Radius, TargetRadius, frameDelta * 8.0f);
RedrawAnimatedVisuals();
}

private static float MassToRadius(int mass) => Mathf.Sqrt(mass);
Expand Down
7 changes: 5 additions & 2 deletions demo/Blackholio/client-godot/FoodController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@ public partial class FoodController : EntityController
new(35 / 255.0f, 245 / 255.0f, 165 / 255.0f),
];

public FoodController(Food food) : base(food.EntityId, ColorPalette[food.EntityId % ColorPalette.Length]) { }
}
public FoodController(Food food) : base(food.EntityId, ColorPalette[food.EntityId % ColorPalette.Length])
{
VisualStyle = CircleVisualStyle.Food;
}
}
Loading
Loading