using System;
using UnityEngine;
namespace Unity.VisualScripting
{
///
/// Runs a timer and outputs elapsed and remaining measurements.
///
[UnitCategory("Time")]
[UnitOrder(7)]
public sealed class Timer : Unit, IGraphElementWithData, IGraphEventListener
{
public sealed class Data : IGraphElementData
{
public float elapsed;
public float duration;
public bool active;
public bool paused;
public bool unscaled;
public Delegate update;
public bool isListening;
}
///
/// The moment at which to start the timer.
/// If the timer is already started, this will reset it.
/// If the timer is paused, this will resume it.
///
[DoNotSerialize]
public ControlInput start { get; private set; }
///
/// Trigger to pause the timer.
///
[DoNotSerialize]
public ControlInput pause { get; private set; }
///
/// Trigger to resume the timer.
///
[DoNotSerialize]
public ControlInput resume { get; private set; }
///
/// Trigger to toggle the timer.
/// If it is idle, it will start.
/// If it is active, it will pause.
/// If it is paused, it will resume.
///
[DoNotSerialize]
public ControlInput toggle { get; private set; }
///
/// The total duration of the timer.
///
[DoNotSerialize]
public ValueInput duration { get; private set; }
///
/// Whether to ignore the time scale.
///
[DoNotSerialize]
[PortLabel("Unscaled")]
public ValueInput unscaledTime { get; private set; }
///
/// Called when the timer is started.co
///
[DoNotSerialize]
public ControlOutput started { get; private set; }
///
/// Called each frame while the timer is active.
///
[DoNotSerialize]
public ControlOutput tick { get; private set; }
///
/// Called when the timer completes.
///
[DoNotSerialize]
public ControlOutput completed { get; private set; }
///
/// The number of seconds elapsed since the timer started.
///
[DoNotSerialize]
[PortLabel("Elapsed")]
public ValueOutput elapsedSeconds { get; private set; }
///
/// The proportion of the duration that has elapsed (0-1).
///
[DoNotSerialize]
[PortLabel("Elapsed %")]
public ValueOutput elapsedRatio { get; private set; }
///
/// The number of seconds remaining until the timer is elapsed.
///
[DoNotSerialize]
[PortLabel("Remaining")]
public ValueOutput remainingSeconds { get; private set; }
///
/// The proportion of the duration remaining until the timer is elapsed (0-1).
///
[DoNotSerialize]
[PortLabel("Remaining %")]
public ValueOutput remainingRatio { get; private set; }
protected override void Definition()
{
isControlRoot = true;
start = ControlInput(nameof(start), Start);
pause = ControlInput(nameof(pause), Pause);
resume = ControlInput(nameof(resume), Resume);
toggle = ControlInput(nameof(toggle), Toggle);
duration = ValueInput(nameof(duration), 1f);
unscaledTime = ValueInput(nameof(unscaledTime), false);
started = ControlOutput(nameof(started));
tick = ControlOutput(nameof(tick));
completed = ControlOutput(nameof(completed));
elapsedSeconds = ValueOutput(nameof(elapsedSeconds));
elapsedRatio = ValueOutput(nameof(elapsedRatio));
remainingSeconds = ValueOutput(nameof(remainingSeconds));
remainingRatio = ValueOutput(nameof(remainingRatio));
}
public IGraphElementData CreateData()
{
return new Data();
}
public void StartListening(GraphStack stack)
{
var data = stack.GetElementData(this);
if (data.isListening)
{
return;
}
var reference = stack.ToReference();
var hook = new EventHook(EventHooks.Update, stack.machine);
Action update = args => TriggerUpdate(reference);
EventBus.Register(hook, update);
data.update = update;
data.isListening = true;
}
public void StopListening(GraphStack stack)
{
var data = stack.GetElementData(this);
if (!data.isListening)
{
return;
}
var hook = new EventHook(EventHooks.Update, stack.machine);
EventBus.Unregister(hook, data.update);
stack.ClearReference();
data.update = null;
data.isListening = false;
}
public bool IsListening(GraphPointer pointer)
{
return pointer.GetElementData(this).isListening;
}
private void TriggerUpdate(GraphReference reference)
{
using (var flow = Flow.New(reference))
{
Update(flow);
}
}
private ControlOutput Start(Flow flow)
{
var data = flow.stack.GetElementData(this);
data.elapsed = 0;
data.duration = flow.GetValue(duration);
data.active = true;
data.paused = false;
data.unscaled = flow.GetValue(unscaledTime);
AssignMetrics(flow, data);
return started;
}
private ControlOutput Pause(Flow flow)
{
var data = flow.stack.GetElementData(this);
data.paused = true;
return null;
}
private ControlOutput Resume(Flow flow)
{
var data = flow.stack.GetElementData(this);
data.paused = false;
return null;
}
private ControlOutput Toggle(Flow flow)
{
var data = flow.stack.GetElementData(this);
if (!data.active)
{
return Start(flow);
}
else
{
data.paused = !data.paused;
return null;
}
}
private void AssignMetrics(Flow flow, Data data)
{
flow.SetValue(elapsedSeconds, data.elapsed);
flow.SetValue(elapsedRatio, Mathf.Clamp01(data.elapsed / data.duration));
flow.SetValue(remainingSeconds, Mathf.Max(0, data.duration - data.elapsed));
flow.SetValue(remainingRatio, Mathf.Clamp01((data.duration - data.elapsed) / data.duration));
}
public void Update(Flow flow)
{
var data = flow.stack.GetElementData(this);
if (!data.active || data.paused)
{
return;
}
data.elapsed += data.unscaled ? Time.unscaledDeltaTime : Time.deltaTime;
data.elapsed = Mathf.Min(data.elapsed, data.duration);
AssignMetrics(flow, data);
var stack = flow.PreserveStack();
flow.Invoke(tick);
if (data.elapsed >= data.duration)
{
data.active = false;
flow.RestoreStack(stack);
flow.Invoke(completed);
}
flow.DisposePreservedStack(stack);
}
}
}