using System; using System.Collections.Generic; using System.Linq; using UnityEngine; namespace Cryville.Crtr.Event { public class EventBatcher : StateBase { public EventBus Bus { get; private set; } readonly Chart chart; ContainerState rootState; readonly Dictionary containerMap = new(); readonly Dictionary stateMap = new(); readonly Dictionary map = new(); readonly Dictionary> coeventMap = new(); readonly HashSet coevents = new(); readonly List stampedEvents = new(); readonly List batches = new(); double beat; float tempo; public EventBatcher(Chart c) : base(new List()) { chart = c; beat = chart.BeatPosition; tempo = (float)c.sigs[0].tempo; Events.Add(c); Events.Add(c.ReleaseEvent); AddEventContainer(c); Events.Sort((a, b) => a.BeatPosition.CompareTo(b.BeatPosition)); } void AddEventContainer(EventContainer c, ContainerState parent = null) { var cs = new ContainerState(c, parent); stateMap.Add(c, cs); if (parent == null) { cs.Depth = 0; rootState = cs; } else { cs.Depth = (ushort)(parent.Depth + 1); } foreach (var ev in c.Events) { if (ev.time == null) { ev.time = c.time; if (ev is EventContainer) ev.endtime = c.endtime; if ((ev.CoeventMode & CoeventMode.Coevent) != 0) coevents.Add(ev); } if (ev.IsLong) { Events.Add(ev.ReleaseEvent); containerMap.Add(ev.ReleaseEvent, cs); } Events.Add(ev); containerMap.Add(ev, cs); if (ev is EventContainer container) AddEventContainer(container, cs); } } public override void ForwardOnceToTime(double toTime) { double toBeat = Math.Round(beat + (toTime - Time) * tempo / 60f, 6); if (EventId >= Events.Count) goto return_ahead; double ebeat = Events[EventId].BeatPosition; double etime = Math.Round((ebeat - beat) / tempo * 60f + Time, 6); if (etime > toTime) goto return_ahead; var batch = GetEventBatch(); Time = etime; beat = ebeat; foreach (var ev in batch) { EventContainer con = null; if (containerMap.ContainsKey(ev)) con = containerMap[ev].Container; var sev = new StampedEvent() { Time = etime, Unstamped = ev, Container = con }; if (ev is EventContainer) { var tev = (EventContainer)ev; stateMap[tev].StampedContainer = sev; stampedEvents.Add(new StampedEvent.ClipBehind { Container = con, Origin = sev, Time = etime + tev.Clip.Behind, }); if (!ev.IsLong) { stampedEvents.Add(new StampedEvent.ClipAhead { Container = con, Origin = sev, Time = etime + tev.Clip.Ahead, }); } } if (ev is ReleaseEvent) { var tev = (ReleaseEvent)ev; var oev = tev.Original; var pev = map[oev]; pev.ReleaseEvent = sev; sev.Origin = pev; if (oev is EventContainer container) { stampedEvents.Add(new StampedEvent.ClipAhead { Container = con, Origin = pev, Time = etime + container.Clip.Ahead, }); } } if (con != null && coevents.Contains(ev)) { if (!coeventMap.TryGetValue(con, out List cevs)) { coeventMap.Add(con, cevs = new List()); } cevs.Add(sev); if ((ev.CoeventMode & CoeventMode.Standalone) != 0) { stampedEvents.Add(sev); } } else stampedEvents.Add(sev); map.Add(ev, sev); if (ev is Chart.Signature) { var tev = (Chart.Signature)ev; if (tev.tempo != null) tempo = (float)tev.tempo; } EventId++; } return; return_ahead: Time = toTime; beat = toBeat; } IOrderedEnumerable GetEventBatch() { float cbeat = Events[EventId].BeatPosition; int b = EventId; while (Mathf.Approximately(Events[b].BeatPosition, cbeat)) { b--; if (b == -1) break; } int a = EventId; while (Mathf.Approximately(Events[a].BeatPosition, cbeat)) { a++; if (a == Events.Count) break; } return from ev in Events.GetRange(b + 1, a - b - 1) orderby ev.Priority select ev; } public EventBus Batch() { stampedEvents.Sort(CompareStampedEvents); var cb = new EventBatch(stampedEvents[0].Time); foreach (var ev in stampedEvents) { if (ev.Time != cb.Time) { batches.Add(cb); cb = new EventBatch(ev.Time); } cb.Enqueue(ev); BatchCoevents(ev); } batches.Add(cb); Bus = new EventBus(rootState, batches); return Bus; } void BatchCoevents(StampedEvent ev, List ocevs = null) { if (ev.Unstamped is not EventContainer container) return; if (coeventMap.TryGetValue(container, out List cevs)) { var rootFlag = ocevs == null; if (rootFlag) ev.Coevents = ocevs = new List(); foreach (var cev in cevs) { ocevs.Add(cev); BatchCoevents(cev, ocevs); } if (rootFlag) ocevs.Sort(CompareStampedEvents); else ocevs.Add(ev); } } int CompareStampedEvents(StampedEvent a, StampedEvent b) { int u = a.CompareTo(b); if (u != 0) return u; if (a.Unstamped != null && b.Unstamped != null && containerMap.ContainsKey(a.Unstamped) && containerMap.ContainsKey(b.Unstamped)) { u = containerMap[a.Unstamped].Depth.CompareTo(containerMap[b.Unstamped].Depth); if (u != 0) return u; } return 0; } } }