From b8d4e62e2cef5ab29927c0c0905441bafc7a821d Mon Sep 17 00:00:00 2001 From: Inspiaaa Date: Wed, 4 Sep 2024 12:51:03 +0200 Subject: [PATCH] Migrate code to Godot 4 - Improve code formatting using latest C# features now supported by the latest .NET version - Update logic for Godot 4 --- src/Co.cs | 153 +++++++------- src/CoroutineBase.cs | 255 +++++++++++------------ src/CoroutineManager.cs | 125 +++++------ src/Coroutines/AwaitCoroutine.cs | 79 ++++--- src/Coroutines/Coroutine.cs | 123 ++++++----- src/Coroutines/ParallelCoroutine.cs | 53 +++-- src/Coroutines/RepeatCoroutine.cs | 77 ++++--- src/Coroutines/SequentialCoroutine.cs | 61 +++--- src/Coroutines/TweenCoroutine.cs | 51 ++--- src/Coroutines/WaitDelayCoroutine.cs | 35 ++-- src/Coroutines/WaitForSignalCoroutine.cs | 40 ++-- src/Coroutines/WaitUntilCoroutine.cs | 49 +++-- src/Coroutines/WaitWhileCoroutine.cs | 49 +++-- src/ICoroutineStopListener.cs | 11 +- src/Util/TimeScheduler.cs | 63 ------ 15 files changed, 572 insertions(+), 652 deletions(-) delete mode 100644 src/Util/TimeScheduler.cs diff --git a/src/Co.cs b/src/Co.cs index 48bef93..9909111 100644 --- a/src/Co.cs +++ b/src/Co.cs @@ -1,126 +1,129 @@ -using Godot; using System; using System.Collections; using System.Threading.Tasks; +using Godot; // Hierarchical Coroutines v1.0 for Godot // by @Inspiaaa -namespace HCoroutines +namespace HCoroutines; + +/// +/// Class that allows for easy access to the standard coroutine types. +/// +public static class Co { - /// - /// Class that allows for easy access to the standard coroutine types. - /// - public static class Co + private static Coroutine[] GetCoroutines(IEnumerator[] enumerators) { - private static Coroutine[] GetCoroutines(IEnumerator[] enumerators) - { - Coroutine[] coroutines = new Coroutine[enumerators.Length]; - - for (int i = 0; i < enumerators.Length; i++) - { - coroutines[i] = new Coroutine(enumerators[i]); - } + Coroutine[] coroutines = new Coroutine[enumerators.Length]; - return coroutines; + for (int i = 0; i < enumerators.Length; i++) + { + coroutines[i] = new Coroutine(enumerators[i]); } - public static float DeltaTime => CoroutineManager.Instance.DeltaTime; + return coroutines; + } + public static float DeltaTime => CoroutineManager.Instance.DeltaTime; + public static double DeltaTimeDouble => CoroutineManager.Instance.DeltaTimeDouble; - public static void Run(CoroutineBase coroutine) - => CoroutineManager.Instance.StartCoroutine(coroutine); - public static Coroutine Run(IEnumerator coroutine) - { - Coroutine co = new Coroutine(coroutine); - CoroutineManager.Instance.StartCoroutine(co); - return co; - } + public static void Run(CoroutineBase coroutine) + => CoroutineManager.Instance.StartCoroutine(coroutine); - public static Coroutine Run(Func creator) - => Run(creator()); + public static Coroutine Run(IEnumerator coroutine) + { + Coroutine co = new Coroutine(coroutine); + CoroutineManager.Instance.StartCoroutine(co); + return co; + } - public static Coroutine Run(Func creator) - { - Coroutine coroutine = new Coroutine(creator); - CoroutineManager.Instance.StartCoroutine(coroutine); - return coroutine; - } + public static Coroutine Run(Func creator) + => Run(creator()); + public static Coroutine Run(Func creator) + { + Coroutine coroutine = new Coroutine(creator); + CoroutineManager.Instance.StartCoroutine(coroutine); + return coroutine; + } - public static Coroutine Coroutine(IEnumerator enumerator) - => new Coroutine(enumerator); - public static Coroutine Coroutine(Func creator) - => new Coroutine(creator); + public static Coroutine Coroutine(IEnumerator enumerator) + => new Coroutine(enumerator); + public static Coroutine Coroutine(Func creator) + => new Coroutine(creator); - public static ParallelCoroutine Parallel(params IEnumerator[] enumerators) - => new ParallelCoroutine(GetCoroutines(enumerators)); - public static ParallelCoroutine Parallel(params CoroutineBase[] coroutines) - => new ParallelCoroutine(coroutines); + public static ParallelCoroutine Parallel(params IEnumerator[] enumerators) + => new ParallelCoroutine(GetCoroutines(enumerators)); + public static ParallelCoroutine Parallel(params CoroutineBase[] coroutines) + => new ParallelCoroutine(coroutines); - public static SequentialCoroutine Sequence(params IEnumerator[] enumerators) - => new SequentialCoroutine(GetCoroutines(enumerators)); - public static SequentialCoroutine Sequence(params CoroutineBase[] coroutines) - => new SequentialCoroutine(coroutines); + public static SequentialCoroutine Sequence(params IEnumerator[] enumerators) + => new SequentialCoroutine(GetCoroutines(enumerators)); + public static SequentialCoroutine Sequence(params CoroutineBase[] coroutines) + => new SequentialCoroutine(coroutines); - public static WaitDelayCoroutine Wait(float delay) - => new WaitDelayCoroutine(delay); + public static WaitDelayCoroutine Wait(float delay) + => new WaitDelayCoroutine(delay); - public static WaitWhileCoroutine WaitWhile(Func condition) - => new WaitWhileCoroutine(condition); + public static WaitWhileCoroutine WaitWhile(Func condition) + => new WaitWhileCoroutine(condition); - public static WaitUntilCoroutine WaitUntil(Func condition) - => new WaitUntilCoroutine(condition); + public static WaitUntilCoroutine WaitUntil(Func condition) + => new WaitUntilCoroutine(condition); - public static WaitForSignalCoroutine WaitForSignal(Godot.Object obj, string signal) - => new WaitForSignalCoroutine(obj, signal); + public static WaitForSignalCoroutine WaitForSignal(GodotObject obj, string signal) + => new WaitForSignalCoroutine(obj, signal); - public static RepeatCoroutine Repeat(int times, Func creator) - => new RepeatCoroutine(times, creator); - public static RepeatCoroutine Repeat(int times, Func creator) - => new RepeatCoroutine(times, coroutine => creator()); + public static RepeatCoroutine Repeat(int times, Func creator) + => new RepeatCoroutine(times, creator); - public static RepeatCoroutine Repeat(int times, Func creator) - => new RepeatCoroutine(times, coroutine => new Coroutine(creator(coroutine))); + public static RepeatCoroutine Repeat(int times, Func creator) + => new RepeatCoroutine(times, coroutine => creator()); - public static RepeatCoroutine Repeat(int times, Func creator) - => new RepeatCoroutine(times, coroutine => new Coroutine(creator())); + public static RepeatCoroutine Repeat(int times, Func creator) + => new RepeatCoroutine(times, coroutine => new Coroutine(creator(coroutine))); + public static RepeatCoroutine Repeat(int times, Func creator) + => new RepeatCoroutine(times, coroutine => new Coroutine(creator())); - public static RepeatCoroutine RepeatInfinitely(Func creator) - => new RepeatCoroutine(-1, creator); - public static RepeatCoroutine RepeatInfinitely(Func creator) - => new RepeatCoroutine(-1, coroutine => creator()); + public static RepeatCoroutine RepeatInfinitely(Func creator) + => new RepeatCoroutine(-1, creator); - public static RepeatCoroutine RepeatInfinitely(Func creator) - => new RepeatCoroutine(-1, coroutine => new Coroutine(creator(coroutine))); + public static RepeatCoroutine RepeatInfinitely(Func creator) + => new RepeatCoroutine(-1, coroutine => creator()); - public static RepeatCoroutine RepeatInfinitely(Func creator) - => new RepeatCoroutine(-1, coroutine => new Coroutine(creator())); + public static RepeatCoroutine RepeatInfinitely(Func creator) + => new RepeatCoroutine(-1, coroutine => new Coroutine(creator(coroutine))); + public static RepeatCoroutine RepeatInfinitely(Func creator) + => new RepeatCoroutine(-1, coroutine => new Coroutine(creator())); - public static TweenCoroutine Tween(Action setupTween) - => new TweenCoroutine(setupTween); + public static TweenCoroutine Tween(Func createTween) + => new TweenCoroutine(createTween); + + // TODO: Create additional methods for creating Tween Coroutines (e.g. create Tween on Manager, setup via Action, + // or bound to a node) - public static AwaitCoroutine Await(Task task) - => new AwaitCoroutine(task); - public static AwaitCoroutine Await(Task task) - => new AwaitCoroutine(task); - } + public static AwaitCoroutine Await(Task task) + => new AwaitCoroutine(task); + + public static AwaitCoroutine Await(Task task) + => new AwaitCoroutine(task); } \ No newline at end of file diff --git a/src/CoroutineBase.cs b/src/CoroutineBase.cs index 1481f35..51b021e 100644 --- a/src/CoroutineBase.cs +++ b/src/CoroutineBase.cs @@ -1,164 +1,163 @@ using Godot; using System; -namespace HCoroutines +namespace HCoroutines; +/// +/// Base class of all coroutines that allows for pausing / resuming / killing / ... the coroutine. +/// It is also responsible for managing the hierarchical structure and organisation +/// of coroutine nodes. +/// The coroutines themselves act like a doubly linked list, so that +/// the list of children can be efficiently managed and even modified during iteration. +/// +public class CoroutineBase : ICoroutineStopListener { + public CoroutineManager Manager; + // TODO: Implement as event? + public ICoroutineStopListener StopListener; + + protected CoroutineBase firstChild, lastChild; + protected CoroutineBase previousSibling, nextSibling; + + public bool IsAlive = true; + public bool IsPlaying = false; + + public void StartCoroutine(CoroutineBase coroutine) + { + coroutine.StopListener = this; + coroutine.Manager = Manager; + + AddChild(coroutine); + coroutine.OnEnter(); + } + /// - /// Base class of all coroutines that allows for pausing / resuming / killing / ... the coroutine. - /// It is also responsible for managing the hierarchical structure and organisation - /// of coroutine nodes. - /// The coroutines themselves act like a doubly linked list, so that - /// the list of children can be efficiently managed and even modified during iteration. + /// Called when the coroutine starts. /// - public class CoroutineBase : ICoroutineStopListener + public virtual void OnEnter() { - public CoroutineManager manager; - public ICoroutineStopListener stopListener; + ResumeUpdates(); + } - protected CoroutineBase firstChild, lastChild; - protected CoroutineBase previousSibling, nextSibling; + /// + /// Called every frame if the coroutine is playing. + /// + public virtual void Update() { } - public bool isAlive = true; - public bool isPlaying = false; + /// + /// Called when the coroutine is killed. + /// + public virtual void OnExit() { } - public void StartCoroutine(CoroutineBase coroutine) + /// + /// Starts playing this coroutine, meaning that it will receive Update() calls + /// each frame. This is independent of the child coroutines. + /// This method only works if the coroutine is still alive. + /// + public void ResumeUpdates() + { + if (!IsAlive) { - coroutine.stopListener = this; - coroutine.manager = manager; + throw new InvalidOperationException("Cannot resume updates on dead coroutine."); + } + + IsPlaying = true; + Manager.ActivateCoroutine(this); + } + + /// + /// Stops giving the coroutine Update() calls each frame. + /// This is independent of the child coroutines. + /// + public void PauseUpdates() + { + IsPlaying = false; + Manager.DeactivateCoroutine(this); + } - AddChild(coroutine); - coroutine.OnEnter(); + public virtual void OnChildStopped(CoroutineBase child) + { + // If the parent coroutine is dead, then there is no reason to + // manually remove the child coroutines. + if (IsAlive) + { + RemoveChild(child); } + } - /// - /// Called when the coroutine starts. - /// - public virtual void OnEnter() + /// + /// Kills this coroutine and all child coroutines that were started using + /// StartCoroutine(...) on this coroutine. + /// + public void Kill() + { + if (!IsAlive) { - ResumeUpdates(); + return; } - /// - /// Called every frame if the coroutine is playing. - /// - public virtual void Update() { } - - /// - /// Called when the coroutine is killed. - /// - public virtual void OnExit() { } - - /// - /// Starts playing this coroutine, meaning that it will receive Update() calls - /// each frame. This is independent of the child coroutines. - /// This method only works if the coroutine is still alive. - /// - public void ResumeUpdates() + try { - if (!isAlive) - { - throw new InvalidOperationException("Cannot resume updates on dead coroutine."); - } + OnExit(); + } + catch (Exception e) + { + GD.PrintErr(e.ToString()); + } - isPlaying = true; - manager.ActivateCoroutine(this); + IsAlive = false; + Manager.DeactivateCoroutine(this); + + CoroutineBase child = firstChild; + while (child != null) + { + child.Kill(); + child = child.nextSibling; } - /// - /// Stops giving the coroutine Update() calls each frame. - /// This is independent of the child coroutines. - /// - public void PauseUpdates() + StopListener?.OnChildStopped(this); + } + + /// + /// Adds a coroutine as a child. + /// + protected void AddChild(CoroutineBase coroutine) + { + if (firstChild == null) { - isPlaying = false; - manager.DeactivateCoroutine(this); + firstChild = coroutine; + lastChild = coroutine; } + else + { + lastChild.nextSibling = coroutine; + coroutine.previousSibling = lastChild; + lastChild = coroutine; + } + } - public virtual void OnChildStopped(CoroutineBase child) + /// + /// Removes a child from the list of child coroutines. + /// + protected void RemoveChild(CoroutineBase coroutine) + { + if (coroutine.previousSibling != null) { - // If the parent coroutine is dead, then there is no reason to - // manually remove the child coroutines - if (isAlive) - { - RemoveChild(child); - } + coroutine.previousSibling.nextSibling = coroutine.nextSibling; } - /// - /// Kills this coroutine and all child coroutines that were started using - /// StartCoroutine(...) on this coroutine. - /// - public void Kill() + if (coroutine.nextSibling != null) { - if (!isAlive) - { - return; - } - - try - { - OnExit(); - } - catch (Exception e) - { - GD.PrintErr(e.ToString()); - } - - isAlive = false; - manager.DeactivateCoroutine(this); - - CoroutineBase child = firstChild; - while (child != null) - { - child.Kill(); - child = child.nextSibling; - } - - stopListener?.OnChildStopped(this); + coroutine.nextSibling.previousSibling = coroutine.previousSibling; } - /// - /// Adds a coroutine as a child. - /// - protected void AddChild(CoroutineBase coroutine) + if (firstChild == coroutine) { - if (firstChild == null) - { - firstChild = coroutine; - lastChild = coroutine; - } - else - { - lastChild.nextSibling = coroutine; - coroutine.previousSibling = lastChild; - lastChild = coroutine; - } + firstChild = coroutine.nextSibling; } - /// - /// Removes a child from the list of child coroutines. - /// - protected void RemoveChild(CoroutineBase coroutine) + if (lastChild == coroutine) { - if (coroutine.previousSibling != null) - { - coroutine.previousSibling.nextSibling = coroutine.nextSibling; - } - - if (coroutine.nextSibling != null) - { - coroutine.nextSibling.previousSibling = coroutine.previousSibling; - } - - if (firstChild == coroutine) - { - firstChild = coroutine.nextSibling; - } - - if (lastChild == coroutine) - { - lastChild = coroutine.previousSibling; - } + lastChild = coroutine.previousSibling; } } -} +} \ No newline at end of file diff --git a/src/CoroutineManager.cs b/src/CoroutineManager.cs index c6fabb4..f656a19 100644 --- a/src/CoroutineManager.cs +++ b/src/CoroutineManager.cs @@ -2,89 +2,90 @@ using System; using System.Collections.Generic; -namespace HCoroutines +namespace HCoroutines; + +public partial class CoroutineManager : Node { - public class CoroutineManager : Node - { - public static CoroutineManager Instance { get; private set; } - public float DeltaTime { get; set; } + public static CoroutineManager Instance { get; private set; } + public float DeltaTime { get; private set; } + public double DeltaTimeDouble { get; private set; } + + private bool isIteratingActiveCoroutines = false; + private HashSet activeCoroutines = new(); + private HashSet coroutinesToDeactivate = new(); + private HashSet coroutinesToActivate = new(); - private bool isIteratingActiveCoroutines = false; - private HashSet activeCoroutines = new HashSet(); - private HashSet coroutinesToDeactivate = new HashSet(); - private HashSet coroutinesToActivate = new HashSet(); + public void StartCoroutine(CoroutineBase coroutine) + { + coroutine.Manager = this; + coroutine.OnEnter(); + } - public void StartCoroutine(CoroutineBase coroutine) + public void ActivateCoroutine(CoroutineBase coroutine) + { + if (isIteratingActiveCoroutines) { - coroutine.manager = this; - coroutine.OnEnter(); + coroutinesToActivate.Add(coroutine); + coroutinesToDeactivate.Remove(coroutine); } - - public void ActivateCoroutine(CoroutineBase coroutine) + else { - if (isIteratingActiveCoroutines) - { - coroutinesToActivate.Add(coroutine); - coroutinesToDeactivate.Remove(coroutine); - } - else - { - activeCoroutines.Add(coroutine); - } + activeCoroutines.Add(coroutine); } + } - public void DeactivateCoroutine(CoroutineBase coroutine) + public void DeactivateCoroutine(CoroutineBase coroutine) + { + if (isIteratingActiveCoroutines) { - if (isIteratingActiveCoroutines) - { - coroutinesToDeactivate.Add(coroutine); - coroutinesToActivate.Remove(coroutine); - } - else - { - activeCoroutines.Remove(coroutine); - } + coroutinesToDeactivate.Add(coroutine); + coroutinesToActivate.Remove(coroutine); } - - public override void _EnterTree() + else { - Instance = this; + activeCoroutines.Remove(coroutine); } + } - public override void _Process(float delta) - { - DeltaTime = delta; + public override void _EnterTree() + { + Instance = this; + } - isIteratingActiveCoroutines = true; + public override void _Process(double delta) + { + DeltaTime = (float)delta; + DeltaTimeDouble = delta; - foreach (CoroutineBase coroutine in activeCoroutines) + isIteratingActiveCoroutines = true; + + foreach (CoroutineBase coroutine in activeCoroutines) + { + if (coroutine.IsAlive && coroutine.IsPlaying) { - if (coroutine.isAlive && coroutine.isPlaying) + try + { + coroutine.Update(); + } + catch (Exception e) { - try - { - coroutine.Update(); - } - catch (Exception e) - { - GD.PrintErr(e.ToString()); - } + GD.PrintErr(e.ToString()); } } + } - isIteratingActiveCoroutines = false; + isIteratingActiveCoroutines = false; - foreach (CoroutineBase coroutine in coroutinesToActivate) - { - activeCoroutines.Add(coroutine); - } - coroutinesToActivate.Clear(); + foreach (CoroutineBase coroutine in coroutinesToActivate) + { + activeCoroutines.Add(coroutine); + } + coroutinesToActivate.Clear(); - foreach (CoroutineBase coroutine in coroutinesToDeactivate) - { - activeCoroutines.Remove(coroutine); - } - coroutinesToDeactivate.Clear(); + foreach (CoroutineBase coroutine in coroutinesToDeactivate) + { + activeCoroutines.Remove(coroutine); } + coroutinesToDeactivate.Clear(); } -} +} \ No newline at end of file diff --git a/src/Coroutines/AwaitCoroutine.cs b/src/Coroutines/AwaitCoroutine.cs index e6a0e87..216c73e 100644 --- a/src/Coroutines/AwaitCoroutine.cs +++ b/src/Coroutines/AwaitCoroutine.cs @@ -1,52 +1,49 @@ -using System; -using System.Collections; using System.Threading.Tasks; -namespace HCoroutines -{ - /// - /// A coroutine that waits until an asynchronous task has been completed. - /// If the coroutine is killed before completion, the async task - /// will currently *not* be canceled. - /// - public class AwaitCoroutine : CoroutineBase - { - public readonly Task Task; +namespace HCoroutines; - public AwaitCoroutine(Task task) - { - this.Task = task; - } +/// +/// A coroutine that waits until an asynchronous task has been completed. +/// If the coroutine is killed before completion, the async task +/// will currently *not* be canceled. +/// +public class AwaitCoroutine : CoroutineBase +{ + public readonly Task Task; - public override void OnEnter() - { - // As the CoroutineManager class is not thread safe, ensure that Kill() - // is executed on the main Godot thread. - var godotTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); - Task.ContinueWith(result => Kill(), godotTaskScheduler); - } + public AwaitCoroutine(Task task) + { + this.Task = task; } - /// - /// A coroutine that waits until an asynchronous task has been completed. - /// If the coroutine is killed before completion, the async task - /// will currently *not* be canceled. - /// - public class AwaitCoroutine : CoroutineBase + public override void OnEnter() { - public readonly Task Task; + // As the CoroutineManager class is not thread safe, ensure that Kill() + // is executed on the main Godot thread. + var godotTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); + Task.ContinueWith(result => Kill(), godotTaskScheduler); + } +} - public AwaitCoroutine(Task task) - { - this.Task = task; - } +/// +/// A coroutine that waits until an asynchronous task has been completed. +/// If the coroutine is killed before completion, the async task +/// will currently *not* be canceled. +/// +public class AwaitCoroutine : CoroutineBase +{ + public readonly Task Task; - public override void OnEnter() - { - // As the CoroutineManager class is not thread safe, ensure that Kill() - // is executed on the main Godot thread. - var godotTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); - Task.ContinueWith(result => Kill(), godotTaskScheduler); - } + public AwaitCoroutine(Task task) + { + this.Task = task; + } + + public override void OnEnter() + { + // As the CoroutineManager class is not thread safe, ensure that Kill() + // is executed on the main Godot thread. + var godotTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); + Task.ContinueWith(result => Kill(), godotTaskScheduler); } } \ No newline at end of file diff --git a/src/Coroutines/Coroutine.cs b/src/Coroutines/Coroutine.cs index 8833131..812aaec 100644 --- a/src/Coroutines/Coroutine.cs +++ b/src/Coroutines/Coroutine.cs @@ -1,84 +1,83 @@ using System; using System.Collections; -using System.ComponentModel; -namespace HCoroutines +namespace HCoroutines; + +/// +/// Runs an IEnumerator as a coroutine (like Unity). +/// If it returns null, it waits until the next frame before continuing +/// the IEnumerator. If it returns another CoroutineBase, it will start it +/// and wait until this new coroutine has finished before continuing the +/// IEnumerator. +/// +public class Coroutine : CoroutineBase { - /// - /// Runs an IEnumerator as a coroutine (like Unity). - /// If it returns null, it waits until the next frame before continuing - /// the IEnumerator. If it returns another CoroutineBase, it will start it - /// and wait until this new coroutine has finished before continuing the - /// IEnumerator. - /// - public class Coroutine : CoroutineBase + private readonly IEnumerator routine; + + public Coroutine(IEnumerator routine) + { + this.routine = routine; + } + + public Coroutine(Func creator) { - private IEnumerator routine; + // TODO: Only create the coroutine OnEnter() (e.g. Sequence of Wait and Coroutine)? + this.routine = creator(this); + } - public Coroutine(IEnumerator routine) + public override void OnEnter() + { + if (routine == null) { - this.routine = routine; + Kill(); + return; } - public Coroutine(Func creator) + ResumeUpdates(); + } + + public override void Update() + { + if (!routine.MoveNext()) { - this.routine = creator(this); + Kill(); + return; } - public override void OnEnter() - { - if (routine == null) - { - Kill(); - return; - } + object obj = routine.Current; - ResumeUpdates(); + // yield return null; => do nothing. + if (obj is null) + { + return; } - public override void Update() + // yield return some coroutine; => Pause until the returned + // coroutine is finished. + if (obj is CoroutineBase childCoroutine) { - if (!routine.MoveNext()) - { - Kill(); - return; - } - - object obj = routine.Current; - - // yield return null; => do nothing. - if (obj is null) - { - return; - } - - // yield return some coroutine; => Pause until the returned - // coroutine is finished. - if (obj is CoroutineBase childCoroutine) - { - // It's important to pause before starting the child coroutine. - // Otherwise, if the child coroutine instantly terminates, which would - // lead to this coroutine resuming, it would pause this coroutine. - // That would not be correct. - PauseUpdates(); - StartCoroutine(childCoroutine); - return; - } - - // yield return some other enumerator; => Create new Coroutine - // and pause until it is finished - if (obj is IEnumerator childEnumerator) - { - PauseUpdates(); - StartCoroutine(new Coroutine(childEnumerator)); - return; - } + // It's important to pause before starting the child coroutine. + // Otherwise, if the child coroutine instantly terminates, which would + // lead to this coroutine resuming, it would pause this coroutine. + // That would not be correct. + PauseUpdates(); + StartCoroutine(childCoroutine); + return; } - public override void OnChildStopped(CoroutineBase child) + // yield return some other enumerator; => Create new Coroutine + // and pause until it is finished + if (obj is IEnumerator childEnumerator) { - base.OnChildStopped(child); - ResumeUpdates(); + PauseUpdates(); + StartCoroutine(new Coroutine(childEnumerator)); + return; } } + + public override void OnChildStopped(CoroutineBase child) + { + base.OnChildStopped(child); + ResumeUpdates(); + } } \ No newline at end of file diff --git a/src/Coroutines/ParallelCoroutine.cs b/src/Coroutines/ParallelCoroutine.cs index 7746da4..de13ab6 100644 --- a/src/Coroutines/ParallelCoroutine.cs +++ b/src/Coroutines/ParallelCoroutine.cs @@ -1,40 +1,39 @@ -namespace HCoroutines +namespace HCoroutines; + +/// +/// Runs multiple coroutines in parallel and exits once all have completed. +/// +public class ParallelCoroutine : CoroutineBase { - /// - /// Runs multiple coroutines in parallel and exits once all have completed. - /// - public class ParallelCoroutine : CoroutineBase + private readonly CoroutineBase[] coroutines; + + public ParallelCoroutine(params CoroutineBase[] coroutines) { - private CoroutineBase[] coroutines; + this.coroutines = coroutines; + } - public ParallelCoroutine(params CoroutineBase[] coroutines) + public override void OnEnter() + { + if (coroutines.Length == 0) { - this.coroutines = coroutines; + Kill(); + return; } - public override void OnEnter() + foreach (CoroutineBase coroutine in coroutines) { - if (coroutines.Length == 0) - { - Kill(); - return; - } - - foreach (CoroutineBase coroutine in coroutines) - { - StartCoroutine(coroutine); - } + StartCoroutine(coroutine); } + } - public override void OnChildStopped(CoroutineBase child) - { - base.OnChildStopped(child); + public override void OnChildStopped(CoroutineBase child) + { + base.OnChildStopped(child); - // If there are no more actively running coroutines, stop. - if (firstChild == null) - { - Kill(); - } + // If there are no more actively running coroutines, stop. + if (firstChild == null) + { + Kill(); } } } \ No newline at end of file diff --git a/src/Coroutines/RepeatCoroutine.cs b/src/Coroutines/RepeatCoroutine.cs index 034fb97..6682bf0 100644 --- a/src/Coroutines/RepeatCoroutine.cs +++ b/src/Coroutines/RepeatCoroutine.cs @@ -1,54 +1,53 @@ using System; -namespace HCoroutines +namespace HCoroutines; + +/// +/// Runs a coroutine multiple times. Each time the coroutine is finished, +/// it is restarted. +/// +public class RepeatCoroutine : CoroutineBase { - /// - /// Runs a coroutine multiple times. Each time the coroutine is finished, - /// it is restarted. - /// - public class RepeatCoroutine : CoroutineBase - { - private int repeatTimes; - private int currentRepeatCount; - private Func coroutineCreator; + private readonly int repeatTimes; + private int currentRepeatCount; + private readonly Func coroutineCreator; - private bool IsInfinite => repeatTimes == -1; + private bool IsInfinite => repeatTimes == -1; - public RepeatCoroutine(int repeatTimes, Func coroutineCreator) - { - this.repeatTimes = repeatTimes; - this.coroutineCreator = coroutineCreator; - } + public RepeatCoroutine(int repeatTimes, Func coroutineCreator) + { + this.repeatTimes = repeatTimes; + this.coroutineCreator = coroutineCreator; + } - public override void OnEnter() + public override void OnEnter() + { + if (repeatTimes == 0) { - if (repeatTimes == 0) - { - Kill(); - return; - } - - Repeat(); + Kill(); + return; } - private void Repeat() - { - currentRepeatCount += 1; - CoroutineBase coroutine = coroutineCreator.Invoke(this); - StartCoroutine(coroutine); - } + Repeat(); + } - public override void OnChildStopped(CoroutineBase child) - { - base.OnChildStopped(child); + private void Repeat() + { + currentRepeatCount += 1; + CoroutineBase coroutine = coroutineCreator.Invoke(this); + StartCoroutine(coroutine); + } - if (!IsInfinite && currentRepeatCount > repeatTimes) - { - Kill(); - return; - } + public override void OnChildStopped(CoroutineBase child) + { + base.OnChildStopped(child); - Repeat(); + if (!IsInfinite && currentRepeatCount > repeatTimes) + { + Kill(); + return; } + + Repeat(); } } \ No newline at end of file diff --git a/src/Coroutines/SequentialCoroutine.cs b/src/Coroutines/SequentialCoroutine.cs index a51f806..dcd5e94 100644 --- a/src/Coroutines/SequentialCoroutine.cs +++ b/src/Coroutines/SequentialCoroutine.cs @@ -1,43 +1,42 @@ -namespace HCoroutines +namespace HCoroutines; + +/// +/// Runs multiple coroutines one after another. Waits until the first +/// one has finished before starting the second one, ... +/// +public class SequentialCoroutine : CoroutineBase { - /// - /// Runs multiple coroutines one after another. Waits until the first - /// one has finished before starting the second one, ... - /// - public class SequentialCoroutine : CoroutineBase + private readonly CoroutineBase[] coroutines; + private int idx = 0; + + public SequentialCoroutine(params CoroutineBase[] coroutines) { - private CoroutineBase[] coroutines; - private int idx = 0; + this.coroutines = coroutines; + } - public SequentialCoroutine(params CoroutineBase[] coroutines) + public override void OnEnter() + { + if (coroutines.Length == 0) { - this.coroutines = coroutines; + Kill(); + return; } - public override void OnEnter() - { - if (coroutines.Length == 0) - { - Kill(); - return; - } + StartCoroutine(coroutines[0]); + } - StartCoroutine(coroutines[0]); - } + public override void OnChildStopped(CoroutineBase child) + { + base.OnChildStopped(child); - public override void OnChildStopped(CoroutineBase child) + idx += 1; + if (idx < coroutines.Length) { - base.OnChildStopped(child); - - idx += 1; - if (idx < coroutines.Length) - { - StartCoroutine(coroutines[idx]); - } - else - { - Kill(); - } + StartCoroutine(coroutines[idx]); + } + else + { + Kill(); } } } \ No newline at end of file diff --git a/src/Coroutines/TweenCoroutine.cs b/src/Coroutines/TweenCoroutine.cs index 75a2e91..cf635e2 100644 --- a/src/Coroutines/TweenCoroutine.cs +++ b/src/Coroutines/TweenCoroutine.cs @@ -2,35 +2,38 @@ using System; using HCoroutines.Util; -namespace HCoroutines +namespace HCoroutines; + +/// +/// A coroutine that manages a tween instance. +/// When the tween is finished, the coroutine also finishes. +/// If the coroutine is killed before that, it also kills the tween instance. +/// +public class TweenCoroutine : CoroutineBase { - /// - /// A coroutine that manages a tween instance. - /// When the tween is finished, the coroutine also finishes. - /// If the coroutine is killed before that, it also kills the tween instance. - /// - public class TweenCoroutine : CoroutineBase + private readonly Func createTween; + private Tween tween; + private int schedulerId; + + public TweenCoroutine(Func createTween) { - private Action setupTween; - private SceneTreeTween tween; - private int schedulerId; + this.createTween = createTween; + } - public TweenCoroutine(Action setupTween) - { - this.setupTween = setupTween; + public override void OnEnter() + { + tween = createTween(); + + if (!tween.IsValid() || !tween.IsRunning()) { + Kill(); + return; } - public override void OnEnter() - { - tween = manager.CreateTween(); - setupTween(tween); - schedulerId = TimeScheduler.Instance.ScheduleOnSignal(Kill, tween, "finished"); - } + tween.Finished += Kill; + } - public override void OnExit() - { - tween.Kill(); - TimeScheduler.Instance.CancelSchedule(schedulerId); - } + public override void OnExit() + { + tween.Kill(); } } \ No newline at end of file diff --git a/src/Coroutines/WaitDelayCoroutine.cs b/src/Coroutines/WaitDelayCoroutine.cs index 8567bde..3e08ae8 100644 --- a/src/Coroutines/WaitDelayCoroutine.cs +++ b/src/Coroutines/WaitDelayCoroutine.cs @@ -1,28 +1,21 @@ using HCoroutines.Util; -namespace HCoroutines -{ - /// - /// Waits until a certain delay has passed. - /// - public class WaitDelayCoroutine : CoroutineBase - { - private float delay; - private int schedulerId; +namespace HCoroutines; - public WaitDelayCoroutine(float delay) - { - this.delay = delay; - } +/// +/// Waits until a certain delay has passed. +/// +public class WaitDelayCoroutine : CoroutineBase +{ + private readonly float delay; - public override void OnEnter() - { - schedulerId = TimeScheduler.Instance.Schedule(Kill, delay); - } + public WaitDelayCoroutine(float delay) + { + this.delay = delay; + } - public override void OnExit() - { - TimeScheduler.Instance.CancelSchedule(schedulerId); - } + public override void OnEnter() + { + Manager.GetTree().CreateTimer(delay).Timeout += Kill; } } \ No newline at end of file diff --git a/src/Coroutines/WaitForSignalCoroutine.cs b/src/Coroutines/WaitForSignalCoroutine.cs index 1062c01..f1b7cca 100644 --- a/src/Coroutines/WaitForSignalCoroutine.cs +++ b/src/Coroutines/WaitForSignalCoroutine.cs @@ -1,30 +1,24 @@ +using Godot; using HCoroutines.Util; -namespace HCoroutines -{ - /// - /// Waits until a certain signal has been fired. - /// - public class WaitForSignalCoroutine : CoroutineBase - { - private Godot.Object targetObject; - private string targetSignal; - private int schedulerId; +namespace HCoroutines; - public WaitForSignalCoroutine(Godot.Object obj, string signal) - { - this.targetObject = obj; - this.targetSignal = signal; - } +/// +/// Waits until a certain signal has been fired. +/// +public class WaitForSignalCoroutine : CoroutineBase +{ + private readonly GodotObject targetObject; + private readonly string targetSignal; + private int schedulerId; - public override void OnEnter() - { - schedulerId = TimeScheduler.Instance.ScheduleOnSignal(Kill, targetObject, targetSignal); - } + public WaitForSignalCoroutine(GodotObject obj, string signal) + { + this.targetObject = obj; + this.targetSignal = signal; + } - public override void OnExit() - { - TimeScheduler.Instance.CancelSchedule(schedulerId); - } + public override void OnEnter() { + Manager.ToSignal(targetObject, targetSignal).OnCompleted(Kill); } } \ No newline at end of file diff --git a/src/Coroutines/WaitUntilCoroutine.cs b/src/Coroutines/WaitUntilCoroutine.cs index 93daa39..228e4ae 100644 --- a/src/Coroutines/WaitUntilCoroutine.cs +++ b/src/Coroutines/WaitUntilCoroutine.cs @@ -1,36 +1,35 @@ using System; -namespace HCoroutines +namespace HCoroutines; + +/// +/// Waits until a certain condition is true. +/// +public class WaitUntilCoroutine : CoroutineBase { - /// - /// Waits until a certain condition is true. - /// - public class WaitUntilCoroutine : CoroutineBase - { - private Func condition; + private readonly Func condition; - public WaitUntilCoroutine(Func condition) - { - this.condition = condition; - } + public WaitUntilCoroutine(Func condition) + { + this.condition = condition; + } - public override void OnEnter() - { - CheckCondition(); - if (isAlive) ResumeUpdates(); - } + public override void OnEnter() + { + CheckCondition(); + if (IsAlive) ResumeUpdates(); + } - public override void Update() - { - CheckCondition(); - } + public override void Update() + { + CheckCondition(); + } - private void CheckCondition() + private void CheckCondition() + { + if (condition()) { - if (this.condition()) - { - Kill(); - } + Kill(); } } } \ No newline at end of file diff --git a/src/Coroutines/WaitWhileCoroutine.cs b/src/Coroutines/WaitWhileCoroutine.cs index 3a30b40..1688925 100644 --- a/src/Coroutines/WaitWhileCoroutine.cs +++ b/src/Coroutines/WaitWhileCoroutine.cs @@ -1,36 +1,35 @@ using System; -namespace HCoroutines +namespace HCoroutines; + +/// +/// Waits while a certain condition is true. +/// +public class WaitWhileCoroutine : CoroutineBase { - /// - /// Waits while a certain condition is true. - /// - public class WaitWhileCoroutine : CoroutineBase - { - private Func condition; + private readonly Func condition; - public WaitWhileCoroutine(Func condition) - { - this.condition = condition; - } + public WaitWhileCoroutine(Func condition) + { + this.condition = condition; + } - public override void OnEnter() - { - CheckCondition(); - if (isAlive) ResumeUpdates(); - } + public override void OnEnter() + { + CheckCondition(); + if (IsAlive) ResumeUpdates(); + } - public override void Update() - { - CheckCondition(); - } + public override void Update() + { + CheckCondition(); + } - private void CheckCondition() + private void CheckCondition() + { + if (!condition()) { - if (!this.condition()) - { - Kill(); - } + Kill(); } } } \ No newline at end of file diff --git a/src/ICoroutineStopListener.cs b/src/ICoroutineStopListener.cs index de5a02c..e61db34 100644 --- a/src/ICoroutineStopListener.cs +++ b/src/ICoroutineStopListener.cs @@ -1,7 +1,6 @@ -namespace HCoroutines +namespace HCoroutines; + +public interface ICoroutineStopListener { - public interface ICoroutineStopListener - { - void OnChildStopped(CoroutineBase coroutine); - } -} + void OnChildStopped(CoroutineBase coroutine); +} \ No newline at end of file diff --git a/src/Util/TimeScheduler.cs b/src/Util/TimeScheduler.cs deleted file mode 100644 index 4dafc93..0000000 --- a/src/Util/TimeScheduler.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Godot; -using System; -using System.Collections.Generic; - -namespace HCoroutines.Util -{ - public class TimeScheduler : Node - { - public static TimeScheduler Instance { get; private set; } - - private Dictionary actionsById = new Dictionary(); - private int idCounter = 0; - - public override void _EnterTree() - { - Instance = this; - } - - private int GetNextScheduleId() - { - int id = idCounter; - // Allow for integer overflow to wrap around to the beginning. - idCounter = unchecked(idCounter + 1); - return id; - } - - public int Schedule(Action action, float delay) - { - SceneTreeTimer timer = GetTree().CreateTimer(delay, pauseModeProcess: false); - return ScheduleOnSignal(action, timer, "timeout"); - } - - public int ScheduleOnSignal(Action action, Godot.Object obj, string signal) - { - int id = GetNextScheduleId(); - actionsById[id] = action; - - obj.Connect( - signal, - this, - nameof(CallCallback), - new Godot.Collections.Array(id), - (int)ConnectFlags.Oneshot - ); - - return id; - } - - public void CancelSchedule(int scheduleId) - { - actionsById.Remove(scheduleId); - } - - private void CallCallback(int id) - { - if (actionsById.TryGetValue(id, out Action action)) - { - action.Invoke(); - actionsById.Remove(id); - } - } - } -} \ No newline at end of file