Code Monkey home page Code Monkey logo

tink_state's Introduction

tink_state's People

Contributors

adrianv avatar aliokan avatar back2dos avatar kevinresol avatar nadako avatar neverbirth avatar postite avatar sh-dave avatar testcodespaceerfer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tink_state's Issues

Issue with SignalObservable(?)

I was using SignalObservable to make a bridge between legacy systems and observables, but looks like there's a bug:

import tink.core.Signal;
import tink.state.Observable;
import tink.state.Scheduler;

function main() {
	var value = 1;
	var changed = Signal.trigger();

	var signalObservable = new Observable(() -> value, changed);
	var derivedObservable = Observable.auto(() -> signalObservable.value);

	function change(newValue) {
		trace("changing");
		value = newValue;
		changed.trigger(Noise);
	}

	trace("first bind");
	var link = derivedObservable.bind(v -> trace("bind callback: " + v), Scheduler.direct);
	change(2);

	trace("unbind");
	link.cancel();

	trace("second bind");
	derivedObservable.bind(v -> trace("bind callback: " + v), Scheduler.direct);

	change(3); // no bind callback invokation here!
	trace(signalObservable.value); // ok: 3
	trace(derivedObservable.value); // wrong: 2
}
src/Main.hx:18: first bind
src/Main.hx:19: bind callback: 1
src/Main.hx:13: changing
src/Main.hx:19: bind callback: 2
src/Main.hx:22: unbind
src/Main.hx:25: second bind
src/Main.hx:26: bind callback: 2
src/Main.hx:13: changing
src/Main.hx:29: 3
src/Main.hx:30: 2

Change is not remembered somehow

Code is basically the one outlined in #50, with some debug traces added:

