using System;
using UnityEngine;
namespace Unity.VisualScripting
{
///
/// Runs a cooldown timer to throttle flow and outputs remaining measurements.
///
[UnitCategory("Time")]
[TypeIcon(typeof(Timer))]
[UnitOrder(8)]
public sealed class Cooldown : Unit, IGraphElementWithData, IGraphEventListener
{
public sealed class Data : IGraphElementData
{
public float remaining;
public float duration;
public bool unscaled;
public bool isReady => remaining <= 0;
public Delegate update;
public bool isListening;
}
///
/// The moment at which to try using the cooldown.
///
[DoNotSerialize]
[PortLabelHidden]
public ControlInput enter { get; private set; }
///
/// Trigger to force reset the cooldown.
///
[DoNotSerialize]
public ControlInput reset { get; private set; }
///
/// The total duration of the cooldown.
///
[DoNotSerialize]
public ValueInput duration { get; private set; }
///
/// Whether to ignore the time scale.
///
[DoNotSerialize]
[PortLabel("Unscaled")]
public ValueInput unscaledTime { get; private set; }
///
/// Called upon entry when the cooldown is ready.
///
[DoNotSerialize]
[PortLabel("Ready")]
public ControlOutput exitReady { get; private set; }
///
/// Called upon entry when the cooldown is not yet ready.
///
[DoNotSerialize]
[PortLabel("Not Ready")]
public ControlOutput exitNotReady { get; private set; }
///
/// Called each frame while the cooldown timer is active.
///
[DoNotSerialize]
public ControlOutput tick { get; private set; }
///
/// Called when the cooldown timer reaches zero.
///
[DoNotSerialize]
[PortLabel("Completed")]
public ControlOutput becameReady { get; private set; }
///
/// The number of seconds remaining until the cooldown is ready.
///
[DoNotSerialize]
[PortLabel("Remaining")]
public ValueOutput remainingSeconds { get; private set; }
///
/// The proportion of the duration remaining until the cooldown is ready (0-1).
///
[DoNotSerialize]
[PortLabel("Remaining %")]
public ValueOutput remainingRatio { get; private set; }
protected override void Definition()
{
enter = ControlInput(nameof(enter), Enter);
reset = ControlInput(nameof(reset), Reset);
duration = ValueInput(nameof(duration), 1f);
unscaledTime = ValueInput(nameof(unscaledTime), false);
exitReady = ControlOutput(nameof(exitReady));
exitNotReady = ControlOutput(nameof(exitNotReady));
tick = ControlOutput(nameof(tick));
becameReady = ControlOutput(nameof(becameReady));
remainingSeconds = ValueOutput(nameof(remainingSeconds));
remainingRatio = ValueOutput(nameof(remainingRatio));
Requirement(duration, enter);
Requirement(unscaledTime, enter);
Succession(enter, exitReady);
Succession(enter, exitNotReady);
Succession(enter, tick);
Succession(enter, becameReady);
Assignment(enter, remainingSeconds);
Assignment(enter, 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);
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 Enter(Flow flow)
{
var data = flow.stack.GetElementData(this);
if (data.isReady)
{
return Reset(flow);
}
else
{
return exitNotReady;
}
}
private ControlOutput Reset(Flow flow)
{
var data = flow.stack.GetElementData(this);
data.duration = flow.GetValue(duration);
data.remaining = data.duration;
data.unscaled = flow.GetValue(unscaledTime);
return exitReady;
}
private void AssignMetrics(Flow flow, Data data)
{
flow.SetValue(remainingSeconds, data.remaining);
flow.SetValue(remainingRatio, Mathf.Clamp01(data.remaining / data.duration));
}
public void Update(Flow flow)
{
var data = flow.stack.GetElementData(this);
if (data.isReady)
{
return;
}
data.remaining -= data.unscaled ? Time.unscaledDeltaTime : Time.deltaTime;
data.remaining = Mathf.Max(0f, data.remaining);
AssignMetrics(flow, data);
var stack = flow.PreserveStack();
flow.Invoke(tick);
if (data.isReady)
{
flow.RestoreStack(stack);
flow.Invoke(becameReady);
}
flow.DisposePreservedStack(stack);
}
}
}