Visão Geral da Arquitetura
O sistema de temporizadores desenvolvido para o framework ET segue uma arquitetura em camadas composta por três elementos principais: a entidade temporizadora, o componente gerenciador e a fábrica de criação. Esta separação de responsabilidades permite controle granular sobre execuções agendadas com suporte a pausas, múltiplas repetições e consulta de estado.
Entidade Temporizadora
A classe ChronosEntity representa uma unidade de temporização independente, encapsulando toda a lógica de ciclo e estado:
using System;
namespace ETModel
{
[ObjectSystem]
public class ChronosAwakeSystem : AwakeSystem<ChronosEntity, float, uint, Action>
{
public override void Awake(ChronosEntity self, float tickSpan, uint repetitions, Action callback)
{
self.Initialize(tickSpan, repetitions, callback);
}
}
public sealed class ChronosEntity : Entity
{
public const uint PERPETUAL = uint.MaxValue;
private float _tickSpan = 0f;
private uint _maxRepetitions = 1;
private uint _finishedRepetitions = 0;
private Action _userCallback = null;
private Action _wrappedCallback = null;
private bool _isFrozen = false;
private float _accumulatedSpan = 0f;
private float _currentCycleProgress = 0f;
private long _lastSyncTicks = 0;
public void Initialize(float tickSpan, uint repetitions, Action callback)
{
_tickSpan = Math.Max(tickSpan, 0f);
_maxRepetitions = Math.Max(repetitions, 1u);
_userCallback = callback;
_lastSyncTicks = DateTime.UtcNow.Ticks;
}
public override void Dispose()
{
if (this.IsDisposed) return;
_userCallback = null;
_wrappedCallback = null;
base.Dispose();
}
internal void SynchronizeCallback()
{
if (_maxRepetitions == PERPETUAL)
_wrappedCallback = () => _userCallback?.Invoke();
else
_wrappedCallback = _userCallback;
}
internal void Tick()
{
if (_isFrozen) return;
if (_wrappedCallback == null || _tickSpan <= 0f) return;
if (_finishedRepetitions >= _maxRepetitions && _maxRepetitions != PERPETUAL)
{
_accumulatedSpan = _tickSpan * _maxRepetitions;
_currentCycleProgress = _tickSpan;
return;
}
long nowTicks = DateTime.UtcNow.Ticks;
float delta = (nowTicks - _lastSyncTicks) / 10_000_000f;
_lastSyncTicks = nowTicks;
_accumulatedSpan += delta;
_currentCycleProgress = _accumulatedSpan - (_finishedRepetitions * _tickSpan);
while (_currentCycleProgress >= _tickSpan &&
(_finishedRepetitions < _maxRepetitions || _maxRepetitions == PERPETUAL))
{
_currentCycleProgress -= _tickSpan;
_finishedRepetitions++;
_wrappedCallback?.Invoke();
}
}
public float TickSpan() => _tickSpan;
public uint MaxRepetitions() => _maxRepetitions;
public uint CompletedRepetitions() => _finishedRepetitions;
public uint PendingRepetitions() => _maxRepetitions - _finishedRepetitions;
public float TotalDuration() =>
(_maxRepetitions == PERPETUAL) ? float.PositiveInfinity : _maxRepetitions * _tickSpan;
public float ElapsedDuration() => _accumulatedSpan;
public float RemainingDuration() =>
(_maxRepetitions == PERPETUAL) ? float.PositiveInfinity :
Math.Max(TotalDuration() - _accumulatedSpan, 0f);
public float CycleProgress() => _currentCycleProgress;
public float CycleRemaining() => Math.Max(_tickSpan - _currentCycleProgress, 0f);
public bool IsExhausted() => _wrappedCallback == null || RemainingDuration() <= 0f;
public bool IsFrozen() => _isFrozen;
public void SetFrozen(bool state) => _isFrozen = state;
public static bool operator >(ChronosEntity a, ChronosEntity b) =>
a?._tickSpan < b?._tickSpan;
public static bool operator <(ChronosEntity a, ChronosEntity b) =>
a?._tickSpan > b?._tickSpan;
}
}
Componente Gerenciador
O ChronosCoordinator atua como singleton responsável pela atualização centralizada de todas as entidades temporizadoras ativas:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ETModel
{
[ObjectSystem]
public class CoordinatorAwakeSystem : AwakeSystem<ChronosCoordinator>
{
public override void Awake(ChronosCoordinator self) => self.Bootstrap();
}
[ObjectSystem]
public class CoordinatorUpdateSystem : UpdateSystem<ChronosCoordinator>
{
public override void Update(ChronosCoordinator self) => self.ProcessFrame();
}
public class ChronosCoordinator : Component
{
public static ChronosCoordinator ActiveInstance { get; private set; }
private readonly List<ChronosEntity> _activeTimers = new List<ChronosEntity>();
private bool _globalFreeze = false;
public void Bootstrap()
{
ActiveInstance = this;
}
public void ProcessFrame()
{
if (_globalFreeze) return;
foreach (var timer in _activeTimers.ToArray())
{
timer.Tick();
if (timer.IsExhausted() || timer.IsDisposed)
{
_activeTimers.Remove(timer);
}
}
}
public void Register(ChronosEntity timer)
{
if (timer != null && !timer.IsDisposed)
_activeTimers.Add(timer);
}
public ChronosEntity FindByCallback(Action callback)
{
return _activeTimers.FirstOrDefault(t =>
ReferenceEquals(t.GetType().GetField("_userCallback")?.GetValue(t), callback));
}
public void Unregister(ChronosEntity timer)
{
_activeTimers.Remove(timer);
timer?.Dispose();
}
public void Unregister(Action callback)
{
var target = FindByCallback(callback);
if (target != null) Unregister(target);
}
public int ActiveCount => _activeTimers.Count;
public override void Dispose()
{
if (IsDisposed) return;
base.Dispose();
foreach (var timer in _activeTimers)
timer.Dispose();
_activeTimers.Clear();
ActiveInstance = null;
}
// Métodos de consulta por callback
public float? QueryTickSpan(Action cb) => FindByCallback(cb)?.TickSpan();
public uint? QueryRemainingRepetitions(Action cb) => FindByCallback(cb)?.PendingRepetitions();
public float? QueryRemainingTime(Action cb) => FindByCallback(cb)?.RemainingDuration();
public bool QueryIsFrozen(Action cb) => FindByCallback(cb)?.IsFrozen() ?? false;
public void SetFreezeState(Action cb, bool freeze)
{
var timer = FindByCallback(cb);
if (timer != null) timer.SetFrozen(freeze);
}
}
}
Fábrica de Instanciação
A classe estática ChronosBuilder simplifica a criação e registro automáticco de temporizadores:
using System;
namespace ETModel
{
public static class ChronosBuilder
{
public static ChronosEntity Schedule(float interval, uint repetitions, Action callback)
{
var coordinator = Game.Scene.GetComponent<ChronosCoordinator>();
if (coordinator == null) return null;
var timer = ComponentFactory.Create<ChronosEntity, float, uint, Action>(
interval, repetitions, callback);
timer.SynchronizeCallback();
coordinator.Register(timer);
return timer;
}
public static ChronosEntity ScheduleOnce(float delay, Action callback) =>
Schedule(delay, 1, callback);
public static ChronosEntity SchedulePerpetual(float interval, Action callback) =>
Schedule(interval, ChronosEntity.PERPETUAL, callback);
}
}
Exemplos de Utilização
// Temporizador único após 2 segundos
ChronosBuilder.ScheduleOnce(2.0f, () => {
Log.Info("Execução única completada");
});
// Temporizador repetitivo 5 vezes a cada 0.5 segundos
ChronosBuilder.Schedule(0.5f, 5, () => {
Log.Info("Ciclo executado");
});
// Temporizador infinito com controle de pausa
var perpetual = ChronosBuilder.SchedulePerpetual(1.0f, () => {
Log.Info("Heartbeat");
});
// Pausar temporizador existente
ChronosCoordinator.ActiveInstance?.SetFreezeState(perpetual.GetType()
.GetField("_userCallback")?.GetValue(perpetual) as Action, true);