dataflow: tell a port's own unit apart from the one its address asks for - #2255
Open
jcelerier wants to merge 1 commit into
Open
dataflow: tell a port's own unit apart from the one its address asks for#2255jcelerier wants to merge 1 commit into
jcelerier wants to merge 1 commit into
Conversation
A port has two possible opinions about what its values are: the process declares one (a colour control is rgba, a gain decibels) and the port's address can override it with @[unit]. Both were written into the same field on the execution port, so setting an address destroyed the process's own declaration, clearing one left nothing at all behind, and a new address without a qualifier kept the previous unit. libossia now keeps them apart; this side stops conflating them. Process::Port gains a declared unit, held as a State::Unit so that Port.hpp does not have to include the dataspace headers. It is virtual because a port that comes back from a saved document is built by the inherited deserializing constructors, which run no subclass code: HSVSlider overrides it and its setupExecution goes away, and register_inlet / register_outlet push the declaration to the execution port once. A unit the node declared for itself is more precise than the widget's - an avnd colour port knows whether it is rgb or rgba - so it wins; a plain value type does not. set_destination_impl writes address_unit, unconditionally the way it already did for the index, so dropping a qualifier gives the declaration back. ControlSurface reads the declaration from the model port rather than from whatever setupExecution left behind, which also gives its outlet - the port that actually carries an address there - the unit it never had, and removes the scheduling hack that existed only because set_destination overwrote the type. register_inlet_impl also registered its port with the execution state a second time, on top of the one inside set_destination_impl, while teardown unregisters once. That compensated for an off-by-one in libossia's message_queue; with that fixed, the extra registration is just wrong. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0197hSihNtB1bPw9WDUV3SR2
jcelerier
force-pushed
the
dataflow-port-units
branch
from
September 5, 2026 10:07
2feee60 to
ef1424a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The score half of a three-repo change. Needs ossia/libossia#934 and
celtera/avendish#200 first — the submodule pointers are deliberately not bumped
here, so this branch does not build until those two land.
Where this started
A report of "an occasional bug when setting an OSC address that has a unit
(
orientation.quaternion) as the input of a micromap". Reproduced with the JSscripting API and
oscsend: two processes reading one OSC address, re-pointeither one, and the other silently stops receiving for the rest of the session.
The root cause is a reference count in libossia; this repo contributed to it.
What changes here
register_inlet_implregistered its port with the execution state twice — onceinside
set_destination_impl, once explicitly — while teardown unregisters once.That happened to compensate for the off-by-one in
message_queue::reg(), so thecommon case balanced and the bug only showed when an address was bound while
playing. With libossia fixed, the extra registration is simply wrong and is
removed.
A port had two opinions about its unit and one field to hold them. What the
process declares and what the address asks for with
@[unit]were both writteninto the execution port's
type, so setting an address destroyed the declaration,clearing one wiped it entirely, and a new address without a qualifier kept the old
unit.
set_destination_implnow writesaddress_unit— unconditionally, the wayit already did for the index — so dropping a qualifier gives the declaration back.
Process::Portgains a declared unit, held as aState::UnitsoPort.hppdoes not pull in the dataspace headers. It is virtual because a port loaded from a
saved document is built by the inherited deserializing constructors, which run no
subclass code.
HSVSlideroverrides it and itssetupExecutionis deleted;register_inlet/register_outletpush the declaration to the execution portonce. A unit the node declared for itself wins over the widget's — an avnd colour
port knows whether it is rgb or rgba — while a plain value type does not.
ControlSurface reads the declaration from the model port instead of from
whatever
setupExecutionleft behind. That gives its outlet — the port thatactually carries an address there — the unit it never had, and removes the
in_execscheduling hack whose comment said it existed only becauseset_destinationoverwrote the type.Testing
test_unit_dataflow_valuegains A port's declared unit: a plain controldeclares nothing; a colour control declares rgba, including through a
Process::Port&, which is how the execution setup asks for it;setUnitforper-instance units; and an
HSVSliderserialized and reloaded throughProcess::load_inletstill declares rgba.Avnd_port_unit_Test(new) pins the avendish-side deduction: arity forxy / xyz / xyzw and rgb / rgba, and
halp_meta(unit, …)resolving.--script: two micromaps onosc:/q@[orientation.quaternion],re-point one, the other keeps receiving (it did not, before); a colour control
bound to a
color.hsvaddress converts, and still converts after its addressis cleared and re-bound, which is the declaration surviving; an outlet in
metres into a millimetre address sends 2000 where it sent 2; 1900 address
changes under OSC traffic with no crash.
test_unit_dataflow_value(70),Avnd_port_unit_Test(16),Avnd_point_tracker_Test(535),Avnd_ossia_value_Test,test_unit_port_address_list,test_unit_port_drop, andtest_unit_automation_values/test_unit_mapping_valuescase by case (thewhole-binary SIGSEGV there is the known pre-existing multiple-
run_in_appproblem on Windows, unrelated).
Known and not addressed
State::Unit's constructor heap-allocates, so this costs one small allocationper port. Dropping
m_unitand leavingunit()purely virtual would remove it —every current user is a fixed-unit port — at the cost of the per-instance case.
Process::Port's vtable: out-of-tree addons builtagainst an older SDK need a rebuild.
otherwise empty address, which is serialized. Migrating it to
setUnit()is amodel change and is left out.
the test suite; a real test needs an
Execution::Context, which nothing intests/builds today.Execution::DocumentPlugin::setupContextandSetupContext::inletsare public, so it is reachable — it just needs playbackand one case per binary here.
🤖 Generated with Claude Code
https://claude.ai/code/session_0197hSihNtB1bPw9WDUV3SR2