Channel Value inconsistent

banged my head on this one until i realized its probably a bug

got a Channel<Spread<Boolean>>
hover tooltip and iobox show the values i expect
Select (By Path) and HoldLatest hold something different (both the same though)
the wrong value even looks like a combination of previous and current spread…

ChannelIOvsHoldLatest.vl (38.2 KB)

stable 6.4 and 6.5

Channels have this high-level notion as they can simplify a lot when doing data-binding - they have this bi-directional mechanism and operators that let you build a network of synced channels.

But sometimes you get in contact with the inner workings where things can get more tricky. I have a solution for you that still is high-level and elegant, but if you don’t know about it, banging your head is very relatable.

The solution:
ChannelIOvsHoldLatest.vl (36.2 KB)

It uses a validator which makes sure that the spread gets fixed before even pushed.


Why is it not a bug?
A network of channels that are linked to each other via Select (ByPath) where everything shall sync in all directions is realized internally by a protection mechanism that makes sure that events spread through the network freely in all directions, but still you never end up with an endless cascade of events.

Everything happens immediately. Like with observables or events.
So if A triggers and B and C react on value 1 immediately, it’s still B that reacts first - someone has to. If B would be allowed to trigger again and push value 2, A and C would react on that immediately and after that, let’s not forget, C would react on the initial push “immediately” - value 1.
So the protection mechanism not only makes sure that it’s impossible to get an endless chain reaction, but we also avoid situations where the order of events gets nonsensical.

The protection mechanism works like this: Don’t allow push to a channel that currently propagates changes. Well, it accepts those values but doesn’t fire further events. The problem with your patch is that it tries exactly that: pushing to a channel as a reaction to a push to the channel.

The solution that I posted solves two issues:

  • You never get a spread that is not allowed - in your case two slices true
  • You don’t need to react on a channel in order to fix it and push back. You don’t need to reason about a channel with valid spreads and another channel that allows intermediate values, which need to get synced

Just as a side note, if the validator idea wouldn’t be in place:
A less elegant solution could have been to do certain things on update and not as immediate reaction. Checking the Value of the channel and when wrong pushing a fixed version.

In general, working with channels can trick you into too much event-based thinking. I for myself like to sometimes do stuff on update if possible, reading the current Value and working with that if possible. In a way, Channels are a hybrid of the Reference and the Observable types and both ideas are valid: having a current value always available, being able to change it and being able to react immediately.

My take on it: event-based thinking can be harder than update-based/declarative/data-flow thinking that we all are more used to - thankfully.


BTW: what you saw in the IOBoxes was probably just the dirty version and the fixed spread never made it to the HoldLatest (Reactive), as it never got pushed.

2 Likes

hello
and thanks for taking the time for a lenghty explaination late night.

for the current usecase the validator is exactly what i need. my bad, glossed over it. checked the channels overview patch a couple of times, but somehow missed the explaination patch for validation in the helpbrowser right above it…


i’d still consider the current behaviour a bit buggy though (sorry!)

from the perspective of a safeguard endless cascades it of course makes sense to reject the repeated push. but why accept the value at all if it can’t trigger a further push? seems like a incoherent state between the channel’s inner value and the one that any receiver/consumer has.

also please consider a way for the patcher to be aware of the that blocking behaviour. something like a IsBlocked property or SetValue should be TrySetValue (since it actually can fail) and get a success output?


about the endless cascade.
i was actually aware that my patch would have caused an endless push cycles of updates and safeguarded against it to stop setting new values once the constraint is met.
my issue is, the validator works nice in this case, where state and ‘notification producer’ are isolated - one patch, no external dependencies/constraints.

however, more complex scenarios occur quite quickly, especially with composition and separating view modules from application state, where turning off the safeguards and guarding against the endless cascade manually would be the more feasible implementation. Actually i thought that SetValue would do exactly that versus the safer method of EnsureValue.
i somehow already managed to patch that push cycle last week, where i had to introduce a lock.that’s why i didn’t assume any safeguards to be there. maybe i can find the time to boil it down a bit to share.


about handling (input) logic on update instead of push based
not sure if i’d consider event/push/streambased less declarative/data-flowy. it’s just a less singlethreaded lockstep model, more like async/concurrent branches with some sync points, still feels very much like dataflow.
in this case (not knowing about validators) i solved it on update. the waste of computing cycles on a spread of 3 item each frame is ok. on other instances it would be a massive waste of resources since one would actually only need to do computation when there is user input and not every frame.
so what about having a OnChange pin on the Value output? guess the info is just not exposed, or one could use a HoldLatest…

so conclusion.

  • please don’t show any dirty state on the iobox
  • consider rejecting a new value completely instead of accepting without pushing
  • please some kind of busy property to be able to handle that case in ‘code’ runtime instead of having to recognise it in the patch GUI.
  • maybe a way to disable the safeguard? since channels are the only way to interact with imgui values, one has no way of dropping down to more barebone.
  • Changed/OnData output on Value (might also trigger for new Unit values)
1 Like

@gregsn Wow, very interesting approach, didn’t know there was such a node with this functionality. This is really very useful to know!