/// <summary> /// Maybe captures a state, if the conditions are favorable /// </summary> /// <param name="frame">frame number to capture</param> /// <param name="callback">will be called with the stream if capture is to be performed</param> /// <param name="indexInvalidated"> /// If provided, will be called with the index of states that are about to be removed. This will happen during /// calls to Write() inside `callback`, and any reuse of the old state will have to happen immediately /// </param> public void Capture(int frame, Action <Stream> callback, Action <int> indexInvalidated = null, bool force = false) { if (!force && !ShouldCapture(frame)) { return; } if (Count == STATEMASK) { indexInvalidated?.Invoke(0); _firstStateIndex = (_firstStateIndex + 1) & STATEMASK; } var start = (_states[HeadStateIndex].Start + _states[HeadStateIndex].Size) & _sizeMask; var initialMaxSize = Count > 0 ? (_states[_firstStateIndex].Start - start) & _sizeMask : Size; Func <long> notifySizeReached = () => { if (Count == 0) { throw new IOException("A single state must not be larger than the buffer"); } indexInvalidated?.Invoke(0); _firstStateIndex = (_firstStateIndex + 1) & STATEMASK; return(Count > 0 ? (_states[_firstStateIndex].Start - start) & _sizeMask : Size); }; var stream = new SaveStateStream(_buffer, start, _sizeMask, initialMaxSize, notifySizeReached); if (_useCompression) { using var compressor = new DeflateStream(stream, CompressionLevel.Fastest, leaveOpen: true); callback(compressor); } else { callback(stream); } _states[_nextStateIndex].Frame = frame; _states[_nextStateIndex].Start = start; _states[_nextStateIndex].Size = (int)stream.Length; _nextStateIndex = (_nextStateIndex + 1) & STATEMASK; //Util.DebugWriteLine($"Size: {Size >> 20}MiB, Used: {Used >> 20}MiB, States: {Count}"); }
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++; }); }); } }