Skip to content

Commit

Permalink
Fix RLE streaming decoder
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyparrish committed Oct 3, 2024
1 parent 2ffcad8 commit 0349ebc
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 51 deletions.
117 changes: 66 additions & 51 deletions common/rle-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,88 +6,103 @@

// Shared RLE code.

static int _rle_control_byte = -1;
static int _rle_more_literals = 0;

static int _rle_output_literals(const uint8_t* data, int bytes);
static void _rle_process(uint8_t control_byte, const uint8_t* data, int bytes);
// If we're processing a repeat, but don't have the actual byte to be repeated,
// this stores the number of repeats. If we're not, it's 0.
static int _rle_pending_repeats = 0;
// If we're in the middle of a sequence of literal bytes spread across multiple
// input buffers, this is the counter for how many are left. If we're not,
// it's 0.
static int _rle_pending_literals = 0;

#if !defined(MIN)
# define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif

static int _rle_output_literals(const uint8_t* data, int bytes, int needed) {
// We are still copying literal bytes to the output stream.
// How many should we copy? The number we need, or the number we have,
// whichever is smaller.
int available = MIN(needed, bytes);
SRAM_WRITE(data, available);
return available;
}

static void _rle_output_repeats(uint8_t data_byte, int repeats) {
for (int i = 0; i < repeats; ++i) {
SRAM_WRITE(&data_byte, 1);
}
}

/**
* Requires these macros:
*
* #define SRAM_WRITE(buffer, size)
*/
static void rle_to_sram(const uint8_t* buffer, int bytes) {
if (_rle_control_byte != -1) {
// Now we have the data to process this cached control byte from another
// callback.
int control_byte = _rle_control_byte;
_rle_control_byte = -1; // clear cache
_rle_process(control_byte, buffer, bytes);
} else if (_rle_more_literals) {
int consumed = _rle_output_literals(buffer, bytes);
buffer += consumed;
bytes -= consumed;
if (!bytes) {
// This shouldn't happen, but if it did, our logic would break.
return;
}

if (bytes) {
_rle_process(buffer[0], buffer + 1, bytes - 1);
if (_rle_pending_repeats) {
// Now we have the data to process this pending repeat command from another
// input buffer.
_rle_output_repeats(*buffer, _rle_pending_repeats);
// This consumes one byte from buffer.
buffer++;
bytes--;
// The pending repeat is satisfied.
_rle_pending_repeats = 0;
} else if (_rle_pending_literals) {
// We are still processing literals from another input buffer.
int consumed = _rle_output_literals(buffer, bytes, _rle_pending_literals);
// This consumed some number of bytes.
buffer += consumed;
bytes -= consumed;
// Our pending literals are also reduced by the same amount.
_rle_pending_literals -= consumed;
}
}

static void rle_reset() {
_rle_control_byte = -1;
_rle_more_literals = 0;
}

static int _rle_output_literals(const uint8_t* data, int bytes) {
// We are still copying literal bytes to the output stream.
int available = MIN(_rle_more_literals, bytes);
SRAM_WRITE(data, available);
_rle_more_literals -= available;
return available;
}
while (bytes) {
// Extract the control byte.
uint8_t control_byte = buffer[0];
buffer++;
bytes--;

static void _rle_process(uint8_t control_byte, const uint8_t* data, int bytes) {
while (true) {
// Parse it.
bool repeat = control_byte & 0x80;
int size = control_byte & 0x7f;

if (repeat) {
if (!bytes) {
// We don't have the byte to repeat.
// Save the control byte for next time and return.
_rle_control_byte = control_byte;
// Save the size for next time.
_rle_pending_repeats = size;
return;
}

// Output the next byte |size| times.
for (int i = 0; i < size; ++i) {
SRAM_WRITE(data, 1);
}
// Repeat the next byte.
_rle_output_repeats(*buffer, size);

// Consume that byte.
data++;
buffer++;
bytes--;
} else {
_rle_more_literals = size;
int consumed = _rle_output_literals(data, bytes);
data += consumed;
// Output literal bytes, as many as we can. (This could be zero.)
int consumed = _rle_output_literals(buffer, bytes, size);

// Consume that data.
buffer += consumed;
bytes -= consumed;
}

if (!bytes) {
// Nothing left in our input.
return;
// Store the number of unfulfilled literal bytes we need from the next
// buffer. (This could be zero.)
_rle_pending_literals = size - consumed;
}

// Set up the next control byte.
control_byte = data[0];
data++;
bytes--;
}
}

static void rle_reset() {
_rle_pending_repeats = 0;
_rle_pending_literals = 0;
}
4 changes: 4 additions & 0 deletions emulator-patches/kinetoscope.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ static bool fetch_range(const char* url, size_t first_byte, size_t size,

static bool fetch_range_to_sram(const char* url, size_t first_byte,
size_t size) {
// This shouldn't be necessary, but in case of an incomplete compressed
// buffer being processed before this, reset the RLE decoder now.
rle_reset();

return fetch_range(url, first_byte, size, http_data_to_sram, NULL);
}

Expand Down

0 comments on commit 0349ebc

Please sign in to comment.