static LookupResult LookupWithAhead( ReadOnlySpan <byte> src, int position, ref RevolvingBufferTracker <int> window, Span <int> recents) { var result1 = Lookup(src, position, ref window, recents); if (result1.Offset >= 0 && (position + 1) < src.Length) { // Store recent value for restoring afterwards. var prevRecent = recents[src[position]]; Remember(src, position, ref window, recents); // Perform lookup for next byte. var result2 = Lookup(src, position + 1, ref window, recents); // Restore recent, pop previous delta value. recents[src[position]] = prevRecent; window.Pop(); if (result2.Length >= result1.Length + 2) { // If result2 is preferred, return with SkipByte set. return(result2.WithSkipByte(true)); } } return(result1); }
/// <summary> /// Update Deltas window and Recents for value of current byte. /// </summary> /// <param name="src">Source buffer.</param> /// <param name="position">Position in source buffer.</param> /// <param name="window">Deltas window.</param> /// <param name="recents">Recents buffer.</param> static void Remember( ReadOnlySpan <byte> src, int position, ref RevolvingBufferTracker <int> window, Span <int> recents) { // Updates both Recents and Window. var value = src[position]; var recent = recents[value] - 1; recents[value] = position + 1; window.Push(recent); }
/// <summary> /// Perform lookup for byte string at position. /// </summary> /// <param name="src">Source buffer.</param> /// <param name="position">Position in source buffer.</param> /// <param name="window">Deltas window.</param> /// <param name="recents">Recents buffer.</param> /// <returns></returns> static LookupResult Lookup( ReadOnlySpan <byte> src, int position, ref RevolvingBufferTracker <int> window, Span <int> recents) { // Get offset to previous index of current byte value. var c = src[position]; var recent = recents[c] - 1; // Check if: recent is 0 (no previous byte with this value), or offset is out-of-range of window. if (recent < 0 || window.Length < (position - recent)) { return(LookupResult.NoneFound()); } else { // Start searching at recent position. var searchPosition = recent; var currentSlice = src.Slice(position, Math.Min(MaxCompare, src.Length - position)); var windowStartPosition = Math.Max(position - window.Length, 0); var result = LookupResult.NoneFound(); // Follow chain backwards and look for matches. while (windowStartPosition <= searchPosition) { // Get slice for bytes to compare against currentSlice var searchSlice = src.Slice(searchPosition); // Find number of potential matches in search slice. var potentialMatches = Math.Min(currentSlice.Length, searchSlice.Length); // Set matches count to maximum, will update if loop exits early. var matches = potentialMatches; for (int i = 0; i < potentialMatches; i++) { if (currentSlice[i] != searchSlice[i]) { matches = i; break; } } // Update lookup result if found new longest match. if (matches >= 3 && matches >= result.Length) { result = new LookupResult(searchPosition, matches); // If we have found a matching string with the max compare size, return early. if (result.Length == MaxCompare) { break; } } // Get search position relative to window start, and update search position. var windowIndex = searchPosition - windowStartPosition; searchPosition = window.Get(windowIndex); // If next index is less-than 0, no previous instance of this value. if (searchPosition < 0) { break; } } return(result); } }
/// <summary> /// Perform raw encode using provided buffers for "Deltas" and "Recents" values with specified <see cref="LookupScheme"/>. /// </summary> /// <param name="src">Source buffer.</param> /// <param name="dst">Destination buffer.</param> /// <param name="recents">Provided buffer for storing Recents.</param> /// <param name="windowBuf">Provided buffer for storing Deltas.</param> /// <param name="lookupScheme">Scheme to use for lookups.</param> /// <returns>Length of encoded bytes.</returns> public static int EncodeRaw_Internal( ReadOnlySpan <byte> src, Span <byte> dst, Span <int> recents, Span <int> windowBuf, LookupScheme lookupScheme = LookupScheme.LookAhead) { // Variables to keep track of indexes for src/dst buffers, and code byte. int codeBytePlace = 0, commandBit = 0, dstPlace = 0, srcPlace = 0; // Helper for tracking state of "Deltas" revolving buffer. var window = new RevolvingBufferTracker <int>(windowBuf); // Recent lookup result. var result = LookupResult.NoneFound(); // Whether or not to use LookAhead lookup scheme. bool useLookAheadScheme = lookupScheme == LookupScheme.LookAhead; while (srcPlace < src.Length) { if (commandBit == 0) { // Reserve code byte location for modifying. codeBytePlace = dstPlace++; dst[codeBytePlace] = 0; } // Perform lookup if result does not have a valid offset from previous loop. if (!result.Found) { if (useLookAheadScheme) { // Perform lookup for both current byte and next byte. result = LookupWithAhead(src, srcPlace, ref window, recents); } else { // Perform standard lookup for only the current byte. result = Lookup(src, srcPlace, ref window, recents); } } // Append to Deltas window and Recents. Remember(src, srcPlace, ref window, recents); // If found chunk in lookup, encode information about it. if (!result.SkipByte && result.Found) { // Calculate distance between current src offset and chunk offset. var distance = srcPlace - result.Offset - 1; // Write bytes to indicate RLE copy. // Encodes differently depending on chunk length. if (result.Length < 0x12) { // Encode for chunk size smaller than 0x12. Uses 2 bytes. dst[dstPlace] = (byte)(((result.Length - 2) << 4) | (distance >> 8)); dst[dstPlace + 1] = (byte)(distance & 0xFF); dstPlace += 2; } else { // Encode for chunk size 0x12 or larger (up to 0x111). Uses 3 bytes. dst[dstPlace] = (byte)(distance >> 8); dst[dstPlace + 1] = (byte)(distance & 0xFF); dst[dstPlace + 2] = (byte)(result.Length - 0x12); dstPlace += 3; } // Would like to call method WriteRLEIndicator instead of above code, but it causes a performance hit. // dstPlace += WriteRLEIndicator(dst, dstPlace, distance, result.Length); // Go through each matched byte and append to delta window. // Todo: Maybe don't do this if already finished encoding? for (int j = 1; j < result.Length; j++) { Remember(src, srcPlace + j, ref window, recents); } srcPlace += result.Length; } else { // If no result from lookup, set bit for direct copy. dst[codeBytePlace] |= (byte)(1 << (7 - commandBit)); dst[dstPlace++] = src[srcPlace++]; } // Update result state. result = LookupResult.ClearIfNotSkipped(ref result); // Update command bit index. commandBit = (commandBit + 1) % 8; } return(dstPlace); }