#nullable enable using System; using System.Threading; using EntJoy.ECS; using EntJoy.JobSystem; using Godot; using ShrinkSDK.Godot; namespace ShrinkSDK.EntJoy.Godot; /// Godot node that owns an EntJoy World and advances its frame boundary. public partial class ShrinkEntJoyHost : ShrinkGodotHost { private static readonly object SchedulerGate = new(); private static int SchedulerUsers; private bool _schedulerAcquired; private bool _worldDisposed; private ulong _lastFrame = ulong.MaxValue; private SceneTree? _tree; [Export] public string EntJoyWorldName { get; set; } = "GodotWorld"; public World? EntJoyWorld { get; private set; } public override void _Ready() { base._Ready(); AcquireScheduler(); EntJoyWorld = new World(EntJoyWorldName); World.DefaultWorld = EntJoyWorld; _tree = GetTree(); _tree.ProcessFrame += OnProcessFrame; TreeExited += OnTreeExited; } public override void _Process(double delta) { base._Process(delta); Advance(); } private void OnProcessFrame() => Advance(); private void Advance() { if (EntJoyWorld == null || _worldDisposed) return; var frame = Engine.GetProcessFrames(); if (frame == _lastFrame) return; _lastFrame = frame; EntJoyWorld.NextFrameEvents(); NativeJobScheduler.EndFrame(); EntJoyWorld.CurrentFrame++; } public override void _ExitTree() { DisposeWorld(); base._ExitTree(); } private void OnTreeExited() { if (_tree != null) _tree.ProcessFrame -= OnProcessFrame; _tree = null; TreeExited -= OnTreeExited; DisposeWorld(); } private void AcquireScheduler() { lock (SchedulerGate) { if (SchedulerUsers++ == 0) NativeJobScheduler.Initialize(); _schedulerAcquired = true; } } private void DisposeWorld() { if (_worldDisposed) return; _worldDisposed = true; EntJoyWorld?.Dispose(); EntJoyWorld = null; if (!_schedulerAcquired) return; lock (SchedulerGate) { _schedulerAcquired = false; if (--SchedulerUsers == 0) NativeJobScheduler.Shutdown(); } } }