Tanoda
EventPlayableMixerBehaviour.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
9using System;
10using System.Collections;
11using System.Collections.Generic;
12using UnityEngine;
13using UnityEngine.Playables;
14using UnityEngine.Timeline;
15
16namespace Leap.Unity.Recording {
17
18 public class EventPlayableMixerBehaviour : PlayableBehaviour {
19
21
22 private List<TimelineClip> _clips;
23 private TimelineClipComparerer _clipComparer;
24
25 private bool _firstFrameFired = false;
26
27 #region PlayableBehaviour Events
28
29 public override void OnGraphStart(Playable playable) {
30 base.OnGraphStart(playable);
31
32 if (_clips == null) {
33 _clips = Pool<List<TimelineClip>>.Spawn();
34 }
35 if (_clipComparer == null) {
36 _clipComparer = Pool<TimelineClipComparerer>.Spawn();
37 }
38
39 _firstFrameFired = false;
40 }
41
42 public override void OnGraphStop(Playable playable) {
43 base.OnGraphStop(playable);
44
45 if (_clips != null) {
46 Pool<List<TimelineClip>>.Recycle(_clips);
47 _clips = null;
48 }
49 if (_clipComparer != null) {
50 Pool<TimelineClipComparerer>.Recycle(_clipComparer);
51 _clipComparer = null;
52 }
53
54 _firstFrameFired = false;
55 }
56
57 public override void PrepareFrame(Playable playable, FrameData info) {
58 base.PrepareFrame(playable, info);
59
60 refreshEvents();
61 }
62
63 public override void ProcessFrame(Playable playable, FrameData info, object playerData) {
64 base.ProcessFrame(playable, info, playerData);
65
66 // Get time of the root playable in the playable graph, which is the timeline itself.
67 var curTime = playable.GetGraph().GetRootPlayable(0).GetTime();
68 var prevTime = playable.GetGraph().GetRootPlayable(0).GetPreviousTime();
69
70 // If there's an event at the beginning of the track, and we're currently at the
71 // beginning of the track, we should fire it now.
72 checkFireTimeZeroEvent(curTime);
73
74 if (!_firstFrameFired) {
75 _firstFrameFired = true;
76 }
77 else {
78 if (prevTime <= curTime) {
79 // Sweep forward from prevTime to curTime if moving forward.
80 sweepFireEvents(prevTime, curTime);
81 }
82 else {
83 // When scrubbing backwards, sweep from the beginning of the track, potentially
84 // firing events based on the eventScrubType.
85 sweepFireFromBeginning(prevTime, curTime);
86 }
87 }
88 }
89
90 #endregion
91
92 #region Event Management & Firing
93
101 private void refreshEvents() {
102 _clips.Clear();
103
104 TimelineClip prevClip = null;
105 bool notSorted = false;
106 foreach (var clip in eventTrack.GetClips()) {
107 _clips.Add(clip);
108
109 if (prevClip != null) {
110 if (clip.start < prevClip.start) {
111 notSorted = true;
112 }
113 }
114 prevClip = clip;
115 }
116
117 if (notSorted) {
118 _clips.Sort(_clipComparer);
119 }
120 }
121
131 private void checkFireTimeZeroEvent(double curLocalTime) {
132 if (curLocalTime == 0f && Application.isPlaying) {
133 if (_clips.Count > 0) {
134 var firstClip = _clips[0];
135 if (firstClip.start == 0) {
136 (firstClip.asset as EventClip).FireEvent();
137 }
138 }
139 }
140 }
141
149 private void sweepFireEvents(double prevTime, double curTime) {
150 if (prevTime > curTime) {
151 Debug.LogError("sweepFireEvents should only be called when the playhead is moving "
152 + "forward in time.");
153 }
154
155 // Find the index of the first event that might be fired.
156 int firstEventIdx = _clips.Count;
157 for (int i = 0; i < _clips.Count; i++) {
158 var clip = _clips[i];
159 if (prevTime < clip.start) {
160 firstEventIdx = i;
161 break;
162 }
163 }
164
165 // Sweep to curTime, firing events along the way.
166 for (int i = firstEventIdx; i < _clips.Count; i++) {
167 var clip = _clips[i];
168
169 if (curTime < clip.start) {
170 break;
171 }
172
173 // Fire!
174 var eventClip = clip.asset as EventClip;
175 if (eventClip != null) {
176 eventClip.FireEvent();
177 }
178 }
179 }
180
194 private void sweepFireFromBeginning(double prevTime, double curTime) {
195 if (prevTime < curTime) {
196 Debug.LogError("sweepFireFromBeginning should only be called if the playhead has "
197 + "scrubbed backwards in time.");
198 }
199
200 var indices = Pool<List<int>>.Spawn();
201 var prevIndices = Pool<List<Maybe<int>>>.Spawn();
202 try {
203 getUniqueMessageScrubbedEvents(prevTime, curTime, indices, prevIndices);
204
205 for (int i = 0; i < indices.Count; i++) {
206 TimelineClip clip = _clips[indices[i]];
207 TimelineClip prevClip = null;
208
209 var maybePrevClipIdx = prevIndices[i];
210 int prevClipIdx = -1;
211 if (maybePrevClipIdx.hasValue) {
212 prevClipIdx = maybePrevClipIdx.valueOrDefault;
213
214 prevClip = _clips[prevClipIdx];
215 }
216
217 if (prevClip != null) {
218 var eventClip = (clip.asset as EventClip);
219 var prevEventClip = (prevClip.asset as EventClip);
220
221 if (eventClip.eventScrubType == EventScrubType.StateChange) {
222 // Debug.Log("Firing clip index " + prevClipIdx + " because its successor "
223 // + "is a StateChange event");
224
225 prevEventClip.FireEvent();
226 }
227 }
228 }
229 }
230 finally {
231 indices.Clear();
232 Pool<List<int>>.Recycle(indices);
233 prevIndices.Clear();
234 Pool<List<Maybe<int>>>.Recycle(prevIndices);
235 }
236 }
237
247 private void getUniqueMessageScrubbedEvents(double time0, double time1,
248 List<int> indicesBuffer,
249 List<Maybe<int>> prevIdxBuffer) {
250 if (indicesBuffer.Count != 0) indicesBuffer.Clear();
251 if (prevIdxBuffer.Count != 0) prevIdxBuffer.Clear();
252
253 if (time1 < time0) {
254 Utils.Swap(ref time0, ref time1);
255 }
256
257 // mostRecentEvents will key an event message to the two latest events with that
258 // event message. (index 0 latest, index 1 just-prior, or -1 if no prior event
259 // exists).
260 var mostRecentEvents = Pool<Dictionary<string, Pair<int>>>.Spawn();
261 try {
262 for (int i = 0; i < _clips.Count; i++) {
263 var timelineClip = _clips[i];
264 if (timelineClip.start > time1) {
265 // We've hit a clip whose start time is after the latest scrub range time.
266 // Since clips are sorted by start time, we've done all the work we have to.
267 break;
268 }
269
270 var eventClip = timelineClip.asset as EventClip;
271
272 var eventMessage = eventClip.message;
273 Pair<int> indices;
274 if (!mostRecentEvents.TryGetValue(eventMessage, out indices)) {
275 mostRecentEvents[eventMessage] = new Pair<int>(i, -1);
276 }
277 else {
278 mostRecentEvents[eventMessage] = new Pair<int>(i, indices[0]);
279 }
280 }
281
282 // Convert mostRecentEvents to the output format.
283 foreach (var messageIndicesPair in mostRecentEvents) {
284 var indices = messageIndicesPair.Value;
285
286 // We only need to fire previous events of events we _actually_ scrubbed through.
287 if (!_clips[indices[0]].start.IsBetween(time0, time1)) continue;
288
289 indicesBuffer.Add(indices[0]);
290 prevIdxBuffer.Add((indices[1] == -1 ? Maybe<int>.None
291 : Maybe<int>.Some(indices[1])));
292 }
293 }
294 finally {
295 mostRecentEvents.Clear();
296 Pool<Dictionary<string, Pair<int>>>.Recycle(mostRecentEvents);
297 }
298 }
299
300 #endregion
301
302 #region Internal Utilities
303
304 public struct Pair<U> {
308 public U a;
309
313 public U b;
314
315 public U this[int idx] {
316 get {
317 checkIdx(idx);
318 if (idx == 0) return a;
319 else return b;
320 }
321 set {
322 checkIdx(idx);
323 if (idx == 0) a = value;
324 else b = value;
325 }
326 }
327
328 private void checkIdx(int idx) {
329 if (idx > 1 || idx < 0) throw new IndexOutOfRangeException();
330 }
331
332 public Pair(U a, U b) {
333 this.a = a;
334 this.b = b;
335 }
336 }
337
338 #endregion
339
340 }
341
342 public class TimelineClipComparerer : IComparer<TimelineClip> {
343 public int Compare(TimelineClip x, TimelineClip y) {
344 return x.start.CompareTo(y.start);
345 }
346 }
347
348}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
override void PrepareFrame(Playable playable, FrameData info)
override void ProcessFrame(Playable playable, FrameData info, object playerData)