Closed kevinresol closed 4 years ago
Ok I think I got it, seems like the result of a computation is not recorded (as a new revision?) if the computation is not triggered by a dependency "from inside" of the computation itself. Here is a reproducible snippet, all traces are expected to print true:
import tink.state.*;
using tink.CoreApi;
class Main {
public static function main() {
var baseMap = new ObservableMap<Int, Entity>([]);
function query(key:String) {
final entityQueries = {
final cache = new Map<Int, Pair<Entity, Observable<Bool>>>();
Observable.auto(() -> {
for (id => entity in baseMap)
if (!cache.exists(id)) {
var first = true;
cache.set(id, new Pair(entity, Observable.auto(() -> {
final v = entity.fulfills(key);
if (first)
first = false
else
trace('$entity: re-compute fulfill: $v [$key]');
v;
}, (now, last) -> {
trace('$entity: last:$last, now:$now [$key]');
last == now;
})));
}
final deleted = [for (id in cache.keys()) if (!baseMap.exists(id)) id];
for (id in deleted)
cache.remove(id);
cache;
},
(_, _) -> false // we're always returning the same map, so the comparator must always yield false
);
}
return Observable.auto(() -> {
trace('(re)compute query list [$key]');
[for (p in entityQueries.value) if (p.b) p.a];
});
}
// in the following test, `result` should contain entities whose subMap contains a 'foo' key
final list = query('foo');
var result = null;
list.bind(v -> result = v, Scheduler.direct);
trace(result.map(e -> e.id).contains(0) == false); // ok (start empty)
trace(query('foo').value.map(e -> e.id).contains(0) == false); // make sure we have the same result from a fresh query
final entity0 = new Entity(0);
final entity1 = new Entity(1);
Scheduler.atomically(() -> {
entity0.subMap.set('foo', entity1);
baseMap.set(0, entity0);
baseMap.set(1, entity1);
});
trace(result.map(e -> e.id).contains(0) == true); // ok (because added 'foo')
trace(query('foo').value.map(e -> e.id).contains(0) == true); // make sure we have the same result from a fresh query
final entity2 = new Entity(2);
Scheduler.atomically(() -> {
baseMap.set(2, entity2);
entity0.subMap.remove('foo');
});
trace(result.map(e -> e.id).contains(0) == false); // ok (because removed 'foo')
trace(query('foo').value.map(e -> e.id).contains(0) == false); // make sure we have the same result from a fresh query
Scheduler.atomically(() -> {
entity0.subMap.set('foo', entity1);
});
trace(result.map(e -> e.id).contains(0) == true); // not ok
trace(query('foo').value.map(e -> e.id).contains(0) == true); // make sure we have the same result from a fresh query
}
}
class Entity {
public final id:Int;
public final subMap:ObservableMap<String, Entity> = new ObservableMap([]);
public function new(id) {
this.id = id;
}
public function fulfills(key:String) {
return subMap.exists(key);
}
public function toString() {
return '$id';
}
}
Code is basically the one outlined in #50, with some debug traces added:
Observation:
fulfill=true
is not memorized and the comparator on next loop still thinks the last value isfalse
This happens randomly and there are a lot of observables in action. So I am still unable to reduce it.
Any hints where I can look at?