A namespace can register an optional commit hook that fires once per COMMIT pass, before any field setters run. This is the ideal place to validate combinations of related fields or veto inconsistent change sets.
Hook Lifecycle and Capabilities
- Timing: Fires after drafts are received but before any field setters are executed. This allows you to inspect or revert drafts before they affect the runtime.
- Execution: Only fires if the namespace has at least one pending draft.
- Operations via
Namespace&:ns.has_draft(fid) / ns.draft(fid, out): Inspect pending changes.ns.effective(fid): Get the value that will be used (the draft if present, otherwise the current working value).ns.clear_draft(fid): Revert a specific field's change.ns.clear_draft(): Revert all pending changes in the namespace (effectively a no-op commit).ns.set_draft(fid, v): Amend a draft with a new value (triggers the field's validator).
When to use a Hook vs a Setter
- Use a Setter: For constraints affecting a single field in isolation.
- Use a Hook: For multi-field validation, enforcing field ordering, or implementing "all-or-nothing" commit semantics.
Provisioner::instance()
.register_namespace("radio", 100)
.field_int ("sf", 1, FF_REBOOT_REQUIRED, 8, 7, 12)
.field_float("bandwidth", 2, FF_REBOOT_REQUIRED, 125e3, 7.8e3, 500e3)
.on_commit([](Namespace& ns) {
// Cross-field validation: SF12 + BW7.8k is allowed but SF12 + BW500k
// saturates the chip. Revert just the bandwidth draft if the
// combination is illegal.
Value sf, bw;
const bool has_sf = ns.draft(1, sf);
const bool has_bw = ns.draft(2, bw);
const int64_t eff_sf = has_sf ? sf.as_int() : ns.effective(1).as_int();
const double eff_bw = has_bw ? bw.as_float() : ns.effective(2).as_float();
if (eff_sf >= 11 && eff_bw > 250e3) {
ns.clear_draft(2); // bandwidth change refused
}
})
.end();