Skip to content

Key/Value Store

The KvStore is a fixed-capacity, typed key/value table for inter-module communication beyond Layer references. It lives in src/core/KvStore.h and is accessed through a singleton. The read path is hot-path optimized: no heap allocation after setup(), constant-time with an FNV-1a hash pre-filter over ≤ 16 entries, and no locking on the single-producer / single-consumer path.

Purpose

Layers move bulk pixel data between effects and drivers. The key/value store moves scalar signals — a brightness level, a beat flag, a sensor reading — between any two modules without coupling them by type. A producer writes a value; a consumer reads it. No wiring in state/modulemanager.json is required.

API

#include "core/KvStore.h"

KvStore& kv = KvStore::instance();

// Write
kv.setFloat("brightness", 0.75f);    // float
kv.setUint8("level",       200);      // uint8_t (0–255)
kv.setBool ("enabled",     true);     // bool

// Read (second argument is the default if the key is absent)
float   v = kv.getFloat("brightness", 1.0f);
uint8_t l = kv.getUint8("level",      0);
bool    e = kv.getBool ("enabled",    false);

// Existence check
if (kv.exists("brightness")) { ... }

// Clear all entries (call in teardown() or between test cases)
kv.clear();

All set* methods return boolfalse when the table is full.

Design constraints

Property Value
Capacity 16 entries (compile-time constant KvStore::Capacity)
Supported types float, uint8_t, bool
Heap allocation after setup() None
Hot-path read cost Linear scan over ≤ 16 entries with FNV-1a hash pre-filter
Thread/core safety Reads unsynchronised (safe for single-producer, single-consumer hot path); add a spinlock when write contention is first observed

Key rules

  • Keys must be string literals — the store holds a const char* pointer, not a copy. "brightness" is valid; a stack-allocated char[] is not.
  • Type mismatch returns the default — reading a float key with getUint8 returns the supplied default, not a cast.
  • Updating an existing key replaces the value and does not consume an extra slot.
  • Call clear() in teardown() of any module that populates the store, or in tests between cases, to prevent residue.

Usage pattern

// Producer (e.g. SineEffectModule)
void loop() override {
    KvStore::instance().setFloat("brightness", computedBrightness_);
    // ... write pixels as normal
}

// Consumer (e.g. BrightnessModifierModule, phase 9)
void loop() override {
    float b = KvStore::instance().getFloat("brightness", 1.0f);
    // ... apply b to pixel output
}

Test coverage

Controls and KV Store — set/get round-trips for float/uint8/bool, default for unknown key, type mismatch default, exists(), update of existing entry, capacity limit.

Brightness Modifier — cross-module KvStore communication (SineEffectModule publishes, BrightnessModifierModule reads).

Backlog

  • Replace linear scan with a hash table once capacity exceeds 16.
  • Add a spinlock (or atomic flag) when dual-core write races are first observed.
  • Consider a typed handle (KvHandle<float>) to avoid key-string lookup every tick.