Skip to content

OnChange moves here, OnSync, ReadOnly keeps its API (0.1.22) - #3

Merged
neu-rah merged 4 commits into
mainfrom
readonly-public
Oct 5, 2026
Merged

neu-rah merged 4 commits into
mainfrom
readonly-public

Conversation

@neu-rah

@neu-rah neu-rah commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Three small data components, in the order of the commits. 0.1.19 -> 0.1.22.

OnChange<fn> (moved from OneMenu)

fn(v) on every set(), v re-read via get() after Base::set(). Unchanged behaviour: OneMenu keeps using oneData::OnChange; (follow-up PR in OneMenu) so its code and examples build unchanged. It is a data component, not a menu one.

OnSync<fn> (new)

Composed over a change tracker (Watch, Dirty): on sync() it calls fn(value) when changed() is true, then Base::sync(), so the tracker keeps its copy and is unchanged. For values that move outside set() (an ISR, a hardware register, a sensor read). Needs get(), changed() and sync() of what it wraps; no Type (so it composes over an ItemRef to a node).

DataDef<OnSync<publish>, Watch<Int>> d;   // d.sync() calls publish(v) once per change, not on an equal set

ReadOnly public, set() deleted

ReadOnly inherited its wrapped part privately, which hides the whole API of the base items, including what an ItemDef reaches through its chain (using Base::enabled), so a ReadOnly could not be composed into an item. set() is now deleted; get(), print(), changed() and sync() stay.

Tests (test/test.cpp)

OnChange (every set, also an equal one; chains through a Watch), OnSync (once per change, not at set(), not on an equal set, two in a row each fire once, over Dirty: it starts dirty and an equal set() counts), ReadOnly (set() not callable, changed()/sync() kept; the existing ReadOnly tests unchanged). g++ and clang++ with -Wall -Wextra -Wpedantic -Werror, and under ASan+UBSan.

Cost (AVR, atmega328p -Os, benchmark/avr_onsync.sh, simavr; eight sets)

flash RAM sizeof cycles
bare Data (set, read back) 592 46 1 107
Watch, loop tests changed() and syncs by hand 622 48 2 159
Watch + OnSync 622 48 2 159
Data + OnChange 602 46 1 147

OnSync costs nothing over the hand-written loop (the two images are byte-identical in size and cycles). Headers only: nothing is instantiated unless used.

🤖 Generated with Claude Code

neu-rah and others added 4 commits October 5, 2026 03:04
…1.20

OnChange<fn>: fn(v) on every set(), as OneMenu had it. OnSync<fn>: on sync(), fn(v) when
changed() is true, then Base::sync() takes the new copy. Host tests for both and an AVR
size/cycle comparison against Data and Watch (test/avr_onsync.sh).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
OnSync reads Base::get() only, so it composes over a node that has get(), changed() and sync()
but no Type (an ItemRef to a node).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A private base hid the wrapped value's whole API, including what an ItemDef reaches through its chain
(using Base::enabled), so a ReadOnly could not be composed into an item. set() is deleted instead; get(),
print(), changed() and sync() stay.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The shared smoke build compiles every test/*.cpp as a host program, and avr_onsync.cpp needs avr/io.h: it is a measurement, like the rest of benchmark/.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@neu-rah
neu-rah merged commit 8ca9819 into main Oct 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant