using System; using System.Collections.Generic; using UnityEngine; namespace Platformer.Core { /// /// The Simulation class implements the discrete event simulator pattern. /// Events are pooled, with a default capacity of 4 instances. /// public static partial class Simulation { static HeapQueue eventQueue = new HeapQueue(); static Dictionary> eventPools = new Dictionary>(); /// /// Create a new event of type T and return it, but do not schedule it. /// /// /// static public T New() where T : Event, new() { Stack pool; if (!eventPools.TryGetValue(typeof(T), out pool)) { pool = new Stack(4); pool.Push(new T()); eventPools[typeof(T)] = pool; } if (pool.Count > 0) return (T)pool.Pop(); else return new T(); } /// /// Clear all pending events and reset the tick to 0. /// public static void Clear() { eventQueue.Clear(); } /// /// Schedule an event for a future tick, and return it. /// /// The event. /// Tick. /// The event type parameter. static public T Schedule(float tick = 0) where T : Event, new() { var ev = New(); ev.tick = Time.time + tick; eventQueue.Push(ev); return ev; } /// /// Reschedule an existing event for a future tick, and return it. /// /// The event. /// Tick. /// The event type parameter. static public T Reschedule(T ev, float tick) where T : Event, new() { ev.tick = Time.time + tick; eventQueue.Push(ev); return ev; } /// /// Return the simulation model instance for a class. /// /// static public T GetModel() where T : class, new() { return InstanceRegister.instance; } /// /// Set a simulation model instance for a class. /// /// static public void SetModel(T instance) where T : class, new() { InstanceRegister.instance = instance; } /// /// Destroy the simulation model instance for a class. /// /// static public void DestroyModel() where T : class, new() { InstanceRegister.instance = null; } /// /// Tick the simulation. Returns the count of remaining events. /// If remaining events is zero, the simulation is finished unless events are /// injected from an external system via a Schedule() call. /// /// static public int Tick() { var time = Time.time; var executedEventCount = 0; while (eventQueue.Count > 0 && eventQueue.Peek().tick <= time) { var ev = eventQueue.Pop(); var tick = ev.tick; ev.ExecuteEvent(); if (ev.tick > tick) { //event was rescheduled, so do not return it to the pool. } else { // Debug.Log($"{ev.tick} {ev.GetType().Name}"); ev.Cleanup(); try { eventPools[ev.GetType()].Push(ev); } catch (KeyNotFoundException) { //This really should never happen inside a production build. Debug.LogError($"No Pool for: {ev.GetType()}"); } } executedEventCount++; } return eventQueue.Count; } } }