Closed acrylic-origami closed 7 years ago
Resolve with 54a41af. The behavior breakdown at low debouncing delays is probably a bigger phenomenon than the operator itself, and will probably improve only from improving the efficiency of Producer
as a whole.
Bouncing around some problems and solutions
Almost everything about
debounce
resists implementation in Hack. Fundamentally, it differs from all of the other operators because it's the absence of items that drives it to emit. The implementation that comes to mind the most naturally: race theSleepWaitHandle
for the debouncing period with the next item to be emitted, and if theSleepWaitHandle
resolves first, emit the item that's paired with it. The last item being delayed by that debouncing period is not helping at all, because for many other operators, aConditionWaitHandle
wrappingProducer::collapse()
(or an equivalent) lasted long enough for any resolutions to hit it before theProducer
ended. In this case, however, the last item can't enjoy this guarantee.Wrapping the
SleepWaitHandle
in aConditionWaitHandle
andcatch
ing the "ConditionWaitHandle not notified by child" exception to emit the next item feels unclean, but it could work.The other half of the problem is that somehow this
SleepWaitHandle
-Producer::next()
race needs to push the values through ayield
. The most likely candidate right now is an async block that closes over the race and aWrapper
to the current value,await
s that race and yields the current value conditionally on the exception fromConditionWaitHandle
.An edge case is lurking: if all exceptions are routed through the
ConditionWaitHandle
hosting the race (very likely — such that the consuming scope can receive these exceptions), and if aConditionWaitHandle
isn't notified by a child further up the stack, all those specific, "true" exceptions will be lost becauseawait
ing scope (the async block) will assume it's just the signal to emit the last value. I'm unsure if the status of theSleepWaitHandle
helps in this situation, because a race condition might permit theSleepWaitHandle
to be finished while a true exception is thrown.The best-case scenario is that a thin wrapper around the
SleepWaitHandle
could notify the raceConditionWaitHandle
that wraps it. Apparently, the only way to do this soundly is withAsyncPoll
, although I'm convinced that so long aSleepWaitHandle
isn't eagerly executed, we can make a self-referential pointer in aConditionWaitHandle
to resolve itself like this: