diff --git a/addons/GDTask/Autoload/GDTaskPlayerLoopAutoload.cs b/addons/GDTask/Autoload/GDTaskPlayerLoopAutoload.cs index 9bd83af..a699c4a 100644 --- a/addons/GDTask/Autoload/GDTaskPlayerLoopAutoload.cs +++ b/addons/GDTask/Autoload/GDTaskPlayerLoopAutoload.cs @@ -1,8 +1,6 @@ -using System; -using System.Linq; -using Fractural.Tasks.Internal; -using System.Threading; +using Fractural.Tasks.Internal; using Godot; +using System; namespace Fractural.Tasks { @@ -16,31 +14,8 @@ public enum PlayerLoopTiming { Process = 0, PhysicsProcess = 1, - } - - [Flags] - public enum InjectPlayerLoopTimings - { - /// - /// Preset: All loops(default). - /// - All = Process | PhysicsProcess, - - /// - /// Preset: All without last except LastPostLateUpdate. - /// - Standard = Process | PhysicsProcess, - - /// - /// Preset: Minimum pattern, Update | PhysicsProcess | LastPostLateUpdate - /// - Minimum = - Process | PhysicsProcess, - - // PlayerLoopTiming - - PhysicsProcess = 1, - Process = 2, + PauseProcess = 2, + PausePhysicsProcess = 3, } public interface IPlayerLoopItem @@ -85,7 +60,7 @@ public static GDTaskPlayerLoopAutoload Global get { if (s_Global != null) return s_Global; - + var newInstance = new GDTaskPlayerLoopAutoload(); newInstance.Initialize(); var currentScene = ((SceneTree)Engine.GetMainLoop()).CurrentScene; @@ -104,6 +79,7 @@ public static GDTaskPlayerLoopAutoload Global private int mainThreadId; private ContinuationQueue[] yielders; private PlayerLoopRunner[] runners; + private ProcessListener processListener; public override void _Ready() { @@ -118,15 +94,25 @@ public override void _Ready() private void Initialize() { + ProcessMode = ProcessModeEnum.Pausable; mainThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId; yielders = new[] { new ContinuationQueue(PlayerLoopTiming.Process), new ContinuationQueue(PlayerLoopTiming.PhysicsProcess), + new ContinuationQueue(PlayerLoopTiming.PauseProcess), + new ContinuationQueue(PlayerLoopTiming.PausePhysicsProcess), }; runners = new[] { new PlayerLoopRunner(PlayerLoopTiming.Process), new PlayerLoopRunner(PlayerLoopTiming.PhysicsProcess), + new PlayerLoopRunner(PlayerLoopTiming.PauseProcess), + new PlayerLoopRunner(PlayerLoopTiming.PausePhysicsProcess), }; + processListener = new ProcessListener(); + AddChild(processListener); + processListener.ProcessMode = ProcessModeEnum.Always; + processListener.OnProcess += PauseProcess; + processListener.OnPhysicsProcess += PausePhysicsProcess; } public override void _Notification(int what) @@ -147,14 +133,26 @@ public override void _Notification(int what) public override void _Process(double delta) { - yielders[(int) PlayerLoopTiming.Process].Run(); - runners[(int) PlayerLoopTiming.Process].Run(); + yielders[(int)PlayerLoopTiming.Process].Run(); + runners[(int)PlayerLoopTiming.Process].Run(); } public override void _PhysicsProcess(double delta) { - yielders[(int) PlayerLoopTiming.PhysicsProcess].Run(); - runners[(int) PlayerLoopTiming.PhysicsProcess].Run(); + yielders[(int)PlayerLoopTiming.PhysicsProcess].Run(); + runners[(int)PlayerLoopTiming.PhysicsProcess].Run(); + } + + private void PauseProcess(double delta) + { + yielders[(int)PlayerLoopTiming.PauseProcess].Run(); + runners[(int)PlayerLoopTiming.PauseProcess].Run(); + } + + private void PausePhysicsProcess(double delta) + { + yielders[(int)PlayerLoopTiming.PausePhysicsProcess].Run(); + runners[(int)PlayerLoopTiming.PausePhysicsProcess].Run(); } } } diff --git a/addons/GDTask/Autoload/ProcessListener.cs b/addons/GDTask/Autoload/ProcessListener.cs new file mode 100644 index 0000000..142533b --- /dev/null +++ b/addons/GDTask/Autoload/ProcessListener.cs @@ -0,0 +1,21 @@ +using Godot; +using System; + +namespace Fractural.Tasks +{ + public partial class ProcessListener : Node + { + public event Action OnProcess; + public event Action OnPhysicsProcess; + + public override void _Process(double delta) + { + OnProcess?.Invoke(delta); + } + + public override void _PhysicsProcess(double delta) + { + OnPhysicsProcess?.Invoke(delta); + } + } +} diff --git a/addons/GDTask/GDTask.Delay.cs b/addons/GDTask/GDTask.Delay.cs index 35e4fd5..8bf5599 100644 --- a/addons/GDTask/GDTask.Delay.cs +++ b/addons/GDTask/GDTask.Delay.cs @@ -1,9 +1,8 @@ using Fractural.Tasks.Internal; +using Godot; using System; -using System.Collections; using System.Runtime.CompilerServices; using System.Threading; -using Godot; namespace Fractural.Tasks { @@ -82,7 +81,7 @@ public static GDTask WaitForEndOfFrame(CancellationToken cancellationToken) } /// - /// Same as GDTask.Yield(PlayerLoopTiming.LastPhysicsProcess). + /// Same as GDTask.Yield(PlayerLoopTiming.PhysicsProcess). /// public static YieldAwaitable WaitForPhysicsProcess() { @@ -90,7 +89,7 @@ public static YieldAwaitable WaitForPhysicsProcess() } /// - /// Same as GDTask.Yield(PlayerLoopTiming.LastPhysicsProcess, cancellationToken). + /// Same as GDTask.Yield(PlayerLoopTiming.PhysicsProcess, cancellationToken). /// public static GDTask WaitForPhysicsProcess(CancellationToken cancellationToken) { @@ -138,7 +137,6 @@ public static GDTask WaitForPhysicsProcess(CancellationToken cancellationToken) delayType = DelayType.Realtime; } #endif - switch (delayType) { case DelayType.Realtime: @@ -566,11 +564,11 @@ public bool MoveNext() } } - if (timing == PlayerLoopTiming.Process) + if (timing == PlayerLoopTiming.Process || timing == PlayerLoopTiming.PauseProcess) elapsed += GDTaskPlayerLoopAutoload.Global.DeltaTime; else elapsed += GDTaskPlayerLoopAutoload.Global.PhysicsDeltaTime; - + if (elapsed >= delayTimeSpan) { core.TrySetResult(null); diff --git a/addons/GDTask/Internal/ContinuationQueue.cs b/addons/GDTask/Internal/ContinuationQueue.cs index 65bed06..b35736b 100644 --- a/addons/GDTask/Internal/ContinuationQueue.cs +++ b/addons/GDTask/Internal/ContinuationQueue.cs @@ -95,7 +95,11 @@ public void Run() case PlayerLoopTiming.Process: Process(); break; - default: + case PlayerLoopTiming.PausePhysicsProcess: + PausePhysicsProcess(); + break; + case PlayerLoopTiming.PauseProcess: + PauseProcess(); break; } #else @@ -105,6 +109,8 @@ public void Run() void PhysicsProcess() => RunCore(); void Process() => RunCore(); + void PausePhysicsProcess() => RunCore(); + void PauseProcess() => RunCore(); [System.Diagnostics.DebuggerHidden] void RunCore() diff --git a/addons/GDTask/Internal/PlayerLoopRunner.cs b/addons/GDTask/Internal/PlayerLoopRunner.cs index 7baacb9..0c344ec 100644 --- a/addons/GDTask/Internal/PlayerLoopRunner.cs +++ b/addons/GDTask/Internal/PlayerLoopRunner.cs @@ -82,6 +82,12 @@ public void Run() case PlayerLoopTiming.Process: Process(); break; + case PlayerLoopTiming.PausePhysicsProcess: + PausePhysicsProcess(); + break; + case PlayerLoopTiming.PauseProcess: + PauseProcess(); + break; } #else RunCore(); @@ -90,6 +96,8 @@ public void Run() void PhysicsProcess() => RunCore(); void Process() => RunCore(); + void PausePhysicsProcess() => RunCore(); + void PauseProcess() => RunCore(); [System.Diagnostics.DebuggerHidden] diff --git a/tests/manual/Test.cs b/tests/manual/Test.cs index d2171c0..b2528c7 100644 --- a/tests/manual/Test.cs +++ b/tests/manual/Test.cs @@ -5,12 +5,14 @@ namespace Tests.Manual { - public partial class Test : Node2D + public partial class Test : Node { [Export] private bool runTestOnReady; [Export] private NodePath spritePath; + [Export] + private Label pauseLabel; public Sprite2D sprite; public override void _Ready() @@ -18,43 +20,54 @@ public override void _Ready() sprite = GetNode(spritePath); if (runTestOnReady) Run().Forget(); + ProcessMode = ProcessModeEnum.Always; + pauseLabel.Text = GetTree().Paused ? "Paused" : "Unpaused"; } public override void _Input(InputEvent @event) { - if (@event.IsActionReleased("ui_select")) + if (@event.IsActionReleased("ui_left")) { Run().Forget(); } + else if (@event.IsActionReleased("ui_right")) + { + RunPause().Forget(); + } + else if (@event.IsActionReleased("ui_up")) + { + GetTree().Paused = !GetTree().Paused; + pauseLabel.Text = GetTree().Paused ? "Paused" : "Unpaused"; + } } private async GDTaskVoid Run() { - GD.Print("Pre delay"); + GD.Print("Run: Pre delay"); sprite.Visible = false; await GDTask.Delay(TimeSpan.FromSeconds(3)); sprite.Visible = true; - GD.Print("Post delay after 3 seconds"); + GD.Print("Run: Post delay after 3 seconds"); - GD.Print("Pre RunWithResult"); + GD.Print("Run: Pre RunWithResult"); string result = await RunWithResult(); - GD.Print($"Post got result: {result}"); + GD.Print($"Run: Post got result: {result}"); - GD.Print("LongTask started"); + GD.Print("Run: LongTask started"); var cts = new CancellationTokenSource(); CancellableReallyLongTask(cts.Token).Forget(); await GDTask.Delay(TimeSpan.FromSeconds(3)); cts.Cancel(); - GD.Print("LongTask cancelled"); + GD.Print("Run: LongTask cancelled"); await GDTask.WaitForEndOfFrame(); - GD.Print("WaitForEndOfFrame"); + GD.Print("Run: WaitForEndOfFrame"); await GDTask.WaitForPhysicsProcess(); - GD.Print("WaitForPhysicsProcess"); + GD.Print("Run: WaitForPhysicsProcess"); await GDTask.NextFrame(); - GD.Print("NextFrame"); + GD.Print("Run: NextFrame"); } private async GDTask RunWithResult() @@ -66,13 +79,60 @@ private async GDTask RunWithResult() private async GDTaskVoid CancellableReallyLongTask(CancellationToken cancellationToken) { int seconds = 10; - GD.Print($"Starting long task ({seconds} seconds long)."); + GD.Print($"Run: Starting long task ({seconds} seconds long)."); for (int i = 0; i < seconds; i++) { - GD.Print($"Working on long task for {i} seconds..."); + GD.Print($"Run: Working on long task for {i} seconds..."); await GDTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cancellationToken); } - GD.Print("Finished long task."); + GD.Print("Run: Finished long task."); + } + + private async GDTaskVoid RunPause() + { + GD.Print("RunPause: Pre delay"); + sprite.Visible = false; + await GDTask.Delay(TimeSpan.FromSeconds(3), PlayerLoopTiming.PauseProcess); + sprite.Visible = true; + GD.Print("RunPause: Post delay after 3 seconds"); + + GD.Print("RunPause: Pre RunWithResult"); + string result = await RunWithResultPause(); + GD.Print($"RunPause: Post got result: {result}"); + + GD.Print("RunPause: LongTask started"); + var cts = new CancellationTokenSource(); + + CancellableReallyLongTaskPause(cts.Token).Forget(); + + await GDTask.Delay(TimeSpan.FromSeconds(3), PlayerLoopTiming.PauseProcess); + cts.Cancel(); + GD.Print("RunPause: LongTask cancelled"); + + await GDTask.Yield(PlayerLoopTiming.PauseProcess); + GD.Print("RunPause: Yield(PlayerLoopTiming.PauseProcess)"); + await GDTask.Yield(PlayerLoopTiming.PausePhysicsProcess); + GD.Print("RunPause: Yield(PlayerLoopTiming.PausePhysicsProcess)"); + await GDTask.NextFrame(PlayerLoopTiming.PauseProcess); + GD.Print("RunPause: NextFrame"); + } + + private async GDTask RunWithResultPause() + { + await GDTask.Delay(TimeSpan.FromSeconds(2), PlayerLoopTiming.PauseProcess); + return "Hello"; + } + + private async GDTaskVoid CancellableReallyLongTaskPause(CancellationToken cancellationToken) + { + int seconds = 10; + GD.Print($"RunPause: Starting long task ({seconds} seconds long)."); + for (int i = 0; i < seconds; i++) + { + GD.Print($"RunPause: Working on long task for {i} seconds..."); + await GDTask.Delay(TimeSpan.FromSeconds(1), PlayerLoopTiming.PauseProcess, cancellationToken); + } + GD.Print("RunPause: Finished long task."); } } } diff --git a/tests/manual/Test.tscn b/tests/manual/Test.tscn index ea67b9e..d7a3a0b 100644 --- a/tests/manual/Test.tscn +++ b/tests/manual/Test.tscn @@ -3,11 +3,32 @@ [ext_resource type="Script" path="res://tests/manual/Test.cs" id="1"] [ext_resource type="Texture2D" uid="uid://cw01ge0bgos8m" path="res://icon.png" id="2"] -[node name="Test" type="Node2D"] +[node name="Test" type="Node" node_paths=PackedStringArray("pauseLabel")] script = ExtResource("1") -spritePath = NodePath("Sprite2D") +spritePath = NodePath("Node2D/Sprite2D") +pauseLabel = NodePath("UI/Control/PauseLabel") -[node name="Sprite2D" type="Sprite2D" parent="."] +[node name="Node2D" type="Node2D" parent="."] + +[node name="Sprite2D" type="Sprite2D" parent="Node2D"] texture = ExtResource("2") -[node name="Camera2D" type="Camera2D" parent="."] +[node name="Camera2D" type="Camera2D" parent="Node2D"] + +[node name="UI" type="CanvasLayer" parent="."] + +[node name="Control" type="Control" parent="UI"] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="PauseLabel" type="Label" parent="UI/Control"] +layout_mode = 0 +offset_left = 18.0 +offset_top = 18.0 +offset_right = 75.0 +offset_bottom = 41.0 +text = "Paused"