pebble/src/fw/util/shared_circular_buffer.c
Josh Soref fa235e9c73 spelling: normalized
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2025-01-29 00:03:25 -05:00

293 lines
11 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "shared_circular_buffer.h"
#include "system/logging.h"
#include "system/passert.h"
#include "util/math.h"
#include <string.h>
// -------------------------------------------------------------------------------------------------
// Returns the amount of data available for the given client
static uint32_t prv_get_data_length(const SharedCircularBuffer* buffer, SharedCircularBufferClient *client) {
uint32_t len;
// The end_index is the index of the next byte to go into the buffer
// The read_index is the index of the first byte
// An empty buffer is when end_index == read_index
if (buffer->write_index >= client->read_index) {
len = buffer->write_index - client->read_index;
} else {
len = buffer->buffer_size - client->read_index;
len += buffer->write_index;
}
return len;
}
// -------------------------------------------------------------------------------------------------
// Returns max amount of data available among all clients
// On exit, *max_client will contain the client with the most amount of data available
static uint32_t prv_get_max_data_length(const SharedCircularBuffer* buffer, SharedCircularBufferClient **max_client) {
ListNode *iter = buffer->clients;
if (!iter) {
*max_client = NULL;
return 0;
}
uint32_t max_data = 0;
uint32_t len;
while (iter) {
len = prv_get_data_length(buffer, (SharedCircularBufferClient *)iter);
if (len >= max_data) {
*max_client = (SharedCircularBufferClient *)iter;
max_data = len;
}
iter = iter->next;
}
return max_data;
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_init(SharedCircularBuffer* buffer, uint8_t* storage, uint16_t storage_size) {
buffer->buffer = storage;
buffer->buffer_size = storage_size;
buffer->clients = NULL;
buffer->write_index = 0;
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_add_client(SharedCircularBuffer* buffer, SharedCircularBufferClient *client) {
PBL_ASSERTN(!list_contains(buffer->clients, &client->list_node));
buffer->clients = list_prepend(buffer->clients, &client->list_node);
client->read_index = buffer->write_index;
return true;
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_remove_client(SharedCircularBuffer* buffer, SharedCircularBufferClient *client) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
list_remove(&client->list_node, &buffer->clients, NULL);
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_write(SharedCircularBuffer* buffer, const uint8_t* data, uint16_t length,
bool advance_slackers) {
// If no clients, no need to write
if (!buffer->clients) {
PBL_LOG(LOG_LEVEL_WARNING, "no readers");
return false;
}
// If this is bigger than the queue would allow, error
if (length >= buffer->buffer_size) {
return false;
}
// Make sure there's room, deleting bytes from slackers if requested
SharedCircularBufferClient *slacker;
uint32_t max_data = prv_get_max_data_length(buffer, &slacker);
uint32_t avail_space = buffer->buffer_size - 1 - max_data;
while (length > avail_space) {
if (!advance_slackers) {
return false;
}
// Delete data from the biggest slacker
slacker->read_index = buffer->write_index;
max_data = prv_get_max_data_length(buffer, &slacker);
avail_space = buffer->buffer_size - max_data;
}
const uint16_t remaining_length = buffer->buffer_size - buffer->write_index;
if (remaining_length < length) {
// Need to write the message in two chunks around the end of the buffer. Write the first chunk
memcpy(&buffer->buffer[buffer->write_index], data, remaining_length);
buffer->write_index = 0;
data += remaining_length;
length -= remaining_length;
}
// Write the last chunk
memcpy(&buffer->buffer[buffer->write_index], data, length);
buffer->write_index = (buffer->write_index + length) % buffer->buffer_size;
return true;
}
// ---------------------------------------------------------------------------------------------
bool shared_circular_buffer_read(const SharedCircularBuffer* buffer,
SharedCircularBufferClient *client, uint16_t length,
const uint8_t** data_out, uint16_t* length_out) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
uint32_t data_length = prv_get_data_length(buffer, client);
if (data_length < length) {
return false;
}
*data_out = &buffer->buffer[client->read_index];
const uint16_t bytes_read = buffer->buffer_size - client->read_index;
*length_out = MIN(bytes_read, length);
return true;
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_consume(SharedCircularBuffer* buffer, SharedCircularBufferClient *client, uint16_t length) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
uint32_t data_length = prv_get_data_length(buffer, client);
if (data_length < length) {
return false;
}
client->read_index = (client->read_index + length) % buffer->buffer_size;
return true;
}
// -------------------------------------------------------------------------------------------------
uint16_t shared_circular_buffer_get_write_space_remaining(const SharedCircularBuffer* buffer) {
SharedCircularBufferClient *slacker;
uint32_t max_data = prv_get_max_data_length(buffer, &slacker);
return buffer->buffer_size - 1 - max_data;
}
// -------------------------------------------------------------------------------------------------
uint16_t shared_circular_buffer_get_read_space_remaining(const SharedCircularBuffer* buffer,
SharedCircularBufferClient *client) {
PBL_ASSERTN(list_contains(buffer->clients, &client->list_node));
return prv_get_data_length(buffer, client);
}
// -------------------------------------------------------------------------------------------------
bool shared_circular_buffer_read_consume(SharedCircularBuffer *buffer, SharedCircularBufferClient *client,
uint16_t length, uint8_t *data, uint16_t *length_out) {
uint16_t data_length = prv_get_data_length(buffer, client);
uint16_t bytes_left = MIN(length, data_length);
*length_out = bytes_left;
while (bytes_left) {
uint16_t chunk;
const uint8_t *read_ptr;
shared_circular_buffer_read(buffer, client, bytes_left, &read_ptr, &chunk);
memcpy(data, read_ptr, chunk);
shared_circular_buffer_consume(buffer, client, chunk);
data += chunk;
bytes_left -= chunk;
}
return (*length_out == length);
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_add_subsampled_client(
SharedCircularBuffer *buffer, SubsampledSharedCircularBufferClient *client,
uint16_t subsample_numerator, uint16_t subsample_denominator) {
PBL_ASSERTN(shared_circular_buffer_add_client(buffer,
&client->buffer_client));
subsampled_shared_circular_buffer_client_set_ratio(
client, subsample_numerator, subsample_denominator);
}
// -------------------------------------------------------------------------------------------------
void shared_circular_buffer_remove_subsampled_client(
SharedCircularBuffer *buffer,
SubsampledSharedCircularBufferClient *client) {
shared_circular_buffer_remove_client(buffer, &client->buffer_client);
}
// -------------------------------------------------------------------------------------------------
void subsampled_shared_circular_buffer_client_set_ratio(
SubsampledSharedCircularBufferClient *client,
uint16_t numerator, uint16_t denominator) {
PBL_ASSERTN(numerator > 0 && denominator >= numerator);
if (client->numerator != numerator || client->denominator != denominator) {
// The subsampling algorithm does not need the subsampling ratio to
// be normalized to reduced form.
client->numerator = numerator;
client->denominator = denominator;
// Initialize the state so that the next item in the buffer is copied,
// not discarded.
client->subsample_state = denominator - numerator;
}
}
// -------------------------------------------------------------------------------------------------
size_t shared_circular_buffer_read_subsampled(
SharedCircularBuffer* buffer,
SubsampledSharedCircularBufferClient *client,
size_t item_size, void *data, uint16_t num_items) {
uint16_t bytes_available = prv_get_data_length(
buffer, &client->buffer_client);
// Optimized case when no subsampling
if (client->numerator == client->denominator) {
num_items = MIN(num_items, bytes_available / item_size);
uint16_t bytes_out;
shared_circular_buffer_read_consume(
buffer, &client->buffer_client, num_items * item_size,
(uint8_t *)data, &bytes_out);
PBL_ASSERTN(bytes_out == num_items * item_size);
return num_items;
}
// An interesting property of the subsampling algorithm used is that
// the subsampling ratio does not need to be in reduced form. It will
// give the exact same results if the numerator and denominator have a
// common divisor.
char *out_buf = data;
size_t items_read = 0;
while (items_read < num_items && bytes_available >= item_size) {
bytes_available -= item_size;
client->subsample_state += client->numerator;
if (client->subsample_state >= client->denominator) {
client->subsample_state %= client->denominator;
uint16_t bytes_out;
shared_circular_buffer_read_consume(buffer, &client->buffer_client,
item_size, (uint8_t *)out_buf,
&bytes_out);
PBL_ASSERTN(bytes_out == item_size);
out_buf += item_size;
items_read++;
} else {
PBL_ASSERTN(shared_circular_buffer_consume(buffer, &client->buffer_client,
item_size));
}
}
return items_read;
}