public function query(q:Query):Observable<Array<Entity>> {
	final qs = q.toString();
	final entityQueries = {
		final cache = new Map<Int, Pair<Entity, Observable<Bool>>>();
		Observable.auto(() -> {
			for (id => entity in map)
				if (!cache.exists(id)) {
					var first = true;
					cache.set(id, new Pair(entity, Observable.auto(() -> {
						final v = entity.fulfills(q);
						if (first)
							first = false
						else
							trace('$entity: re-compute fulfill: $v [$qs]');
						v;
					}, (now, last) -> {
							trace('$entity: last:$last, now:$now [$qs]');
							last == now;
						}, id -> 'World:${entity.toString()}:fulfills#$id')));
				}

			final deleted = [for (id in cache.keys()) if (!map.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
			id -> 'World:cache#$id');
	}

	return Observable.auto(() -> {
		trace('(re)compute query list [$qs]');
		[for (p in entityQueries.value) if (p.b) p.a];
	}, null, id -> 'World:root#$id');
}

Observation:

  • somehow the result of fulfill=true is not memorized and the comparator on next loop still thinks the last value is false
=> game loop starts
unset a key in observable map
recompute "fulfill" -> false
comparator -> was:true, now:false
recompute query list
=> game loop ends

=> game loop starts
set a key in observable map
recompute query list
recompute "fulfill" -> true   ** Note this
=> game loop ends

=> game loop starts
unset a key in observable map
recompute "fulfill" -> false
comparator -> was:false, now:false    ** was false????????

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?

Exception catching mode

The issue is the same as in the tink_core ticket: haxetink/tink_core#165
Or will adding an exception catching mode to tink_core also fulfill the same purpose for tink_state?

I made a small test program which checks how it works with both signals and observables. The results are more scary than I thought. If any signal handler / observable binding throws an exception, not only this prevents other handlers / bindings from being called with the current signal / state change, but none of the handlers/bindings ever run again on further signals / state changes.

exception-test.tar.gz

ObservableMap observes added/removed key

Somehow I would like to have a signal to notify when a key is added/removed. For now I can observe the key list and compare the old vs new one but doesn't seems to be very smart.

ObservableMap errors and warnings with Haxe preview 5

Hi,
got error with haxe preview version 5.

tink_state/0,10,0/src/tink/state/ObservableMap.hx:13: lines 13-84 : Field keyValueIterator needed by haxe.IMap is missing

and warning
tink_state/0,10,0/src/tink/state/ObservableMap.hx:13: characters 38-52 : Warning : This typedef is deprecated in favor of haxe.IMap<IMap.K, IMap.V>

Support Haxe 3.2.1

I don't know if this is still actually a think. But if it is then the Cannot create closure on abstract inline method errors need to be dealt with.

What API replaces observableLength and observableValues?

Hi,
I'm trying to figure out how to use ObservableArray for add/remove item notifications. I am using tink_state 1.0.0-beta.2, but also tried 0.11.1. In the older version there were (already deprecated) properties observableLength and observableValues. Binding to those properties works correctly for me in 0.11.1. In 1.0.0-beta.2 they no longer work and I cannot figure out what API should I use instead.

Obervable.ofPromise does not fire binding?

In the following the binding only fires Loading, but expected to also fire Done(1)

final p = Promise.trigger();
final o = Observable.auto(() -> p.asPromise());

o.bind(v -> trace(Std.string(v)), Scheduler.direct); 
trace(Std.string(o.value)); // Loading
p.resolve(1);
trace(Std.string(o.value)); // Done(1)

mapAsync broken?

Currently the follow test fails.

It is not updating the mapped observable after the origin changed.

  public function mapAsync() {
    final s = new State(0);
    final o:Observable<Int> = s;
    final m = o.mapAsync(v -> Future.delay(0, v));
    final fired = [];
    m.bind(v -> fired.push(v)); // observe the mapped value
    haxe.Timer.delay(() -> {
      s.set(1); // update state
      trace(fired);
      haxe.Timer.delay(() -> {
        asserts.assert(Std.string(fired[0]) == 'Loading');
        asserts.assert(Std.string(fired[1]) == 'Done(0)');
        asserts.assert(Std.string(fired[2]) == 'Done(1)'); // not fired
        asserts.assert(fired.length == 3);
        asserts.done();
      }, 10);
    }, 0);
    return asserts;
  }

early break in AutoObservable s.hasChanged check can trigger multiple recalculations

I've found an interesting thing while porting your excellent library to C#. When AutoObservable checks if any of the subscriptions has actually changed it breaks earlier and skips checking other dependencies, which sounds logical... However hasChanged in the current revision also updates the revision and value for a Subscription, and if we only do that for the first changed dependency and skip all other, the subsValid will return false and we'll need a second loop iteration.

At first I was thinking that the correct fix would be to simply remove break here, however after some investigation, I think it's even better to update Subscription.lastRev on reuse. What do you think?

Suspected memory leak in ObservableIterator

class Main {
	static function main() {
		new haxe.Timer(4000).run = function() {
			var memory = js.Node.process.memoryUsage();
			inline function format(v:Float)
				return Std.int(v / 1024 / 1024 * 100) / 100;

			trace('${Date.now().toString()}: Heap: ${format(memory.heapUsed)} / ${format(memory.heapTotal)} MB');
		};
		
		var map = new tink.state.ObservableMap<String, String>([]);
		
		new haxe.Timer(100).run = function() {
			for(id in map.keys()) trace(id);
		}
	}
}

The above code will create an ObservableIterator every 100ms, and the memory usage will go up over time. From what I investigated I suspect the changes.nextTime() given to the measurement caused the leak.

Allow atomic updates.

Should allow updating multiple states "at the same time" (e.g. at the end of a transition) without firing direct bindings inbetween.

Add an equivalent of Promised for Future, without the Failed state

From the gitter chat:

I wonder if tink_state should/could have a Futuristic (or something) type, which is like Promised but without Failed state, basically an observable for Future rather than Promise.

in our codebase we use Future instead of Promise quite a lot, because in a lot of cases the error case is handled globally and leads to a "something went wrong, reload your game" modal dialog, especially when it comes to backend communication. so there's no error handling on a higher level. now if some model have data initialized like this, it can only have two states: Loading and Done :)

Add State.update

Should add an update function to State that can access the current value without creating dependencies. Particularly useful for autorun, e.g.:

autorun(() -> {
  someDependencies;
  someState.value += 1;// will create infinite loop
});
// vs.:
autorun(() -> {
  someDependencies;
  someState.update(v -> v + 1);
});

Cannot access the js package while in a macro (for js.Node)

Following thrown when compiled with hxnodejs:
tink/state/State.hx:106: characters 7-14 : You cannot access the js package while in a macro (for js.Node)

Note, haxe version is set to edge "2b6424c". UPD: reproduced with latest haxe as well.

Debugging tools.

Some helpers to debug observables would be good, in particular getting the dependency graphs of auto-observables.

Optimize subscriptions.

Currently, auto observables subscribe in a rather inefficient matter:

  1. the subscribe to the same dependency multiple times.
  2. when they recompute, they clear all subscriptions.

It should be solvable in a more efficient manner, although self-invaliding computations make things rather challenging.

map only works after getting value once

var state = new State(0);
state.observe().changed.handle(function(_) trace('state changed: ${state.value}'));

var mapping = state.observe().map(function(_) return state.value + 1);
// trace(mapping.value); // uncomment this line to make it work
mapping.changed.handle(function(_) trace('mapping changed ${mapping.value}'));

state.set(1);
state.set(2);

The above code prints:

state changed: 1
state changed: 2

which means the map is not working.

It is most likely caused by the valid filter here

async auto-observable value not being updated properly when not bound (only on `v1` branch)

The following code returns Loading on the second value access, unless we bind (and thus wake up and do the triggerAsync thing). Only happens on the v1 branch, master is fine:

import tink.core.Future;
import tink.state.Observable;

function main() {
	var o = Observable.auto(() -> new Future(trigger -> {
		return haxe.Timer.delay(() -> trigger(10), 500).stop;
	}));

	trace(o.value);

	haxe.Timer.delay(() -> {
		trace(o.value); // still Loading
	}, 1000);
}

Add `ObservableObject.isConstant`

Should also return true for auto observables that have no dependencies. Subscriptions on constant observables would just noop out.

ObservableArray#set does not expand the array

Somehow I accidentally used .set() instead of .push() to add a new value and discovered this bug. Not sure if worth fixing.

var arr = new ObservableArray<Bool>();
trace(arr.length); // 0
arr.set(0, true);
trace(arr.length); // 0

ObservableMap.keys() returns the same iterator until changed

class Main {
    static function main() {
        var map = new tink.state.ObservableMap<String, String>(new Map());
        
        map.set('foo', 'bar');
        
        for(key in map.keys()) trace(key);
        for(key in map.keys()) trace(key);
        
    }
}

The above code is expected to print foo twice but actually only once.

Because map.keys() returns the same iterator which is already depleted.

Get rid of implicit conversions (at least partially).

Sometimes weird things happen because of the from/to that Observable and State have with plain values. The latter hasn't yet cause problems for me, but the former can lead to some headaches, in particular the @:from T for Observable<T>.

I propose instead to introduce something like:

@:forward
abstract Var<T>(Observable<T>) from Observable<T> to Observable<T> {
  @:from static function ofConst<T>(value:T):Var<T> return Observable.const(value);
}

Migration path would be to add this in the next release and generate warnings when implicit conversion from constants to observables is used, and then remove it relatively soon after. Thoughts?

CC @kevinresol @dmitry-kuzovatkin @gene-pavlovsky

Generate warnings when state is updated within bind.

There should be a way to opt out of it, but generally speaking if changes to observable A trigger an update of state B, then B should be computed from A.

While previously it would cause the scheduler to enter an eager loop, this was now fixed in be5a620. It is now even possible to have cycles such as this one on async platforms:

    var s1 = new State(0),
        s2 = new State(0);
    s1.observe().bind(function (v) s2.set(v + 1));
    s2.observe().bind(function (v) s1.set(v + 1));

It will trash the CPU, but it will not block it. When updates take longer than 10ms, the next batch of updates is scheduled to run later. The above cycle is of course absolute non-sense, but if there's an exit condition, it will be reached eventually, without blowing up everything else in the process.

Reduce allocations.

While benchmarking vs. MobX, I've noticed tink_state taxes the GC quite a bit more. Haven't profiled it yet, but two things seem to stand out as relatively obvious:

  1. Invalidator is the basis of all things and it subscribes every Invalidatable only once. The usage of a CallbackList is therefore not necessary, and the allocation of the CallbackLink can be avoided in favor of a method to unsubscribe an Invalidatable (by identity) again.
  2. The data contained in SubscribtionTo can be inlined into AutoObservable.

Granular observation in Map/Array

As reference, MobX is able to do the following:

const {observable, autorun} = require("mobx");

const map1 = observable.map();

autorun(() => {
	console.log('compute');
    console.log(map1.has('foo'))
})

console.log(0);
map1.set('foo', '1'); // computes
console.log(1);
map1.set('bar', '2'); // no compute because 'bar' key is not involved
console.log(2);
map1.set('foo', '3'); // no compute because existence of 'foo' unchanged
console.log(3);
map1.delete('foo'); // computes
console.log(4);

console.log('==================')

const map2 = observable.map();

autorun(() => {
	console.log('compute');
    console.log(map2.get('foo'));
})

console.log(0);
map2.set('foo', '1'); // computes
console.log(1);
map2.set('bar', '2'); // no compute because 'bar' key is not involved
console.log(2);
map2.set('foo', '3'); // computes
console.log(3);
map2.set('foo', '3'); // no compute because value unchanged
console.log(4);

I think it is done by internally caching the request: https://github.com/mobxjs/mobx/blob/6daafc4f7930f807dd047bae1be55938e95017e5/src/types/observablemap.ts#L115-L131

LinkObject import is missing in the Binding.hx

Hi,

noticed that LinkObject is being used in src/tink/state/internal/Binding.hx

but there is no import from tink.core.Callback.LinkObject,

because of that could not compile my app, is it intentional? and I am missing something or its bug ?

thank you

Add js-compat flag.

To accompany haxetink/tink_core#161,

  • make it so that bind and value are available on all ObservableObject implementors at runtime (and for StateObject there should be a setter for value).
  • make sure Promised is usable
  • the runtime version of auto will be interesting ... perhaps there should simply be an asyncAuto for async computations

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.