Skip to content

hapi/slots.h: slots, state composed along a chain and addressed by tag - #5

Merged
neu-rah merged 4 commits into
mainfrom
slots-core
Sep 30, 2026
Merged

neu-rah merged 4 commits into
mainfrom
slots-core

Conversation

@neu-rah

@neu-rah neu-rah commented Sep 30, 2026

Copy link
Copy Markdown
Member

What

hapi::Slot<Tag, S, Contract> is a component that adds one value of type S to the state the components after it built. The state is one object of static size with no heap; a slot is reached by its tag (hapi::slot<Tag>(state)), not by a name or a position; an empty slot adds nothing.

  • The same tag twice (also across a nested Chain or APIOf) and a tag that is not in the state are compile errors with their own messages.
  • Nothing about what the state means, how it evolves or how it travels is decided here: the third parameter, a Contract, is how a user adds members to the state's type. each(visitor) calls Tag::name() and S::each only when it is used.
  • hapi.h includes hapi/slots.h (lazy instantiation: nothing is paid unless used; the amalgamator needs it reachable). single/hapi.h is regenerated.
  • CHANGELOG: a 0.8.0 heading. The items that came after the v0.7.0 tag (single/hapi.h, Distinct, examples/static_net) move under it together with the slots, so the 0.7.0 section describes what the tag holds again. library.json is 0.8.0; the tag itself is not part of this PR.

Checked locally

  • tests/*.cpp as CI compiles them (-Wall -Wextra -Wpedantic): all pass on g++ and clang++; tests/slots_tests.cpp also under -Werror and ASan+UBSan.
  • tests/negative/run.sh: 20/20 on g++ and clang++ (3 new: duplicate tag, duplicate across a nested APIOf, missing tag).
  • tests/slots/run.sh: a typed slot access is the same flashed AVR program as a hand-indexed array (264 bytes, avr-g++ -Os, ATmega328p).
  • tests/single_header/run.sh: 36 ok, 0 FAIL (it now also builds slots_tests from include/ and from the single header alone).
  • Compile time (-fsyntax-only, g++): a chain of 256 slots 1.5 s, against 0.9 s for 256 plain Parts; with the default template depth a chain stops at about 450 Parts, slots or not.

Not checked

  • MSVC: this PR's CI is the first run there. tests/slots_tests.cpp guards the two size checks that depend on empty-base optimisation on _MSC_VER (MSVC lays out several empty bases differently unless __declspec(empty_bases) is used) and prints the sizes instead.
  • ESP32, STM32 (host and ATmega328p only).

Context

This is the slot core of a larger piece of work that keeps everything else out of HAPI (names, a step contract, a schema hash and a wire format live in OneMachine). The evidence, including static_net converted on this core alone (same outputs, byte-identical AVR images, measured against the example as it is today), is in the private R&D repository; the example's conversion is a separate, second pull request.

🤖 Generated with Claude Code

neu-rah and others added 4 commits September 30, 2026 02:52
Slot<Tag,S,Contract> adds one value of type S to the state the components after it built; slot<Tag>(state) reaches it
by its tag. One object of static size, no heap, an empty slot adds nothing. The same tag twice (also across nested Chain
and APIOf) and an absent tag are compile errors with their own messages. What the state means, how it evolves or travels
is not decided here: the Contract parameter is how a user adds members to the state's type. hapi.h includes it.

Tests: tests/slots_tests.cpp (tags, each order, the Contract, nested Chain and APIOf), tests/negative/slots_*.cpp,
tests/slots/run.sh (same flashed AVR program as a hand-indexed array), tests/single_header/run.sh builds slots_tests both
ways. Docs: README "Composing state: slots", docs/REFERENCE.md. CHANGELOG: 0.8.0 heading (the items that came after the
v0.7.0 tag, single/hapi.h, Distinct and static_net, move under it with the slots); library.json 0.8.0; single/hapi.h regenerated.

From R&D/HAPI/typedState @ 41e4086.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… bytes (measured in CI)

The first CI run on MSVC printed the sizes that slots_tests.cpp guards: a state of 6 bytes became 8 with an empty slot; a nested
composition is still the same size as the flat one (10 = 10). The README, the header comment, the CHANGELOG and the test comment
now say that instead of "adds nothing"; __declspec(empty_bases) was not tried. single/hapi.h regenerated (the header comment).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e class

MSVC applies the empty base optimisation to one base only, so an empty slot cost 2 bytes there (CI, first run). The macro is empty on
every other compiler and on clang-cl. Whether it brings MSVC back to zero is what this pull request's CI is for; the corrected wording
stays until it says so. single/hapi.h regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ecks are unconditional again

CI on MSVC with HAPI_EMPTY_BASES (__declspec(empty_bases)): a state with an empty slot is 6 bytes, the same as without; nested and flat are
both 10. The two size checks of slots_tests.cpp no longer skip MSVC, so the claim is enforced there by CI; README, CHANGELOG, REFERENCE and
the header comment say it without the footnote. single/hapi.h regenerated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@neu-rah
neu-rah merged commit 9484468 into main Sep 30, 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