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 bool — false 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-allocatedchar[]is not. - Type mismatch returns the default — reading a
floatkey withgetUint8returns the supplied default, not a cast. - Updating an existing key replaces the value and does not consume an extra slot.
- Call
clear()inteardown()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.