using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Multiplayer.Samples.BossRoom.Shared.Infrastructure
{
///
/// Some objects might need to be on a slower update loop than the usual MonoBehaviour Update and without precise timing, e.g. to refresh data from services.
/// Some might also not want to be coupled to a Unity object at all but still need an update loop.
///
public class UpdateRunner : MonoBehaviour
{
class SubscriberData
{
public float Period;
public float NextCallTime;
}
readonly Queue m_PendingHandlers = new Queue();
readonly HashSet> m_Subscribers = new HashSet>();
readonly Dictionary, SubscriberData> m_SubscriberData = new Dictionary, SubscriberData>();
public void OnDestroy()
{
m_PendingHandlers.Clear();
m_Subscribers.Clear();
m_SubscriberData.Clear();
}
///
/// Subscribe in order to have onUpdate called approximately every period seconds (or every frame, if period <= 0).
/// Don't assume that onUpdate will be called in any particular order compared to other subscribers.
///
public void Subscribe(Action onUpdate, float updatePeriod)
{
if (onUpdate == null)
{
return;
}
if (onUpdate.Target == null) // Detect a local function that cannot be Unsubscribed since it could go out of scope.
{
Debug.LogError("Can't subscribe to a local function that can go out of scope and can't be unsubscribed from");
return;
}
if (onUpdate.Method.ToString().Contains("<")) // Detect
{
Debug.LogError("Can't subscribe with an anonymous function that cannot be Unsubscribed, by checking for a character that can't exist in a declared method name.");
return;
}
if (!m_Subscribers.Contains(onUpdate))
{
m_PendingHandlers.Enqueue(() =>
{
if (m_Subscribers.Add(onUpdate))
{
m_SubscriberData.Add(onUpdate, new SubscriberData() { Period = updatePeriod, NextCallTime = 0 });
}
});
}
}
///
/// Safe to call even if onUpdate was not previously Subscribed.
///
public void Unsubscribe(Action onUpdate)
{
m_PendingHandlers.Enqueue(() =>
{
m_Subscribers.Remove(onUpdate);
m_SubscriberData.Remove(onUpdate);
});
}
///
/// Each frame, advance all subscribers. Any that have hit their period should then act, though if they take too long they could be removed.
///
void Update()
{
while (m_PendingHandlers.Count > 0)
{
m_PendingHandlers.Dequeue()?.Invoke();
}
foreach (var subscriber in m_Subscribers)
{
var subscriberData = m_SubscriberData[subscriber];
if (Time.time >= subscriberData.NextCallTime)
{
subscriber.Invoke(Time.deltaTime);
subscriberData.NextCallTime = Time.time + subscriberData.Period;
}
}
}
}
}