From 7a1dd7eb1a3caa6432535869121b49701746d05c Mon Sep 17 00:00:00 2001 From: Syntriax Date: Sun, 30 Mar 2025 20:29:26 +0300 Subject: [PATCH] feat: time systems added --- Engine.Systems/Time/IReadOnlyStopwatch.cs | 18 ++++ Engine.Systems/Time/IReadOnlyTicker.cs | 11 ++ Engine.Systems/Time/IReadOnlyTimer.cs | 22 ++++ Engine.Systems/Time/IStopwatch.cs | 10 ++ Engine.Systems/Time/ITicker.cs | 11 ++ Engine.Systems/Time/ITimer.cs | 10 ++ Engine.Systems/Time/StopwatchBehaviour.cs | 99 ++++++++++++++++++ Engine.Systems/Time/TickerBehaviour.cs | 38 +++++++ Engine.Systems/Time/TimerBehaviour.cs | 118 ++++++++++++++++++++++ Engine.Systems/Time/TimerState.cs | 9 ++ 10 files changed, 346 insertions(+) create mode 100644 Engine.Systems/Time/IReadOnlyStopwatch.cs create mode 100644 Engine.Systems/Time/IReadOnlyTicker.cs create mode 100644 Engine.Systems/Time/IReadOnlyTimer.cs create mode 100644 Engine.Systems/Time/IStopwatch.cs create mode 100644 Engine.Systems/Time/ITicker.cs create mode 100644 Engine.Systems/Time/ITimer.cs create mode 100644 Engine.Systems/Time/StopwatchBehaviour.cs create mode 100644 Engine.Systems/Time/TickerBehaviour.cs create mode 100644 Engine.Systems/Time/TimerBehaviour.cs create mode 100644 Engine.Systems/Time/TimerState.cs diff --git a/Engine.Systems/Time/IReadOnlyStopwatch.cs b/Engine.Systems/Time/IReadOnlyStopwatch.cs new file mode 100644 index 0000000..3caaaa9 --- /dev/null +++ b/Engine.Systems/Time/IReadOnlyStopwatch.cs @@ -0,0 +1,18 @@ +namespace Syntriax.Engine.Systems.Time; + +public interface IReadOnlyStopwatch +{ + event StopwatchEventHandler? OnStarted; + event StopwatchDeltaEventHandler? OnDelta; + event StopwatchEventHandler? OnStopped; + + double Time { get; } + + TimerState State { get; } + + event StopwatchEventHandler? OnPaused; + event StopwatchEventHandler? OnResumed; + + delegate void StopwatchEventHandler(IReadOnlyStopwatch sender); + delegate void StopwatchDeltaEventHandler(IReadOnlyStopwatch sender, double delta); +} diff --git a/Engine.Systems/Time/IReadOnlyTicker.cs b/Engine.Systems/Time/IReadOnlyTicker.cs new file mode 100644 index 0000000..9ffec3b --- /dev/null +++ b/Engine.Systems/Time/IReadOnlyTicker.cs @@ -0,0 +1,11 @@ +namespace Syntriax.Engine.Systems.Time; + +public interface IReadOnlyTicker : IReadOnlyStopwatch +{ + event TickerTickEventHandler? OnTick; + + int TickCounter { get; } + double TickPeriod { get; set; } + + delegate void TickerTickEventHandler(IReadOnlyTicker sender); +} diff --git a/Engine.Systems/Time/IReadOnlyTimer.cs b/Engine.Systems/Time/IReadOnlyTimer.cs new file mode 100644 index 0000000..660ccb7 --- /dev/null +++ b/Engine.Systems/Time/IReadOnlyTimer.cs @@ -0,0 +1,22 @@ +namespace Syntriax.Engine.Systems.Time; + +public interface IReadOnlyTimer +{ + event TimerEventHandler? OnStarted; + event TimerDeltaEventHandler? OnDelta; + event TimerEventHandler? OnStopped; + + event TimerEventHandler? OnPaused; + event TimerEventHandler? OnResumed; + + double StartTime { get; } + double Remaining { get; } + + float Percentage { get; } + + TimerState State { get; } + + delegate void TimerEventHandler(IReadOnlyTimer sender); + delegate void TimerDeltaEventHandler(IReadOnlyTimer sender, double delta); + +} diff --git a/Engine.Systems/Time/IStopwatch.cs b/Engine.Systems/Time/IStopwatch.cs new file mode 100644 index 0000000..57c8227 --- /dev/null +++ b/Engine.Systems/Time/IStopwatch.cs @@ -0,0 +1,10 @@ +namespace Syntriax.Engine.Systems.Time; + +public interface IStopwatch : IReadOnlyStopwatch +{ + void StopwatchStart(); + void StopwatchStop(); + + void StopwatchPause(); + void StopwatchResume(); +} diff --git a/Engine.Systems/Time/ITicker.cs b/Engine.Systems/Time/ITicker.cs new file mode 100644 index 0000000..081a021 --- /dev/null +++ b/Engine.Systems/Time/ITicker.cs @@ -0,0 +1,11 @@ +namespace Syntriax.Engine.Systems.Time; + +public interface ITicker : IStopwatch +{ + event TickerTickEventHandler? OnTick; + + int TickCounter { get; } + double TickPeriod { get; set; } + + delegate void TickerTickEventHandler(ITicker sender); +} diff --git a/Engine.Systems/Time/ITimer.cs b/Engine.Systems/Time/ITimer.cs new file mode 100644 index 0000000..bc7b5ff --- /dev/null +++ b/Engine.Systems/Time/ITimer.cs @@ -0,0 +1,10 @@ +namespace Syntriax.Engine.Systems.Time; + +public interface ITimer : IReadOnlyTimer +{ + void TimerStart(double time); + void TimerStop(); + + void TimerPause(); + void TimerResume(); +} diff --git a/Engine.Systems/Time/StopwatchBehaviour.cs b/Engine.Systems/Time/StopwatchBehaviour.cs new file mode 100644 index 0000000..1d59e80 --- /dev/null +++ b/Engine.Systems/Time/StopwatchBehaviour.cs @@ -0,0 +1,99 @@ +using Syntriax.Engine.Core; +using Syntriax.Engine.Core.Abstract; + +namespace Syntriax.Engine.Systems.Time; + +public class StopwatchBehaviour : Behaviour, IStopwatch +{ + public event IReadOnlyStopwatch.StopwatchEventHandler? OnStarted = null; + public event IReadOnlyStopwatch.StopwatchDeltaEventHandler? OnDelta = null; + public event IReadOnlyStopwatch.StopwatchEventHandler? OnStopped = null; + public event IReadOnlyStopwatch.StopwatchEventHandler? OnPaused = null; + public event IReadOnlyStopwatch.StopwatchEventHandler? OnResumed = null; + + public double Time { get; protected set; } = 0f; + public TimerState State { get; protected set; } = TimerState.Idle; + + private bool shouldBeTicking = false; + private bool hasStartedTickingBefore = false; + + public virtual void StopwatchStart() + { + Time = 0f; + + shouldBeTicking = true; + hasStartedTickingBefore = false; + + if (IsActive) + StartStopwatch(); + } + + public virtual void StopwatchStop() + { + if (!shouldBeTicking) + return; + + shouldBeTicking = false; + + State = TimerState.Stopped; + OnStopped?.Invoke(this); + } + + protected override void OnUpdate() + { + if (State is not TimerState.Ticking) + return; + + double delta = GameManager.Time.DeltaSpan.TotalSeconds; + + Time += delta; + OnDelta?.Invoke(this, delta); + } + + protected override void OnEnteredHierarchy(IGameManager gameManager) + { + if (!shouldBeTicking || State is TimerState.Ticking) + return; + + if (hasStartedTickingBefore) + StopwatchResume(); + else + StartStopwatch(); + } + + protected override void OnExitedHierarchy(IGameManager gameManager) + { + if (!shouldBeTicking || State is not TimerState.Ticking) + return; + + StopwatchPause(); + } + + public virtual void StopwatchPause() + { + State = TimerState.Paused; + OnPaused?.Invoke(this); + } + + public virtual void StopwatchResume() + { + State = TimerState.Ticking; + OnResumed?.Invoke(this); + } + + private void StartStopwatch() + { + hasStartedTickingBefore = true; + + State = TimerState.Ticking; + OnStarted?.Invoke(this); + } + + protected override void OnFinalize() + { + Time = 0f; + State = TimerState.Idle; + shouldBeTicking = false; + hasStartedTickingBefore = false; + } +} diff --git a/Engine.Systems/Time/TickerBehaviour.cs b/Engine.Systems/Time/TickerBehaviour.cs new file mode 100644 index 0000000..d1ab2f0 --- /dev/null +++ b/Engine.Systems/Time/TickerBehaviour.cs @@ -0,0 +1,38 @@ +namespace Syntriax.Engine.Systems.Time; + +public class TickerBehaviour : StopwatchBehaviour, ITicker +{ + public event ITicker.TickerTickEventHandler? OnTick = null; + + public double TickPeriod { get; set; } = 1f; + public int TickCounter { get; private set; } = 0; + + private double nextTick = 0f; + + public override void StopwatchStart() + { + TickCounter = 0; + base.StopwatchStart(); + nextTick = Time + TickPeriod; + } + + protected override void OnUpdate() + { + base.OnUpdate(); + + if (Time < nextTick) + return; + + nextTick += TickPeriod; + TickCounter++; + OnTick?.Invoke(this); + } + + protected override void OnFinalize() + { + base.OnFinalize(); + + TickCounter = 0; + nextTick = 0f; + } +} diff --git a/Engine.Systems/Time/TimerBehaviour.cs b/Engine.Systems/Time/TimerBehaviour.cs new file mode 100644 index 0000000..afd4492 --- /dev/null +++ b/Engine.Systems/Time/TimerBehaviour.cs @@ -0,0 +1,118 @@ +using Syntriax.Engine.Core; +using Syntriax.Engine.Core.Abstract; + +namespace Syntriax.Engine.Systems.Time; + +public class TimerBehaviour : Behaviour, ITimer +{ + public event IReadOnlyTimer.TimerEventHandler? OnStarted = null; + public event IReadOnlyTimer.TimerDeltaEventHandler? OnDelta = null; + public event IReadOnlyTimer.TimerEventHandler? OnStopped = null; + public event IReadOnlyTimer.TimerEventHandler? OnPaused = null; + public event IReadOnlyTimer.TimerEventHandler? OnResumed = null; + + public TimerState State { get; protected set; } = TimerState.Idle; + public double StartTime { get; protected set; } = 0f; + public float Percentage => (float)(1f - (Remaining / StartTime)); + + private double _remaining = 0f; + public double Remaining + { + get => _remaining; + protected set + { + if (value < .0f) + value = .0f; + + _remaining = value; + } + } + + private bool shouldBeTicking = false; + private bool hasStartedTickingBefore = false; + + public virtual void TimerStart(double time) + { + StartTime = time; + Remaining = time; + + shouldBeTicking = true; + hasStartedTickingBefore = false; + + if (IsActive) + StartTimer(); + } + + public virtual void TimerStop() + { + if (!shouldBeTicking) + return; + + shouldBeTicking = false; + + State = TimerState.Stopped; + OnStopped?.Invoke(this); + } + + protected override void OnUpdate() + { + if (State is not TimerState.Ticking) + return; + + double delta = GameManager.Time.DeltaSpan.TotalSeconds; + + Remaining -= delta; + OnDelta?.Invoke(this, delta); + + if (Remaining <= .0f) + TimerStop(); + } + + protected override void OnEnteredHierarchy(IGameManager gameManager) + { + if (!shouldBeTicking || State is TimerState.Ticking) + return; + + if (hasStartedTickingBefore) + TimerResume(); + else + StartTimer(); + } + + protected override void OnExitedHierarchy(IGameManager gameManager) + { + if (!shouldBeTicking || State is not TimerState.Ticking) + return; + + TimerPause(); + } + + public virtual void TimerPause() + { + State = TimerState.Paused; + OnPaused?.Invoke(this); + } + + public virtual void TimerResume() + { + State = TimerState.Ticking; + OnResumed?.Invoke(this); + } + + private void StartTimer() + { + hasStartedTickingBefore = true; + + State = TimerState.Ticking; + OnStarted?.Invoke(this); + } + + protected override void OnFinalize() + { + StartTime = 0f; + Remaining = 0f; + State = TimerState.Idle; + shouldBeTicking = false; + hasStartedTickingBefore = false; + } +} diff --git a/Engine.Systems/Time/TimerState.cs b/Engine.Systems/Time/TimerState.cs new file mode 100644 index 0000000..4702f3d --- /dev/null +++ b/Engine.Systems/Time/TimerState.cs @@ -0,0 +1,9 @@ +namespace Syntriax.Engine.Systems.Time; + +public enum TimerState +{ + Idle, + Ticking, + Paused, + Stopped +}