public void Capture(int frame, IStatable source, bool force = false) { if (frame <= Last) { CaptureHighPriority(frame, source); } _current.Capture(frame, s => source.SaveStateBinary(new BinaryWriter(s)), index => { var state = _current.GetState(index); _recent.Capture(state.Frame, s => state.GetReadStream().CopyTo(s), index2 => { var state2 = _recent.GetState(index2); var from = _ancient.Count > 0 ? _ancient[_ancient.Count - 1].Key : 0; if (state2.Frame - from >= _ancientInterval) { var ms = new MemoryStream(); state2.GetReadStream().CopyTo(ms); _ancient.Add(new KeyValuePair <int, byte[]>(state2.Frame, ms.ToArray())); } }); }, force); }
public void Capture(int frame) { if (!Active) { return; } _buffer.Capture(frame, s => _stateSource.SaveStateBinary(new BinaryWriter(s))); }
private void CaptureGap(int frame, IStatable source) { // We need to do this here for the following scenario // We are currently far enough in the game that there is a large "ancient interval" section // The user navigates to a frame after ancient interval 2, replay happens and we start filling gaps // Then the user, still without having made an edit, navigates to a frame before ancient interval 2, but after ancient interval 1 // Without this logic, we end up with out of order states // We cannot use InvalidateGaps because that does not address the state cache or check for reserved states. for (int i = _gapFiller.Count - 1; i >= 0; i--) { var lastGap = _gapFiller.GetState(i); if (lastGap.Frame < frame) { break; } if (_reserveCallback(lastGap.Frame)) { AddToReserved(lastGap); } else { StateCache.Remove(lastGap.Frame); } _gapFiller.InvalidateEnd(i); } _gapFiller.Capture( frame, s => { AddStateCache(frame); source.SaveStateBinary(new BinaryWriter(s)); }, index => { var state = _gapFiller.GetState(index); StateCache.Remove(state.Frame); if (_reserveCallback(state.Frame)) { AddToReserved(state); return; } }); }
private void CaptureGap(int frame, IStatable source) { // We need to do this here for the following scenario // We are currently far enough in the game that there is a large "ancient interval" section // The user navigates to a frame after ancient interval 2, replay happens and we start filling gaps // Then the user, still without having made an edit, navigates to a frame before ancient interval 2, but after ancient interval 1 // Without this logic, we end up with out of order states if (_gapFiller.Count > 0 && frame < GapStates().First().Frame) { InvalidateGaps(frame); } _gapFiller.Capture( frame, s => { StateCache.Add(frame); source.SaveStateBinary(new BinaryWriter(s)); }, index => StateCache.Remove(index)); }
private ZwinderBuffer UpdateBuffer(ZwinderBuffer buffer, RewindConfig newConfig, bool keepOldStates) { if (buffer == null) // just make a new one, plain and simple { buffer = new ZwinderBuffer(newConfig); } else if (!buffer.MatchesSettings(newConfig)) // no need to do anything if these settings are already in use { if (keepOldStates) { // force capture all the old states, let the buffer handle decay if they don't all fit ZwinderBuffer old = buffer; buffer = new ZwinderBuffer(newConfig); for (int i = 0; i < old.Count; i++) { ZwinderBuffer.StateInformation si = old.GetState(i); // don't allow states that should be reserved to decay here, where we don't attempt re-capture if (_reserveCallback(si.Frame)) { AddToReserved(si); } else { buffer.Capture(si.Frame, s => si.GetReadStream().CopyTo(s), null, true); } } old.Dispose(); } else { buffer.Dispose(); buffer = new ZwinderBuffer(newConfig); } } return(buffer); }
public unsafe void Capture(int frame) { Sync(); if (!_active) { return; } if (_masterFrame == -1) { var sss = new SaveStateStream(this); _stateSource.SaveStateBinary(new BinaryWriter(sss)); (_master, _scratch) = (_scratch, _master); _masterLength = (int)sss.Position; _masterFrame = frame; _count++; return; } if (!_buffer.WillCapture(_masterFrame)) { return; } { var sss = new SaveStateStream(this); _stateSource.SaveStateBinary(new BinaryWriter(sss)); Work(() => { _buffer.Capture(_masterFrame, underlyingStream_ => { var zeldas = SpanStream.GetOrBuild(underlyingStream_); if (_master.Length < _scratch.Length) { var replacement = new byte[_scratch.Length]; Array.Copy(_master, replacement, _master.Length); _master = replacement; } var lengthHolder = _masterLength; var lengthHolderSpan = new ReadOnlySpan <byte>(&lengthHolder, 4); zeldas.Write(lengthHolderSpan); fixed(byte *older_ = _master) fixed(byte *newer_ = _scratch) { int *older = (int *)older_; int *newer = (int *)newer_; int lastIndex = (Math.Min(_masterLength, (int)sss.Position) + 3) / 4; int lastOldIndex = (_masterLength + 3) / 4; int *olderEnd = older + lastIndex; int *from = older; int *to = older; while (older < olderEnd) { if (*older++ == *newer++) { if (to < from) { // Save on [to, from] lengthHolder = (int)(from - to); zeldas.Write(lengthHolderSpan); zeldas.Write(new ReadOnlySpan <byte>(to, lengthHolder * 4)); } to = older; } else { if (from < to) { // encode gap [from, to] lengthHolder = (int)(to - from) | IS_GAP; zeldas.Write(lengthHolderSpan); } from = older; } } if (from < to) { // encode gap [from, to] lengthHolder = (int)(to - from) | IS_GAP; zeldas.Write(lengthHolderSpan); } if (lastOldIndex > lastIndex) { from += lastOldIndex - lastIndex; } if (to < from) { // Save on [to, from] lengthHolder = (int)(from - to); zeldas.Write(lengthHolderSpan); zeldas.Write(new ReadOnlySpan <byte>(to, lengthHolder * 4)); } } (_master, _scratch) = (_scratch, _master); _masterLength = (int)sss.Position; _masterFrame = frame; _count++; }); }); } }
public void Capture(int frame, IStatable source, bool force = false) { // We already have this state, no need to capture if (StateCache.Contains(frame)) { return; } if (_reserveCallback(frame)) { CaptureReserved(frame, source); return; } // We do not want to consider reserved states for a notion of Last // reserved states can include future states in the case of branch states if ((frame <= LastRing && NeedsGap(frame)) || force) { // We use the gap buffer for forced capture to avoid crowding the "current" buffer and thus reducing it's actual span of covered frames. CaptureGap(frame, source); return; } _current.Capture(frame, s => { source.SaveStateBinary(new BinaryWriter(s)); AddStateCache(frame); }, index => { var state = _current.GetState(index); StateCache.Remove(state.Frame); // If this is a reserved state, go ahead and reserve instead of potentially trying to force it into recent, for further eviction logic later if (_reserveCallback(state.Frame)) { AddToReserved(state); return; } _recent.Capture(state.Frame, s => { state.GetReadStream().CopyTo(s); AddStateCache(state.Frame); }, index2 => { var state2 = _recent.GetState(index2); StateCache.Remove(state2.Frame); var isReserved = _reserveCallback(state2.Frame); // Add to reserved if reserved, or if it matches an "ancient" state consideration if (isReserved || !HasNearByReserved(state2.Frame)) { AddToReserved(state2); } }); }, force); }
public void CaptureHighPriority(int frame, IStatable source) { _highPriority.Capture(frame, s => source.SaveStateBinary(new BinaryWriter(s))); }