208 lines
7.4 KiB
C
208 lines
7.4 KiB
C
/* ----------------------------------------------------------------------------
|
|
* Copyright (c) 2013 Ben Blazak <benblazak.dev@gmail.com>
|
|
* Released under The MIT License (see "doc/licenses/MIT.md")
|
|
* Project located at <https://github.com/benblazak/ergodox-firmware>
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
/** description
|
|
* Implements the layer-stack defined in "../layer-stack.h"
|
|
*
|
|
* Notes:
|
|
* - This would be relatively trivial to implement using linked lists; but
|
|
* while I'm sure it takes more PROGMEM to do it this way, it uses less SRAM,
|
|
* and is also probably faster on average. I also think it's a nice example
|
|
* of how to resize arrays in C based on demand. I tried to generalize the
|
|
* functionality a little into a "flex-array" for "lib/data-types", in order
|
|
* to make the same technique easier to use elsewhere. It turns out though
|
|
* that doing this in a type-agnostic way leads to passing an inordinate
|
|
* amount of information on the call stack for each function call (array
|
|
* size, element size, ..., ...), which for small stacks defeats the purpose
|
|
* (of saving SRAM), and is kind of inefficient and ugly anyway. But
|
|
* sacrificing a bit of PROGMEM (and programmer time) to reimplement this
|
|
* everywhere it's used seems worth it, since the amount of core code that
|
|
* would be able to be generalized out anyway is relatively small and
|
|
* straightforward.
|
|
*/
|
|
|
|
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include "../layer-stack.h"
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/** macros/MIN_UNUSED/description
|
|
* The minimum number of elements to have unused after a resize
|
|
*/
|
|
#define MIN_UNUSED 0
|
|
|
|
/** macros/MAX_UNUSED/description
|
|
* The maximum number of elements to have unused after a resize
|
|
*/
|
|
#define MAX_UNUSED 4
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/** types/element_t/description
|
|
* The type of one layer-element in our layer-stack
|
|
*
|
|
* Struct members:
|
|
* - `id`: The layer-id of this element
|
|
* - `number`: The layer-number of this element
|
|
*/
|
|
typedef struct {
|
|
uint8_t id;
|
|
uint8_t number;
|
|
} element_t;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/** variables/stack/description
|
|
* To hold the layer-stack and directly related metadata
|
|
*
|
|
* Struct members:
|
|
* - `allocated`: The number of positions allocated
|
|
* - `filled`: The number of positions filled
|
|
* - `data`: A pointer to the array of layer-elements
|
|
*
|
|
* Notes:
|
|
* - The maximum value of `uint8_t filled` is `UINT8_MAX` indicating a stack
|
|
* with `UINT8_MAX` elements. This means that any valid offset or index will
|
|
* be between `0` and `UINT8_MAX-1` inclusive, and that `UINT8_MAX` will
|
|
* therefore always be an invalid value for an offset or index.
|
|
*/
|
|
static struct {
|
|
uint8_t allocated;
|
|
uint8_t filled;
|
|
element_t * data;
|
|
} stack;
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
/** functions/resize_stack/description
|
|
* Resize the stack, so that the number of unused elements is between
|
|
* `MIN_UNUSED` and `MAX_UNUSED`, inclusive
|
|
*
|
|
* Returns:
|
|
* - success: `0`
|
|
* - failure: [other]
|
|
*
|
|
* Implementation notes:
|
|
* - We use a signed type for `unused` in case `filled` is greater than
|
|
* `allocated`, as may happen when pushing a new element onto the stack.
|
|
* This number should normally be much smaller than either `filled` or
|
|
* `allocated`, so we don't need to worry about the maximum value being 2^7-1
|
|
* instead of 2^8-1.
|
|
* - The only time `realloc()` should fail is if we are trying to grow the
|
|
* stack. See [the documentation]
|
|
* (http://www.nongnu.org/avr-libc/user-manual/malloc.html)
|
|
* for `malloc()` in avr-libc.
|
|
*/
|
|
static uint8_t resize_stack(void) {
|
|
int8_t unused = stack.allocated - stack.filled;
|
|
|
|
if (MIN_UNUSED <= unused && unused <= MAX_UNUSED)
|
|
return 0; // nothing to do
|
|
|
|
uint8_t new_allocated;
|
|
if (UINT8_MAX >= stack.filled + (MIN_UNUSED+MAX_UNUSED)/2)
|
|
new_allocated = stack.filled + (MIN_UNUSED+MAX_UNUSED)/2;
|
|
else if (UINT8_MAX >= stack.filled + MIN_UNUSED)
|
|
new_allocated = UINT8_MAX;
|
|
else
|
|
return 1; // unable to count the required number of elements
|
|
|
|
void * new_data = realloc( stack.data, sizeof(element_t) * new_allocated );
|
|
if (!new_data)
|
|
return 1; // error: `realloc()` failed (unable to grow stack)
|
|
|
|
stack.allocated = new_allocated;
|
|
stack.data = new_data;
|
|
|
|
return 0; // success
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
|
|
uint8_t layer_stack__peek(uint8_t offset) {
|
|
if (offset > stack.filled-1)
|
|
return 0; // default
|
|
|
|
return stack.data[stack.filled-1-offset].number;
|
|
}
|
|
|
|
uint8_t layer_stack__push( uint8_t offset,
|
|
uint8_t layer_id,
|
|
uint8_t layer_number ) {
|
|
|
|
// if an element with the given layer-id already exists
|
|
{
|
|
uint8_t old_offset = layer_stack__find_id(layer_id);
|
|
if (old_offset != UINT8_MAX) {
|
|
stack.data[stack.filled-1-old_offset].number = layer_number;
|
|
return old_offset;
|
|
}
|
|
}
|
|
|
|
// add an element
|
|
if (stack.filled == UINT8_MAX)
|
|
return UINT8_MAX; // error: stack already full
|
|
stack.filled++;
|
|
uint8_t index = stack.filled-1-offset;
|
|
if (index > stack.filled-1 || resize_stack()) {
|
|
stack.filled--;
|
|
return UINT8_MAX; // error: index out of bounds, or resize failed
|
|
}
|
|
|
|
// shift up
|
|
// - start with the top element (which is currently uninitialized), and
|
|
// copy the element below it up
|
|
// - continue until we copy the element currently at `index` (which will
|
|
// then be duplicated at `index` and `index-1`)
|
|
// - if the top element is currently at `index`, this will do nothing
|
|
for (uint8_t i = stack.filled-1; i > index; i--)
|
|
stack.data[i] = stack.data[i-1];
|
|
|
|
// set values
|
|
stack.data[index].id = layer_id;
|
|
stack.data[index].number = layer_number;
|
|
|
|
return offset; // success
|
|
}
|
|
|
|
uint8_t layer_stack__pop_id(uint8_t layer_id) {
|
|
uint8_t offset = layer_stack__find_id(layer_id);
|
|
|
|
if (offset == UINT8_MAX)
|
|
return UINT8_MAX; // error: no element with given layer-id
|
|
|
|
uint8_t index = stack.filled-1-offset;
|
|
|
|
// shift down
|
|
// - start with the element at `index`, and copy the element above it down
|
|
// - continue until we copy the top element (which will then be duplicated
|
|
// at indices `stack.filled-2` and `stack.filled-1`)
|
|
// - if the top element is at `index`, this will do nothing
|
|
for (uint8_t i = index; i < stack.filled-1; i++)
|
|
stack.data[i] = stack.data[i+1];
|
|
|
|
// remove an element
|
|
stack.filled--;
|
|
resize_stack(); // we're shrinking the stack, so this should never fail
|
|
|
|
return offset; // success
|
|
}
|
|
|
|
uint8_t layer_stack__find_id(uint8_t layer_id) {
|
|
for (uint8_t i = 0; i < stack.filled; i++)
|
|
if (stack.data[i].id == layer_id)
|
|
return stack.filled-1-i; // offset
|
|
return UINT8_MAX; // error: no element with given layer-id
|
|
}
|
|
|
|
uint8_t layer_stack__size(void) {
|
|
return stack.filled;
|
|
}
|
|
|