summaryrefslogtreecommitdiff
path: root/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs
diff options
context:
space:
mode:
authorAndrew Lee <alee14498@protonmail.com>2020-04-19 17:19:32 -0400
committerAndrew Lee <alee14498@protonmail.com>2020-04-19 17:19:32 -0400
commitc55fba8ab2a1c9d3df65eda4a5a1e957f4aa1f78 (patch)
treeee4d51c7c1d633e11f46453ef1edd3c77c4ef9f7 /Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs
downloadProject-Sandbox-c55fba8ab2a1c9d3df65eda4a5a1e957f4aa1f78.tar.gz
Project-Sandbox-c55fba8ab2a1c9d3df65eda4a5a1e957f4aa1f78.tar.bz2
Project-Sandbox-c55fba8ab2a1c9d3df65eda4a5a1e957f4aa1f78.zip
Inital commit
Diffstat (limited to 'Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs')
-rw-r--r--Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs310
1 files changed, 310 insertions, 0 deletions
diff --git a/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs b/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs
new file mode 100644
index 0000000..f028a7a
--- /dev/null
+++ b/Library/PackageCache/com.unity.timeline@1.2.13/Runtime/TimelinePlayable.cs
@@ -0,0 +1,310 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.Animations;
+using UnityEngine.Audio;
+using UnityEngine.Playables;
+
+namespace UnityEngine.Timeline
+{
+ // Generic evaluation callback called after all the clips have been processed
+ internal interface ITimelineEvaluateCallback
+ {
+ void Evaluate();
+ }
+
+
+#if UNITY_EDITOR
+ /// <summary>
+ /// This Rebalancer class ensures that the interval tree structures stays balance regardless of whether the intervals inside change.
+ /// </summary>
+ class IntervalTreeRebalancer
+ {
+ private IntervalTree<RuntimeElement> m_Tree;
+ public IntervalTreeRebalancer(IntervalTree<RuntimeElement> tree)
+ {
+ m_Tree = tree;
+ }
+
+ public bool Rebalance()
+ {
+ m_Tree.UpdateIntervals();
+ return m_Tree.dirty;
+ }
+ }
+#endif
+
+ // The TimelinePlayable Playable
+ // This is the actual runtime playable that gets evaluated as part of a playable graph.
+ // It "compiles" a list of tracks into an IntervalTree of Runtime clips.
+ // At each frame, it advances time, then fetches the "intersection: of various time interval
+ // using the interval tree.
+ // Finally, on each intersecting clip, it will calculate each clips' local time, as well as
+ // blend weight and set them accordingly
+
+
+ /// <summary>
+ /// The root Playable generated by timeline.
+ /// </summary>
+ public class TimelinePlayable : PlayableBehaviour
+ {
+ private IntervalTree<RuntimeElement> m_IntervalTree = new IntervalTree<RuntimeElement>();
+ private List<RuntimeElement> m_ActiveClips = new List<RuntimeElement>();
+ private List<RuntimeElement> m_CurrentListOfActiveClips;
+ private int m_ActiveBit = 0;
+
+ private List<ITimelineEvaluateCallback> m_EvaluateCallbacks = new List<ITimelineEvaluateCallback>();
+
+ private Dictionary<TrackAsset, Playable> m_PlayableCache = new Dictionary<TrackAsset, Playable>();
+
+ internal static bool muteAudioScrubbing = true;
+
+#if UNITY_EDITOR
+ private IntervalTreeRebalancer m_Rebalancer;
+#endif
+ /// <summary>
+ /// Creates an instance of a Timeline
+ /// </summary>
+ /// <param name="graph">The playable graph to inject the timeline.</param>
+ /// <param name="tracks">The list of tracks to compile</param>
+ /// <param name="go">The GameObject that initiated the compilation</param>
+ /// <param name="autoRebalance">In the editor, whether the graph should account for the possibility of changing clip times</param>
+ /// <param name="createOutputs">Whether to create PlayableOutputs in the graph</param>
+ /// <returns>A subgraph with the playable containing a TimelinePlayable behaviour as the root</returns>
+ public static ScriptPlayable<TimelinePlayable> Create(PlayableGraph graph, IEnumerable<TrackAsset> tracks, GameObject go, bool autoRebalance, bool createOutputs)
+ {
+ if (tracks == null)
+ throw new ArgumentNullException("Tracks list is null", "tracks");
+
+ if (go == null)
+ throw new ArgumentNullException("GameObject parameter is null", "go");
+
+ var playable = ScriptPlayable<TimelinePlayable>.Create(graph);
+ playable.SetTraversalMode(PlayableTraversalMode.Passthrough);
+ var sequence = playable.GetBehaviour();
+ sequence.Compile(graph, playable, tracks, go, autoRebalance, createOutputs);
+ return playable;
+ }
+
+ /// <summary>
+ /// Compiles the subgraph of this timeline
+ /// </summary>
+ /// <param name="graph">The playable graph to inject the timeline.</param>
+ /// <param name="timelinePlayable"></param>
+ /// <param name="tracks">The list of tracks to compile</param>
+ /// <param name="go">The GameObject that initiated the compilation</param>
+ /// <param name="autoRebalance">In the editor, whether the graph should account for the possibility of changing clip times</param>
+ /// <param name="createOutputs">Whether to create PlayableOutputs in the graph</param>
+ public void Compile(PlayableGraph graph, Playable timelinePlayable, IEnumerable<TrackAsset> tracks, GameObject go, bool autoRebalance, bool createOutputs)
+ {
+ if (tracks == null)
+ throw new ArgumentNullException("Tracks list is null", "tracks");
+
+ if (go == null)
+ throw new ArgumentNullException("GameObject parameter is null", "go");
+
+ var outputTrackList = new List<TrackAsset>(tracks);
+ var maximumNumberOfIntersections = outputTrackList.Count * 2 + outputTrackList.Count; // worse case: 2 overlapping clips per track + each track
+ m_CurrentListOfActiveClips = new List<RuntimeElement>(maximumNumberOfIntersections);
+ m_ActiveClips = new List<RuntimeElement>(maximumNumberOfIntersections);
+
+ m_EvaluateCallbacks.Clear();
+ m_PlayableCache.Clear();
+
+ CompileTrackList(graph, timelinePlayable, outputTrackList, go, createOutputs);
+
+#if UNITY_EDITOR
+ if (autoRebalance)
+ {
+ m_Rebalancer = new IntervalTreeRebalancer(m_IntervalTree);
+ }
+#endif
+ }
+
+ private void CompileTrackList(PlayableGraph graph, Playable timelinePlayable, IEnumerable<TrackAsset> tracks, GameObject go, bool createOutputs)
+ {
+ foreach (var track in tracks)
+ {
+ if (!track.IsCompilable())
+ continue;
+
+ if (!m_PlayableCache.ContainsKey(track))
+ {
+ track.SortClips();
+ CreateTrackPlayable(graph, timelinePlayable, track, go, createOutputs);
+ }
+ }
+ }
+
+ void CreateTrackOutput(PlayableGraph graph, TrackAsset track, GameObject go, Playable playable, int port)
+ {
+ if (track.isSubTrack)
+ return;
+
+ var bindings = track.outputs;
+ foreach (var binding in bindings)
+ {
+ var playableOutput = binding.CreateOutput(graph);
+ playableOutput.SetReferenceObject(binding.sourceObject);
+ playableOutput.SetSourcePlayable(playable, port);
+ playableOutput.SetWeight(1.0f);
+
+ // only apply this on our animation track
+ if (track as AnimationTrack != null)
+ {
+ EvaluateWeightsForAnimationPlayableOutput(track, (AnimationPlayableOutput)playableOutput);
+#if UNITY_EDITOR
+ if (!Application.isPlaying)
+ EvaluateAnimationPreviewUpdateCallback(track, (AnimationPlayableOutput)playableOutput);
+#endif
+ }
+ if (playableOutput.IsPlayableOutputOfType<AudioPlayableOutput>())
+ ((AudioPlayableOutput)playableOutput).SetEvaluateOnSeek(!muteAudioScrubbing);
+
+ // If the track is the timeline marker track, assume binding is the PlayableDirector
+ if (track.timelineAsset.markerTrack == track)
+ {
+ var director = go.GetComponent<PlayableDirector>();
+ playableOutput.SetUserData(director);
+ foreach (var c in go.GetComponents<INotificationReceiver>())
+ {
+ playableOutput.AddNotificationReceiver(c);
+ }
+ }
+ }
+ }
+
+ void EvaluateWeightsForAnimationPlayableOutput(TrackAsset track, AnimationPlayableOutput animOutput)
+ {
+ m_EvaluateCallbacks.Add(new AnimationOutputWeightProcessor(animOutput));
+ }
+
+ void EvaluateAnimationPreviewUpdateCallback(TrackAsset track, AnimationPlayableOutput animOutput)
+ {
+ m_EvaluateCallbacks.Add(new AnimationPreviewUpdateCallback(animOutput));
+ }
+
+ private static Playable CreatePlayableGraph(PlayableGraph graph, TrackAsset asset, GameObject go, IntervalTree<RuntimeElement> tree, Playable timelinePlayable)
+ {
+ return asset.CreatePlayableGraph(graph, go, tree, timelinePlayable);
+ }
+
+ private Playable CreateTrackPlayable(PlayableGraph graph, Playable timelinePlayable, TrackAsset track, GameObject go, bool createOutputs)
+ {
+ if (!track.IsCompilable()) // where parents are not compilable (group tracks)
+ return timelinePlayable;
+
+ Playable playable;
+ if (m_PlayableCache.TryGetValue(track, out playable))
+ return playable;
+
+ if (track.name == "root")
+ return timelinePlayable;
+
+ TrackAsset parentActor = track.parent as TrackAsset;
+ var parentPlayable = parentActor != null ? CreateTrackPlayable(graph, timelinePlayable, parentActor, go, createOutputs) : timelinePlayable;
+ var actorPlayable = CreatePlayableGraph(graph, track, go, m_IntervalTree, timelinePlayable);
+ bool connected = false;
+
+ if (!actorPlayable.IsValid())
+ {
+ // if a track says it's compilable, but returns Playable.Null, that can screw up the whole graph.
+ throw new InvalidOperationException(track.name + "(" + track.GetType() + ") did not produce a valid playable. Use the compilable property to indicate whether the track is valid for processing");
+ }
+
+
+ // Special case for animation tracks
+ if (parentPlayable.IsValid() && actorPlayable.IsValid())
+ {
+ int port = parentPlayable.GetInputCount();
+ parentPlayable.SetInputCount(port + 1);
+ connected = graph.Connect(actorPlayable, 0, parentPlayable, port);
+ parentPlayable.SetInputWeight(port, 1.0f);
+ }
+
+ if (createOutputs && connected)
+ {
+ CreateTrackOutput(graph, track, go, parentPlayable, parentPlayable.GetInputCount() - 1);
+ }
+
+ CacheTrack(track, actorPlayable, connected ? (parentPlayable.GetInputCount() - 1) : -1, parentPlayable);
+ return actorPlayable;
+ }
+
+ /// <summary>
+ /// Overridden to handle synchronizing time on the timeline instance.
+ /// </summary>
+ /// <param name="playable">The Playable that owns the current PlayableBehaviour.</param>
+ /// <param name="info">A FrameData structure that contains information about the current frame context.</param>
+ public override void PrepareFrame(Playable playable, FrameData info)
+ {
+#if UNITY_EDITOR
+ if (m_Rebalancer != null)
+ m_Rebalancer.Rebalance();
+#endif
+
+ // force seek if we are being evaluated
+ // or if our time has jumped. This is used to
+ // resynchronize
+ Evaluate(playable, info);
+ }
+
+ private void Evaluate(Playable playable, FrameData frameData)
+ {
+ if (m_IntervalTree == null)
+ return;
+
+ double localTime = playable.GetTime();
+ m_ActiveBit = m_ActiveBit == 0 ? 1 : 0;
+
+ m_CurrentListOfActiveClips.Clear();
+ m_IntervalTree.IntersectsWith(DiscreteTime.GetNearestTick(localTime), m_CurrentListOfActiveClips);
+
+ foreach (var c in m_CurrentListOfActiveClips)
+ {
+ c.intervalBit = m_ActiveBit;
+ if (frameData.timeLooped)
+ c.Reset();
+ }
+
+ // all previously active clips having a different intervalBit flag are not
+ // in the current intersection, therefore are considered becoming disabled at this frame
+ var timelineEnd = playable.GetDuration();
+ foreach (var c in m_ActiveClips)
+ {
+ if (c.intervalBit != m_ActiveBit)
+ {
+ var clipEnd = (double)DiscreteTime.FromTicks(c.intervalEnd);
+ var time = frameData.timeLooped ? Math.Min(clipEnd, timelineEnd) : Math.Min(localTime, clipEnd);
+ c.EvaluateAt(time, frameData);
+ c.enable = false;
+ }
+ }
+
+ m_ActiveClips.Clear();
+ // case 998642 - don't use m_ActiveClips.AddRange, as in 4.6 .Net scripting it causes GC allocs
+ for (var a = 0; a < m_CurrentListOfActiveClips.Count; a++)
+ {
+ m_CurrentListOfActiveClips[a].EvaluateAt(localTime, frameData);
+ m_ActiveClips.Add(m_CurrentListOfActiveClips[a]);
+ }
+
+ int count = m_EvaluateCallbacks.Count;
+ for (int i = 0; i < count; i++)
+ {
+ m_EvaluateCallbacks[i].Evaluate();
+ }
+ }
+
+ private void CacheTrack(TrackAsset track, Playable playable, int port, Playable parent)
+ {
+ m_PlayableCache[track] = playable;
+ }
+
+ //necessary to build on AOT platforms
+ static void ForAOTCompilationOnly()
+ {
+ new List<IntervalTree<RuntimeElement>.Entry>();
+ }
+ }
+}