You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
3.7 KiB
C#

1 week ago
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.BossRoom.Infrastructure
{
/// <summary>
/// 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.
/// </summary>
public class UpdateRunner : MonoBehaviour
{
class SubscriberData
{
public float Period;
public float NextCallTime;
public float LastCallTime;
}
readonly Queue<Action> m_PendingHandlers = new Queue<Action>();
readonly HashSet<Action<float>> m_Subscribers = new HashSet<Action<float>>();
readonly Dictionary<Action<float>, SubscriberData> m_SubscriberData = new Dictionary<Action<float>, SubscriberData>();
public void OnDestroy()
{
m_PendingHandlers.Clear();
m_Subscribers.Clear();
m_SubscriberData.Clear();
}
/// <summary>
/// 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.
/// </summary>
public void Subscribe(Action<float> 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, LastCallTime = Time.time });
}
});
}
}
/// <summary>
/// Safe to call even if onUpdate was not previously Subscribed.
/// </summary>
public void Unsubscribe(Action<float> onUpdate)
{
m_PendingHandlers.Enqueue(() =>
{
m_Subscribers.Remove(onUpdate);
m_SubscriberData.Remove(onUpdate);
});
}
/// <summary>
/// Each frame, advance all subscribers. Any that have hit their period should then act, though if they take too long they could be removed.
/// </summary>
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.time - subscriberData.LastCallTime);
subscriberData.LastCallTime = Time.time;
subscriberData.NextCallTime = Time.time + subscriberData.Period;
}
}
}
}
}