feat: integrate composable Godot runtime and EventBus debugger
This commit is contained in:
55 files changed
+1481
-114
No files matched your search
@@ -0,0 +1,108 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Godot;
|
||||
using ShrinkApp;
|
||||
using ShrinkDataSaver;
|
||||
using ShrinkEventBus;
|
||||
using ShrinkSDK.Godot;
|
||||
|
||||
public partial class WorldDemo : Node
|
||||
{
|
||||
private readonly ShrinkGodotRuntime _runtime = new();
|
||||
private WorldDemoWorld? _active;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
ProcessMode = ProcessModeEnum.Always;
|
||||
_runtime.Initialize(this);
|
||||
RunAsync().Forget();
|
||||
}
|
||||
public override void _Process(double delta) => _runtime.Tick(delta);
|
||||
public override void _Notification(int what) => _runtime.Notify(what);
|
||||
|
||||
private bool SwitchWorld(bool failRestore = false)
|
||||
{
|
||||
var candidate = new WorldDemoWorld();
|
||||
try
|
||||
{
|
||||
AddChild(candidate);
|
||||
candidate.Restore(failRestore);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
candidate.Free();
|
||||
return false;
|
||||
}
|
||||
var old = _active;
|
||||
_active = candidate;
|
||||
old?.Free();
|
||||
candidate.Commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Check(bool value, string message)
|
||||
{
|
||||
if (!value) throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
private async UniTaskVoid RunAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var uninitialized = new ShrinkGodotRuntime();
|
||||
var rejected = false;
|
||||
try { await uninitialized.StartAppAsync(); }
|
||||
catch (InvalidOperationException) { rejected = true; }
|
||||
Check(rejected, "Start must require initialization.");
|
||||
ShrinkGodotRuntime.RequireWoven(GetType().Assembly);
|
||||
await _runtime.StartAppAsync(new ShrinkAppSettings
|
||||
{
|
||||
DisabledModuleIds = ["shrink.command", "shrink.datasaver", "shrink.network"]
|
||||
});
|
||||
var root = Path.Combine(_runtime.PersistentDataPath, "world-demo");
|
||||
var storage = ShrinkGodotStorage.CreateRenameOnly(root);
|
||||
await storage.WriteAsync("probe", new byte[] { 1 });
|
||||
await storage.WriteAsync("probe", new byte[] { 2 });
|
||||
await storage.WriteAsync("probe", new byte[] { 3 });
|
||||
Check((await storage.ReadAsync("probe.bak1"))[0] == 2, "First backup mismatch.");
|
||||
Check((await storage.ReadAsync("probe.bak2"))[0] == 1, "Second backup mismatch.");
|
||||
|
||||
Check(SwitchWorld(), "First world failed.");
|
||||
var first = _active!;
|
||||
var firstKey = first.Key;
|
||||
Check(first.IsInsideTree() && first.ReadyCount == 1 && first.EffectCount == 0,
|
||||
"Restore must suppress effects and commit must publish ready.");
|
||||
Check(!SwitchWorld(true) && ReferenceEquals(_active, first) && first.IsInsideTree(),
|
||||
"Failed candidate replaced the active world.");
|
||||
Check(EventBus.CaptureNamedBuses().Count(bus => bus.Key == firstKey) == 1,
|
||||
"Candidate failure damaged the original bus.");
|
||||
Check(SwitchWorld(), "Second world failed.");
|
||||
Check(!GodotObject.IsInstanceValid(first) && !EventBus.TryGetBus(firstKey, out _),
|
||||
"Old node or subscription bus survived replacement.");
|
||||
var active = _active!;
|
||||
GetTree().Paused = true;
|
||||
Check(EventBus.PostDeferred(active.Bus, new DemoEffect()).Accepted, "Deferred rejected.");
|
||||
Check(active.EffectCount == 0, "Effect ran synchronously.");
|
||||
for (var frame = 0; frame < 4 && active.EffectCount == 0; frame++)
|
||||
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
|
||||
Check(active.EffectCount == 1, "Paused app did not pump.");
|
||||
GetTree().Paused = false;
|
||||
active.Free();
|
||||
_active = null;
|
||||
await _runtime.ShutdownAsync();
|
||||
Check(_runtime.IsShutdown && !_runtime.TryPost(() => { }), "Closed runtime accepted dispatch.");
|
||||
Check(!await Task.Run(() => _runtime.TryPostDeferred(() => { })), "Worker posted after shutdown.");
|
||||
GD.Print("SHRINK_GODOT_WORLD_PASS");
|
||||
GetTree().Quit(0);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GD.PushError($"SHRINK_GODOT_WORLD_FAIL {exception}");
|
||||
GetTree().Quit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user