/// <summary> /// Builder for quadratic extrapolation function /// </summary> public static TimelineExtrapolator <T> BuildQuadraticExtrapolator <T> (Func <T, T, T> add, Func <T, float, T> mul) { return((timeline, context) => { // first need to get the 2 previous values TimelineEntry <T> pp = context._prev._prev; // either there is no previous value or it has the same time if (pp == null || pp._time == context.Prev._time) { return context._prev._value; } TimelineEntry <T> ppp = pp._prev; double t = context._time; double tp = context._prev._time; double tpp = pp._time; if (ppp == null || ppp._time == pp._time) // only 2 values so do linear extrapolation { float factorPrevPrev = (float)((tp - t) / (tp - tpp)); float factorPrev = 1 - factorPrevPrev; return (add(mul(pp._value, factorPrevPrev), mul(context._prev._value, factorPrev))); } else // we have three values so can do quadratic { double tppp = ppp._time; float L0 = (float)(((t - tpp) * (t - tp)) / ((tppp - tpp) * (tppp - tp))); //PPP float L1 = (float)(((t - tppp) * (t - tp)) / ((tpp - tppp) * (tpp - tp))); //PP float L2 = (float)(((t - tppp) * (t - tpp)) / ((tp - tppp) * (tp - tpp))); //P return (add(mul(ppp._value, L0), add(mul(pp._value, L1), mul(context._prev._value, L2)))); } }); }
/// <summary> /// Delete entries from the timeline starting at the first entry so /// that the number of entries in the timeline does not exceed MaxEntries /// Also update _prevEntry and _nextEntry if the current values are removed /// from the timeline /// </summary> internal override void GuaranteeSize() { lock (_entryLock) { while (_numEntries > _maxEntries && _numEntries > 0) { _firstEntry = _firstEntry._next; _numEntries--; } if (_firstEntry != null) { _firstEntry._prev = null; if (_prevEntry != null && _firstEntry._time > _prevEntry._time) { _prevEntry = null; } if (_nextEntry != null && _firstEntry._time > _nextEntry._time) { _nextEntry = _firstEntry; } } else // the timeline has 0 entries { _prevEntry = null; _nextEntry = null; _lastEntry = null; } } }
/// <summary> /// Adds a value to the timeline. /// </summary> /// <param name="time">The time at which to insert the value.</param> /// <param name="value">The value to insert.</param> /// <param name="isTimeAbsolute">Whether or not te specified time is absolute.</param> /// <remarks> /// Entry will be processed. Insertion is not guaranteed. Insertion may be propagated, depending on settings. /// </remarks> public void Set(double time, T value, bool isTimeAbsolute = false) { double absoluteTime = isTimeAbsolute ? time : _now + time; var currentEntry = new TimelineEntry <T>(absoluteTime, value); Insert(currentEntry, false); // message format: // 2 bytes (ushort) timelineIndex // 8 bytes (double) time // n bytes (byte[]) if (_numGuaranteedSends <= 0) { foreach (TimelineSendFilter <T> sendFilter in _sendFilters) { if (sendFilter != null && !sendFilter(this, currentEntry)) { return; } } } lock (_entryLock) { _lastSendTime = DateTime.Now; currentEntry._sent = true; _lastLastSentEntry = _lastSentEntry; _lastSentEntry = currentEntry; if (_numGuaranteedSends > 0) { _numGuaranteedSends--; } } byte[] timelineData = Encode(value); byte[] data = new byte[sizeof(ushort) + sizeof(double) + timelineData.Length * sizeof(byte)]; BinaryWriter writer = new BinaryWriter(new MemoryStream(data)); writer.Write(_index); writer.Write(absoluteTime); writer.Write(timelineData); writer.Close(); if (_manager != null) { _manager.EnqueueOutgoingMessage(new TimelineMessage(TimelineMessageType.RelayAbsolute, data, _deliveryMode)); } }
/// <summary> /// Adds a remote value. /// </summary> /// <param name="time">The time at which to insert the value.</param> /// <param name="valueBytes">The encoded bytes of the value to insert.</param> /// <param name="isTimeAbsolute">Whether or not the specified time is absolute.</param> /// <param name="isCached">Indicates whether the value comes from a remote peer or is a cached value from the synchronizer</param> internal override void RemoteSet(double time, byte[] valueBytes, bool isTimeAbsolute, bool isCached) { double absoluteTime = isTimeAbsolute ? time : _now + time; T value = Decode(valueBytes); var entry = new TimelineEntry <T>(absoluteTime, value); Insert(entry, isCached); if (!(_ignoreCachedEvents && isCached)) { _delayedRemoteInsertionEvents.Enqueue(entry); } }
/// <summary> /// Gets the list of entries which fall within a time range. /// </summary> /// <param name="startTime">The lower time bound.</param> /// <param name="isStartTimeInclusive">Whether or not the start time is inclusive.</param> /// <param name="endTime">The upper time bound.</param> /// <param name="isEndTimeInclusive">Whether or not the end time is inclusive.</param> /// <param name="isTimeAbsolute">Whether or not te specified time is absolute.</param> /// <returns>The list of entries which fall within the specified time range.</returns> public TimelineEntry <T>[] GetRange(double startTime, bool isStartTimeInclusive, double endTime, bool isEndTimeInclusive, bool isTimeAbsolute = false) { double absStartTime = isTimeAbsolute ? startTime : _now + startTime; double absEndTime = isTimeAbsolute ? endTime : _now + endTime; Func <double, bool> isTimeInRange; if (isStartTimeInclusive) { if (isEndTimeInclusive) { isTimeInRange = time => time >= absStartTime && time <= absEndTime; } else { isTimeInRange = time => time >= absStartTime && time < absEndTime; } } else { if (isEndTimeInclusive) { isTimeInRange = time => time > absStartTime && time <= absEndTime; } else { isTimeInRange = time => time > absStartTime && time < absEndTime; } } List <TimelineEntry <T> > entriesInRange = new List <TimelineEntry <T> >(); TimelineEntry <T> currentEntry = _firstEntry; lock (_entryLock) { while (currentEntry != null) { if (isTimeInRange(currentEntry._time)) { entriesInRange.Add(currentEntry); } currentEntry = currentEntry._next; } } return(entriesInRange.ToArray()); }
/// <summary> /// Builder for linear extrapolation function /// </summary> public static TimelineExtrapolator <T> BuildLinearExtrapolator <T> (Func <T, T, T> add, Func <T, float, T> mul, float maxTimeJump = float.PositiveInfinity) { return((timeline, context) => { TimelineEntry <T> prevPrev = context._prevPrev; TimelineEntry <T> prev = context._prev; if (prevPrev == null || prevPrev._time == prev._time) { return prev._value; // either there is no previous value or it has the same time } double tPrevPrev = prevPrev._time; double tPrev = prev._time; double t = context._time; float timeJump = Math.Min(maxTimeJump, (float)(t - tPrev)); t = tPrev + timeJump; float factorPrevPrev = (float)((tPrev - t) / (tPrev - tPrevPrev)); float factorPrev = 1 - factorPrevPrev; return (add(mul(prevPrev._value, factorPrevPrev), mul(prev._value, factorPrev))); }); }
/// <summary> /// Builder for quadratic interpolation function /// </summary> public static TimelineInterpolator <T> BuildQuadraticInterpolator <T> (Func <T, T, T> add, Func <T, float, T> mul) { return((timeline, context) => { // Lagrange basis functions // L0(t) = ((t-t1)(t-t2))/((t0-t1)(t0-t2)) // L1(t) = ((t-t0)(t-t2))/((t1-t0)(t1-t2)) // L2(t) = ((t-t0)(t-t1))/((t2-t0)(t2-t1)) // P(t) = y0*L0(t) + y1*L1(t) + y2*L2(t) double tPrev = context._prev._time; double tNext = context._next._time; double t = context._time; // need a third value for interpolation // look for a value before tPrev - if there isn't one we // will just do linear interplation TimelineEntry <T> prevPrev = context._prev; while (prevPrev._prev != null && prevPrev._time >= context._prev._time) // need loop to make sure that there are not 2 entries with the same time { prevPrev = context._prev._prev; } if (prevPrev != null) // we have three values so do quadratic { double tPrevPrev = prevPrev._time; float L0 = (float)(((t - tPrev) * (t - tNext)) / ((tPrevPrev - tPrev) * (tPrevPrev - tNext))); float L1 = (float)(((t - tPrevPrev) * (t - tNext)) / ((tPrev - tPrevPrev) * (tPrev - tNext))); float L2 = (float)(((t - tPrevPrev) * (t - tPrev)) / ((tNext - tPrevPrev) * (tNext - tPrev))); return (add(mul(prevPrev._value, L0), add(mul(context._prev._value, L1), mul(context._next._value, L2)))); } else // no third value so do linear interpolation { float factorNext = (float)((t - tPrev) / (tNext - tPrev)); float factorPrev = 1 - factorNext; return (add(mul(context._prev._value, factorPrev), mul(context._next._value, factorNext))); } }); }
/// <summary> /// Adds an entry locally. For internal use only. /// </summary> /// <param name="entry">The entry to insert.</param> /// <param name="isCached">Whether or not the entry is a cached value.</param> /// <remarks> /// Entry is not processed. Insertion is guaranteed, and will not be propagated. /// </remarks> void Insert(TimelineEntry <T> entry, bool isCached) { lock (_entryLock) { entry._isCached = isCached; // Start with last entry in timeline and look for where the new Entry // should be inserted TimelineEntry <T> entryBefore = _lastEntry; TimelineEntry <T> entryAfter = null; while (entryBefore != null && entryBefore._time > entry._time) { entryAfter = entryBefore; entryBefore = entryBefore._prev; } if (entryBefore != null) { entryBefore._next = entry; } if (entryAfter != null) { entryAfter._prev = entry; } entry._prev = entryBefore; entry._next = entryAfter; _numEntries++; // update _prevEntry and _nextEntry if inserting between them if ((_prevEntry == null || _prevEntry._time <= entry._time) && entry._time <= _now) { _prevEntry = entry; } if ((_nextEntry == null || _nextEntry._time > entry._time) && entry._time > _now) { _nextEntry = entry; } // update _firstEntry and _lastEntry if inserting before or after them if ((_firstEntry == null || _firstEntry._time > entry._time)) { _firstEntry = entry; } if ((_lastEntry == null || _lastEntry._time <= entry._time)) { _lastEntry = entry; } GuaranteeSize(); if (!(_ignoreCachedEvents && isCached)) { _delayedInsertionEvents.Enqueue(entry); if (entry._time <= _now) { _delayedPassingEvents.Enqueue(entry); } } } }
/// <summary> /// Removes all entries which fall within a time range. /// </summary> /// <param name="startTime">The lower time bound.</param> /// <param name="isStartTimeInclusive">Whether or not the start time is inclusive.</param> /// <param name="endTime">The upper time bound.</param> /// <param name="isEndTimeInclusive">Whether or not the end time is inclusive.</param> /// <param name="isTimeAbsolute">Whether or not te specified time is absolute.</param> /// <returns>The number of entries removed.</returns> public int RemoveRange(double startTime, bool isStartTimeInclusive, double endTime, bool isEndTimeInclusive, bool isTimeAbsolute = false) { double absStartTime = isTimeAbsolute ? startTime : _now + startTime; double absEndTime = isTimeAbsolute ? endTime : _now + endTime; int numRemoved = 0; Func <double, bool> isTimeInRange; if (isStartTimeInclusive) { if (isEndTimeInclusive) { isTimeInRange = time => time >= absStartTime && time <= absEndTime; } else { isTimeInRange = time => time >= absStartTime && time < absEndTime; } } else { if (isEndTimeInclusive) { isTimeInRange = time => time > absStartTime && time <= absEndTime; } else { isTimeInRange = time => time > absStartTime && time < absEndTime; } } TimelineEntry <T> currentEntry = _firstEntry; TimelineEntry <T> beforeRemoveEntry = null; TimelineEntry <T> afterRemoveEntry = null; lock (_entryLock) { // find the entry before the first entry to be removed while (currentEntry != null && (currentEntry._time < absStartTime || (isStartTimeInclusive && currentEntry._time == absStartTime))) { beforeRemoveEntry = currentEntry; currentEntry = currentEntry._next; } // find the entry after the last entry to be removed while (currentEntry != null && (currentEntry._time < absEndTime || (isEndTimeInclusive && currentEntry._time == absEndTime))) { currentEntry = currentEntry._next; afterRemoveEntry = currentEntry; numRemoved++; } // join together the two entries: beforeRemoveEntry and afterRemoveEntry if (beforeRemoveEntry != null) { beforeRemoveEntry._next = afterRemoveEntry; } if (afterRemoveEntry != null) { afterRemoveEntry._prev = beforeRemoveEntry; } // fix up _firstEntry, _lastEntry, _prevEntry and _next Entry if they have been // affected by the removal if (_prevEntry != null && isTimeInRange(_prevEntry._time)) { _prevEntry = beforeRemoveEntry; } if (_nextEntry != null && isTimeInRange(_nextEntry._time)) { _nextEntry = afterRemoveEntry; } if (beforeRemoveEntry == null) { _firstEntry = afterRemoveEntry; } if (afterRemoveEntry == null) { _lastEntry = beforeRemoveEntry; } _numEntries -= numRemoved; } return(numRemoved); }
/// <summary> /// Perform updates for the current frame. For internal use only. /// </summary> internal override void Step() { lock (_entryLock) { if (EntryInserted != null) { while (_delayedInsertionEvents.Count != 0) { EntryInserted(this, _delayedInsertionEvents.Dequeue()); } } else { _delayedInsertionEvents.Clear(); } if (RemoteEntryInserted != null) { while (_delayedRemoteInsertionEvents.Count != 0) { RemoteEntryInserted(this, _delayedRemoteInsertionEvents.Dequeue()); } } else { _delayedRemoteInsertionEvents.Clear(); } if (EntryPassed != null) { while (_delayedPassingEvents.Count != 0) { EntryPassed(this, _delayedPassingEvents.Dequeue()); } } else { _delayedPassingEvents.Clear(); } // Update prev and next entry references. while (_nextEntry != null && _now >= _nextEntry._time) { if (EntryPassed != null) { EntryPassed(this, _nextEntry); } if (EntryMet != null) { EntryMet(this, _nextEntry); } _nextEntry = _nextEntry._next; } if (_nextEntry != null) { _prevEntry = _nextEntry._prev; } else { _prevEntry = _lastEntry; } } }