rokojori_action_library/Runtime/Time/TimelineRunner.cs

353 lines
8.1 KiB
C#

using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System;
using Godot;
using Rokojori.Extensions;
namespace Rokojori;
public class TimelineRunner
{
public Timeline timeLine;
public TimelineManager manager;
public float lastPosition = 0;
public float position = 0;
public bool playing = false;
public float deltaScale = 1;
public float speed = 1;
List<TimelineEvent> events = new List<TimelineEvent>();
List<TimelineSpan> spans = new List<TimelineSpan>();
List<TimelineCallback> callbacks = new List<TimelineCallback>();
AnimationCurve modulatorCurve;
float modulatorTime;
Vector3 modulatorRandomization;
Action<bool> modulatorOnReady = null;
float _lastModulation = 1;
float _currentDelta = 1;
public float modulatedSpeed => (float)( speed * _lastModulation * deltaScale );
public float currentDelta => _currentDelta;
public TimelineRunner( Timeline timeLine, TimelineManager manager )
{
this.timeLine = timeLine;
this.manager = manager;
this.speed = timeLine.startSpeed;
playing = timeLine.autoStart;
}
public void Modulate( AnimationCurve curve, Action<bool> onReady )
{
if ( modulatorOnReady != null )
{
modulatorOnReady( false );
}
modulatorCurve = curve;
modulatorTime = 0;
modulatorRandomization = curve.Randomize();
modulatorOnReady = onReady;
}
public void UpdateTimeLine( float realtimeDelta )
{
if ( ! playing || ( manager.paused && ! timeLine.executesInPause ))
{
return;
}
var modulation = 1f;
if ( modulatorCurve != null )
{
modulation = modulatorCurve.SampleRandomized( modulatorTime, modulatorRandomization );
_lastModulation = modulation;
modulatorTime += realtimeDelta;
if ( modulatorTime >= modulatorCurve.GetRandomizedEndTime( modulatorRandomization ) )
{
var mr = modulatorOnReady;
modulatorOnReady = null;
modulatorCurve = null;
modulatorTime = 0;
modulatorRandomization = Vector3.Zero;
if ( mr != null )
{
mr( true );
}
}
}
else
{
_lastModulation = 1;
}
_currentDelta = realtimeDelta * deltaScale * speed * modulation;
lastPosition = position;
position += _currentDelta;
var isForward = speed >= 0;
if ( isForward )
{
ProcessForward();
}
}
void ProcessForward()
{
ProcessEvents();
ProcessSpans();
ProcessCallbacks();
}
List<TimelineCallback> _callbackAdditions = new List<TimelineCallback>();
void ProcessCallbacks()
{
if ( _callbackAdditions.Count > 0 )
{
callbacks.AddRange( _callbackAdditions );
_callbackAdditions.Clear();
}
callbacks.ForEach( c => c.callback( c ) );
}
void ProcessEvents()
{
if ( requestedRemovalIDs.Count > 0 )
{
requestedRemovalIDs.Sort();
RJLog.Log( "Removing:", requestedRemovalIDs, "From:", events );
//Lists.RemoveIncreasingSortedIndices( events, requestedRemovalIDs );
events = events.Filter( e => ! requestedRemovalIDs.Contains( e.id ) );
requestedRemovalIDs.Clear();
}
List<int> eventRemovals = null;
for ( int i = 0; i < events.Count; i++ )
{
var timelineEvent = events[ i ];
if ( timelineEvent.looping )
{
ProcessLoopingEvent( timelineEvent );
continue;
}
if ( ! RangeDouble.ContainsExclusiveMax( lastPosition, position, timelineEvent.position ) )
{
if ( timelineEvent.wasInside )
{
eventRemovals = AddRemoval( timelineEvent.persistent, i, eventRemovals );
}
continue;
}
timelineEvent.callback( timelineEvent );
timelineEvent.wasInside = true;
}
if ( eventRemovals != null )
{
ListExtensions.RemoveIncreasingSortedIndices( events, eventRemovals );
}
}
// Upper bound of callbacks a single looping event can emit in one frame, so a huge
// frame spike or a jumped timeline position can't stall the game with catching up.
const int maxLoopCallbacksPerFrame = 128;
// Fires once for every loop boundary inside [ lastPosition, position ). Checking for a
// single boundary dropped the tick completely whenever a frame was longer than the loop
// duration, which silently stopped loop events at low frame rates. Looping events are
// only removed by an explicit RemoveEvent, never by elapsing.
void ProcessLoopingEvent( TimelineEvent timelineEvent )
{
if ( timelineEvent.loopDuration <= 0 )
{
return;
}
var boundary = timelineEvent.GetNextLoopPosition( lastPosition );
var numCallbacks = 0;
while ( boundary < position && numCallbacks < maxLoopCallbacksPerFrame )
{
timelineEvent.callback( timelineEvent );
timelineEvent.wasInside = true;
boundary += timelineEvent.loopDuration;
numCallbacks ++;
}
}
void ProcessSpans()
{
if ( spans.Count == 0 )
{
return;
}
List<int> spanRemovals = null;
var timelineSpan = RangeDouble.Create( lastPosition, position );
var span = RangeDouble.Create( 0, 1 );
var isForward = lastPosition < position;
for ( int i = 0; i < spans.Count; i++ )
{
span.min = spans[ i ].start;
span.max = spans[ i ].end;
var overlaps = timelineSpan.Overlaps( span );
if ( ! overlaps )
{
if ( spans[ i ].wasInside )
{
spanRemovals = AddRemoval( spans[ i ].persistent, i, spanRemovals );
}
continue;
}
var isStart = timelineSpan.ContainsValue( spans[ i ].start );
var isEnd = timelineSpan.ContainsValue( spans[ i ].end );
var spanType = TimelineSpanUpdateType.InSpan;
if ( isStart && isEnd )
{
spanType = TimelineSpanUpdateType.CompletelyInside;
}
else if ( isStart )
{
spanType = TimelineSpanUpdateType.Start;
}
else if ( isEnd )
{
spanType = TimelineSpanUpdateType.End;
}
spans[ i ].wasInside = true;
spans[ i ].callback( spans[ i ], spanType );
}
if ( spanRemovals != null )
{
ListExtensions.RemoveIncreasingSortedIndices( spans, spanRemovals );
}
}
List<int> AddRemoval( bool isPersistent, int index, List<int> list )
{
if ( isPersistent )
{
return list;
}
if ( list == null )
{
list = new List<int>();
}
list.Add( index );
return list;
}
List<int> requestedRemovalIDs = new List<int>();
public void RemoveEvent( int eventID )
{
requestedRemovalIDs.Add( eventID );
RJLog.Log( "requestedRemovals add:", eventID );
}
public TimelineCallback _ScheduleCallback( Action<TimelineCallback> callback, int eventID )
{
var tle = new TimelineCallback();
tle.id = eventID;
tle.callback = callback;
tle.timeLine = timeLine;
_callbackAdditions.Add( tle );
return tle;
}
public TimelineEvent _ScheduleEvent( float position, int eventID, bool isPersistent, Action<TimelineEvent> callback )
{
var tle = new TimelineEvent();
tle.position = position;
tle.id = eventID;
tle.persistent = isPersistent;
tle.callback = callback;
tle.timeLine = timeLine;
events.Add( tle );
return tle;
}
public TimelineEvent _ScheduleLoopEvent( float loopDuration, float loopOffset, int eventID, bool isPersistent, Action<TimelineEvent> callback )
{
var tle = new TimelineEvent();
tle.position = loopOffset;
tle.looping = true;
tle.loopDuration = loopDuration;
tle.id = eventID;
tle.persistent = isPersistent;
tle.callback = callback;
tle.timeLine = timeLine;
events.Add( tle );
return tle;
}
public TimelineSpan _ScheduleSpan( float start, float end, int eventID, bool isPersistent, Action<TimelineSpan, TimelineSpanUpdateType> callback )
{
var tse = new TimelineSpan();
tse.start = start;
tse.end = end;
tse.id = eventID;
tse.persistent = isPersistent;
tse.wasInside = false;
tse.callback = callback;
tse.timeLine = timeLine;
spans.Add( tse );
return tse;
}
}