Dataflow async
Library ID dataflow-async
Latest version 1.0.5

Debouncing and delayed

When using this library it's possible to obtain a field or attribute whose value is the same, but in some way changed temporally. The initial value of this field is always the same as the original.

Debouncing

link

The first one is created through the debounced() function: when the value of the original field/attribute changes it waits for a grace period before applying the change to itself. If the value changes during this grace period, the waits starts again. It is also possible to specify a maximum amount of time between updates, to avoid a situation where the original data constantly changes thus never allowing the debounced field to update.

import com.femastudios.dataflow.listen.* import com.femastudios.dataflow.async.* import com.femastudios.dataflow.async.util.* fun main() { var i = 0 val attr = attributeOf { i++ } lifecycle { listen(attr.debounced(delay = 100, maxDelay = 200)) { println("debounced value $it") } for(x in 0 until 100) { attr.recompute() Thread.sleep(10) } Thread.sleep(220) //Allow max delay to elapse } }
int i = 0 Attribute<Integer> attr = Attribute.of(wc -> i++); LifecycleOwner.lifecycle(l -> { l.listen(attr.debounced(100, 200), (newValue) => { System.out.println("debounced value " + newValue); }); for(int x = 0; x < 100; x++) { attr.recompute(); Thread.sleep(10); } Thread.sleep(220); //Allow max delay to elapse });

As you can see the debounced attribute changes value every once in a while, depending on the values of delay and maxDelay. If you try and remove the maxDelay from the debounced you'll see that the listener is called only at the end, when the "spamming" of recompute() finishes. Try to remove completely the debounced attribute, listening only for attr, and you'll see how many calls this technique saves.

Note: you can also pass a lambda to calculate the delay. It's useful if you want different delays for different values (for instance, you can avoid debouncing errors). The lambda will pass as the first parameter the current debounced value and the new value from the original field.

Delayed

link

You can also obtained a delayed() attribute or field: this simply means that all changes will be delayed by a given amount of time, effectively having a past live snapshot of your data.

import com.femastudios.dataflow.listen.* import com.femastudios.dataflow.async.* import com.femastudios.dataflow.async.util.* fun main() { var i = 0 val attr = attributeOf { Thread.sleep(10) i++ } lifecycle { val start = System.currentTimeMillis() listen(attr) { println("attr=$it @ " + (System.currentTimeMillis() - start) + "ms") } listen(attr.delayed(1000)) { println("delayed=$it @ " + (System.currentTimeMillis() - start) + "ms") } println() attr.recompute() Thread.sleep(250) attr.recompute() Thread.sleep(100) attr.recompute() Thread.sleep(1100) //Allow delayed to finish } }
int i = 0 Attribute<Integer> attr = Attribute.of(wc -> { Thread.sleep(10); return i++; }); LifecycleOwner.lifecycle(l -> { final long start = System.currentTimeMillis() l.listen(attr, (newValue) => { System.out.println("attr=" + it + " @ " + (System.currentTimeMillis() - start) + "ms"); }); l.listen(attr.delayed(1000), (newValue) => { System.out.println("delayed=" + it + " @ " + (System.currentTimeMillis() - start) + "ms"); }); System.out.println(); attr.recompute(); Thread.sleep(250); attr.recompute(); Thread.sleep(100); attr.recompute(); Thread.sleep(1100) //Allow delayed to finish });

As you can see by running the example all values assumed by attr are assumed by its delayed counterpart one second later. Note that due to scheduling uncertainty some intermediate states may be lost if they are too temporally close; you can see this by removing the sleep in the attribute calculation. Don't worry: the most recent state is always chosen.

Note: as for debouncing, you can also pass a lambda to calculate the delay. The lambda will always pass as the first parameter the current delayed value and the new value from the original field.