diff --git a/.haxerc b/.haxerc index f8137ff..cf76fd6 100644 --- a/.haxerc +++ b/.haxerc @@ -1,4 +1,4 @@ { - "version": "4.2.0", + "version": "4.2.3", "resolveLibs": "scoped" } \ No newline at end of file diff --git a/haxe_libraries/tink_streams.hxml b/haxe_libraries/tink_streams.hxml index 3d33804..0db9043 100644 --- a/haxe_libraries/tink_streams.hxml +++ b/haxe_libraries/tink_streams.hxml @@ -1,6 +1,4 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_streams#5066a96c4a8b483479b6a8df8893eaf8922d3bea" into tink_streams/0.4.0/github/5066a96c4a8b483479b6a8df8893eaf8922d3bea +# @install: lix --silent download "gh://github.com/haxetink/tink_streams#f4478825ef0a30df1187f02a354ec61176b47b8b" into tink_streams/0.3.3/github/f4478825ef0a30df1187f02a354ec61176b47b8b -lib tink_core --cp ${HAXE_LIBCACHE}/tink_streams/0.4.0/github/5066a96c4a8b483479b6a8df8893eaf8922d3bea/src --D tink_streams=0.4.0 -# temp for development, delete this file when pure branch merged --D pure \ No newline at end of file +-cp ${HAXE_LIBCACHE}/tink_streams/0.3.3/github/f4478825ef0a30df1187f02a354ec61176b47b8b/src +-D tink_streams=0.3.3 \ No newline at end of file diff --git a/haxe_libraries/tink_testrunner.hxml b/haxe_libraries/tink_testrunner.hxml index d09dc08..d5a2d6f 100644 --- a/haxe_libraries/tink_testrunner.hxml +++ b/haxe_libraries/tink_testrunner.hxml @@ -1,7 +1,6 @@ -# @install: lix --silent download "gh://github.com/haxetink/tink_testrunner#45f704215ae28c3d864755036dc2ee63f7c44e8a" into tink_testrunner/0.9.0/github/45f704215ae28c3d864755036dc2ee63f7c44e8a +# @install: lix --silent download "gh://github.com/haxetink/tink_testrunner#866de8b991be89b969825b0c0f5565d51f96a6f7" into tink_testrunner/0.8.0/github/866de8b991be89b969825b0c0f5565d51f96a6f7 -lib ansi -lib tink_macro -lib tink_streams --cp ${HAXE_LIBCACHE}/tink_testrunner/0.9.0/github/45f704215ae28c3d864755036dc2ee63f7c44e8a/src --D tink_testrunner=0.9.0 ---macro addGlobalMetadata('ANSI.Attribute', "@:native('ANSIAttribute')", false) \ No newline at end of file +-cp ${HAXE_LIBCACHE}/tink_testrunner/0.8.0/github/866de8b991be89b969825b0c0f5565d51f96a6f7/src +-D tink_testrunner=0.8.0 \ No newline at end of file diff --git a/src/tink/state/Observable.hx b/src/tink/state/Observable.hx index eecd940..00ca905 100644 --- a/src/tink/state/Observable.hx +++ b/src/tink/state/Observable.hx @@ -276,6 +276,9 @@ private class ConstObservable implements ObservableObject { #end } + function retain() {} + function release() {} + public function getValue() return value; diff --git a/src/tink/state/ObservableArray.hx b/src/tink/state/ObservableArray.hx index 2b591bd..f7831ff 100644 --- a/src/tink/state/ObservableArray.hx +++ b/src/tink/state/ObservableArray.hx @@ -23,7 +23,7 @@ abstract ObservableArray(ArrayImpl) from ArrayImpl to Observable this.get(index)); + return Observable.auto(() -> get(index)); @:deprecated('use iterator instead') public function values() @@ -33,7 +33,7 @@ abstract ObservableArray(ArrayImpl) from ArrayImpl to Observable(ArrayView) from ArrayView { public function keys() return 0...this.length; - @:op([]) public inline function get(index) - return this.get(index); + @:op([]) public function get(index) { + return + if (AutoObservable.needsTracking(this)) { + var wrappers = AutoObservable.currentAnnex().get(Wrappers).forSource(this); + + wrappers.get(index, () -> new TransformObservable( + this, + _ -> this.get(index), + null, + () -> wrappers.remove(index) + #if tink_state.debug , () -> 'Entry $index of ${this.toString()}' #end + )).value; + } + else this.get(index); + } public function toArray():Array return this.copy(); @@ -139,12 +152,21 @@ private class ArrayImpl extends Invalidator implements ArrayView { public var length(get, never):Int; function get_length() - return calc(() -> entries.length); + return observableLength.value; public function new(entries) { - super(#if tink_state.debug id -> 'ObservableArray#$id${this.entries.toString()}' #end); + super(#if tink_state.debug id -> 'ObservableArray#$id[${this.entries.toString()}]' #end); this.entries = entries; - this.observableLength = new TransformObservable(this, _ -> this.entries.length, null #if tink_state.debug , () -> 'length of ${toString()}' #end); + this.observableLength = new TransformObservable( + this, + _ -> { + valid = true; + this.entries.length; + }, + null, + null + #if tink_state.debug , () -> 'length of ${this.toString()}' #end + ); } public function replace(values:Array) @@ -192,8 +214,10 @@ private class ArrayImpl extends Invalidator implements ArrayView { public function shift() return update(() -> entries.shift()); - public function get(index:Int) - return calc(() -> entries[index]); + public function get(index:Int) { + valid = true; + return entries[index]; + } public function set(index:Int, value:T) return update(() -> entries[index] = value); @@ -238,11 +262,46 @@ private class ArrayImpl extends Invalidator implements ArrayView { } } +private class Wrappers { + final bySource = new Map<{}, SourceWrappers>(); + + public function new(target:{}) {} + + public function forSource(source:ArrayView):SourceWrappers + return cast switch bySource[source] { + case null: bySource[source] = new SourceWrappers(() -> bySource.remove(source)); + case v: v; + } +} + +private class SourceWrappers { + final dispose:()->Void; + var count = 0; + final observables = new Map>(); + + public function new(dispose) + this.dispose = dispose; + + public function get(index, create:() -> Observable):Observable + return switch observables[index] { + case null: + count++; + observables[index] = create(); + case v: v; + } + + public function remove(index:Int) { + if (observables.remove(index) && (--count == 0)) dispose(); + } +} + private class DerivedView implements ArrayView { + final observableLength:Observable; + public var length(get, never):Int; function get_length() - return o.value.length; + return observableLength.value; final o:Observable>; @@ -252,11 +311,19 @@ private class DerivedView implements ArrayView { public function canFire() return self().canFire(); - public function new(o) + public function new(o) { this.o = o; + this.observableLength = new TransformObservable( + o, + a -> a.length, + null, + null + #if tink_state.debug , () -> 'length of ${toString()}' #end + ); + } public function get(index:Int) - return o.value[index]; + return self().getValue()[index]; inline function self() return (o:ObservableObject>); @@ -297,4 +364,7 @@ private class DerivedView implements ArrayView { public function keyValueIterator() return o.value.keyValueIterator(); + function retain() {} + function release() {} + } \ No newline at end of file diff --git a/src/tink/state/ObservableDate.hx b/src/tink/state/ObservableDate.hx index 5b5c6d4..67efef0 100644 --- a/src/tink/state/ObservableDate.hx +++ b/src/tink/state/ObservableDate.hx @@ -74,4 +74,7 @@ class ObservableDate implements ObservableObject { public function getComparator() return null; + + function retain() {} + function release() {} } diff --git a/src/tink/state/ObservableMap.hx b/src/tink/state/ObservableMap.hx index cffcd61..6f0b060 100644 --- a/src/tink/state/ObservableMap.hx +++ b/src/tink/state/ObservableMap.hx @@ -4,22 +4,33 @@ import haxe.Constraints.IMap; import haxe.iterators.*; @:forward -abstract ObservableMap(MapImpl) from MapImpl to IMap { +@:multiType(@:followWithAbstracts K) +abstract ObservableMap(MapImpl) from MapImpl { public var view(get, never):ObservableMapView; inline function get_view() return this; - public function new(init:Map) - this = new MapImpl(init.copy()); + public function new(); - @:op([]) public inline function get(index) - return this.get(index); + @:op([]) public function get(key) + return + if (AutoObservable.needsTracking(this)) + AutoObservable.currentAnnex().get(Wrappers).forSource(this).get(key).value; + else + this.get(key); @:op([]) public inline function set(index, value) { this.set(index, value); return value; } + public function exists(key) + return + if (AutoObservable.needsTracking(this)) + AutoObservable.currentAnnex().get(Wrappers).forSource(this).exists(key).value; + else + this.exists(key); + public function toMap():Map return view.toMap(); @@ -27,7 +38,23 @@ abstract ObservableMap(MapImpl) from MapImpl to IMap { return view.copy(); public function entry(key:K) - return Observable.auto(this.get.bind(key)); + return Observable.auto(() -> get(key)); + + @:to static function toIntMap(dict:MapImpl):MapImpl + return new MapImpl(new Map(), IntMaps.INST); + + @:to static function toEnumValueMap(dict:MapImpl):MapImpl + return new MapImpl(new Map(), EnumValueMaps.INST); + + @:to static function toStringMap(dict:MapImpl):MapImpl + return new MapImpl(new Map(), StringMaps.INST); + + @:to static function toObjectMap(dict:MapImpl):MapImpl<{}, V> + return new MapImpl<{}, V>(new Map(), ObjectMaps.INST); + + static public inline function of(m:Map):ObservableMap + // This runtime lookup here is messy, but I don't see what else we could do ... + return cast new MapImpl(m.copy(), DynamicFactory.of(m)); } @:forward @@ -39,7 +66,7 @@ abstract ObservableMapView(MapView) from MapView { return cast this.copy(); public function copy():ObservableMap - return new MapImpl(cast this.copy()); + return new MapImpl(cast this.copy(), this.getFactory()); public function entry(key:K) return Observable.auto(this.get.bind(key)); @@ -47,6 +74,7 @@ abstract ObservableMapView(MapView) from MapView { private interface MapView extends ObservableObject> { function copy():IMap; + function getFactory():MapFactory; function exists(key:K):Bool; function get(key:K):Null; function iterator():Iterator; @@ -54,75 +82,21 @@ private interface MapView extends ObservableObject> { function keyValueIterator():KeyValueIterator; } -private class Derived implements MapView { - final o:Observable>; - public function new(o) - this.o = o; - - public function canFire() - return self().canFire(); - - public function getRevision() - return self().getRevision(); - - public function exists(key:K):Bool - return o.value.exists(key); - - public function get(key:K):Null - return o.value.get(key); - - public function iterator():Iterator - return o.value.iterator(); - - public function keys():Iterator - return o.value.keys(); - - public function keyValueIterator():KeyValueIterator - return o.value.keyValueIterator(); - - public function copy():IMap - return cast o.value.copy(); - - inline function self() - return (o:ObservableObject>); - - public function getValue() - return this; - - public function isValid() - return self().isValid(); - - public function onInvalidate(i) - return self().onInvalidate(i); - - function neverEqual(a, b) - return false; - - public function getComparator() - return neverEqual; - - #if tink_state.debug - public function getObservers() - return self().getObservers(); - - public function getDependencies() - return self().getDependencies(); - - @:keep public function toString() - return 'ObservableMapView#${o.value.toString()}'; - #end -} - private class MapImpl extends Invalidator implements MapView implements IMap { var valid = false; - var entries:Map; + final entries:Map; + final factory:MapFactory; - public function new(entries:Map) { + public function new(entries, factory) { super(); this.entries = entries; + this.factory = factory; } + public function getFactory() + return factory; + public function observe():Observable> return this; @@ -132,14 +106,18 @@ private class MapImpl extends Invalidator implements MapView impleme public function getValue():MapView return this; - public function get(k:K):Null - return calc(() -> entries.get(k)); + public function get(k:K):Null { + valid = true; + return entries.get(k); + } public function set(k:K, v:V):Void update(() -> { entries.set(k, v); null; }); - public function exists(k:K):Bool - return calc(() -> entries.exists(k)); + public function exists(k:K):Bool { + valid = true; + return entries.exists(k); + } public function remove(k:K):Bool return update(() -> entries.remove(k)); @@ -190,4 +168,105 @@ private class MapImpl extends Invalidator implements MapView impleme public function getDependencies() return EmptyIterator.DEPENDENCIES; #end +} + +private interface MapFactory { + function createMap():Map; +} + +private class IntMaps implements MapFactory { + static public final INST = new IntMaps(); + function new() {} + public function createMap():Map + return new Map(); +} + +private class StringMaps implements MapFactory { + static public final INST = new StringMaps(); + function new() {} + public function createMap():Map + return new Map(); +} + +private class ObjectMaps implements MapFactory<{}> { + static public final INST = new ObjectMaps(); + function new() {} + public function createMap():Map<{}, X> + return new Map(); +} + +private class EnumValueMaps implements MapFactory { + static public final INST = new EnumValueMaps(); + function new() {} + public function createMap():Map + return new Map(); +} + +private class DynamicFactory { + static public function of(m:Map):MapFactory { + var cl:Class = Type.getClass(m); + return + if (cl == haxe.ds.IntMap) cast IntMaps.INST; + else if (cl == haxe.ds.StringMap) cast StringMaps.INST; + else if (cl == haxe.ds.EnumValueMap) cast EnumValueMaps.INST; + else cast ObjectMaps.INST; + } +} + +private class Wrappers { + final bySource = new Map<{}, SourceWrappers>(); + + public function new(target:{}) {} + + public function forSource(source:MapView):SourceWrappers + return cast switch bySource[source] { + case null: bySource[source] = new SourceWrappers(source, () -> bySource.remove(source)); + case v: v; + } +} + +private class SourceWrappers {// TODO: it's probably better to split this in two + final dispose:()->Void; + final source:MapView; + final entries:Map>; + final existences:Map>; + + var count = 0; + + public function new(source, dispose) { + this.source = source; + this.dispose = dispose; + var factory = source.getFactory(); + this.entries = factory.createMap(); + this.existences = factory.createMap(); + } + + public function get(key) + return switch entries[key] { + case null: + count++; + entries[key] = new TransformObservable( + source, + o -> o.get(key), + null, + () -> if (entries.remove(key) && (--count == 0)) dispose() + #if tink_state.debug , () -> 'Entry for $key in ${source.toString()}' #end + ); + case v: v; + } + + public function exists(key) + return switch existences[key] { + case null: + count++; + existences[key] = new TransformObservable( + source, + o -> o.exists(key), + null, + () -> if (existences.remove(key) && (--count == 0)) dispose() + #if tink_state.debug , () -> 'Existence of $key in ${source.toString()}' #end + + ); + case v: v; + } } \ No newline at end of file diff --git a/src/tink/state/State.hx b/src/tink/state/State.hx index 91fdc69..b5a1527 100644 --- a/src/tink/state/State.hx +++ b/src/tink/state/State.hx @@ -67,19 +67,28 @@ private class CompoundState implements StateObject { public function getValue() return data.getValue(); - public function onInvalidate(i) - return data.onInvalidate(i); - #if tink_state.debug - public function getObservers() - return data.getObservers();//TODO: this is not very exact - - public function getDependencies() - return [(cast data:Observable)].iterator(); - - @:keep public function toString() - return 'CompoundState[${data.toString()}]';//TODO: perhaps this should be providable from outside - + final observers = new ObjectMap(); + + public function onInvalidate(i) + return switch observers[i] { + case null: + observers[i] = i; + data.onInvalidate(i) & () -> observers.remove(i); + default: null; + } + + public function getObservers() + return observers.iterator(); + + public function getDependencies() + return [(cast data:Observable)].iterator(); + + @:keep public function toString() + return 'CompoundState[${data.toString()}]';//TODO: perhaps this should be providable from outside + #else + public function onInvalidate(i) + return data.onInvalidate(i); #end public function set(value) { @@ -89,6 +98,9 @@ private class CompoundState implements StateObject { public function getComparator() return this.comparator; + + function retain() {} + function release() {} } private class GuardedState extends SimpleState { diff --git a/src/tink/state/internal/AutoObservable.hx b/src/tink/state/internal/AutoObservable.hx index 71ea0d2..03965fd 100644 --- a/src/tink/state/internal/AutoObservable.hx +++ b/src/tink/state/internal/AutoObservable.hx @@ -3,6 +3,7 @@ package tink.state.internal; #if tink_state.debug import tink.state.debug.Logger.inst as logger; #end +import tink.core.Annex; @:callable @:access(tink.state.internal.AutoObservable) @@ -78,19 +79,17 @@ private class SubscriptionTo { public var used = true; - public function new(source, cur, owner:AutoObservable) { + public function new(source, cur, owner) { this.source = source; this.last = cur; this.lastRev = source.getRevision(); this.owner = owner; - - if (owner.hot) connect(); } public inline function isValid() return source.getRevision() == lastRev; - public inline function hasChanged():Bool { + public function hasChanged():Bool { var nextRev = source.getRevision(); if (nextRev == lastRev) return false; lastRev = nextRev; @@ -137,6 +136,7 @@ class AutoObservable extends Invalidator } #end public var hot(default, null) = false; + final annex:Annex<{}>; var status = Dirty; var last:T = null; var subscriptions:Array; @@ -157,6 +157,9 @@ class AutoObservable extends Invalidator return revision; } + public function getAnnex() + return annex; + function subsValid() { if (subscriptions == null) return false; @@ -180,6 +183,7 @@ class AutoObservable extends Invalidator this.comparator = comparator; this.list.onfill = () -> inline heatup(); this.list.ondrain = () -> inline cooldown(); + this.annex = new Annex<{}>(this); } function heatup() { @@ -210,6 +214,19 @@ class AutoObservable extends Invalidator static public inline function untracked(fn:()->T) return computeFor(null, fn); + + static public inline function needsTracking(o:ObservableObject):Bool + return switch cur { + case null: false; + case v: !v.isSubscribedTo(o); + } + + static public function currentAnnex() + return switch cur { + case null: null; + case v: v.getAnnex(); + } + static public inline function track(o:ObservableObject):V { var ret = o.getValue(); if (cur != null && o.canFire()) @@ -264,6 +281,7 @@ class AutoObservable extends Invalidator if (!s.used) { if (hot) s.disconnect(); dependencies.remove(s.source); + s.source.release(); #if tink_state.debug logger.unsubscribed(s.source, this); #end @@ -290,6 +308,8 @@ class AutoObservable extends Invalidator logger.subscribed(source, this); #end var sub:Subscription = cast new SubscriptionTo(source, cur, this); + source.retain(); + if (hot) sub.connect(); dependencies.set(source, sub); subscriptions.push(sub); case v: @@ -299,6 +319,12 @@ class AutoObservable extends Invalidator } } + public function isSubscribedTo(source:ObservableObject) + return switch dependencies.get(source) { + case null: false; + case s: s.used; + } + public function invalidate() if (status == Computed) { status = Dirty; @@ -312,5 +338,7 @@ class AutoObservable extends Invalidator } private interface Derived { + function getAnnex():Annex<{}>; + function isSubscribedTo(source:ObservableObject):Bool; function subscribeTo(source:ObservableObject, cur:R):Void; } \ No newline at end of file diff --git a/src/tink/state/internal/Invalidatable.hx b/src/tink/state/internal/Invalidatable.hx index f298c5c..b1f8c24 100644 --- a/src/tink/state/internal/Invalidatable.hx +++ b/src/tink/state/internal/Invalidatable.hx @@ -11,7 +11,7 @@ interface Invalidatable { class Invalidator implements OwnedDisposable { var revision = new Revision(); - final observers = new ObjectMap(); + final observers = new ObjectMap(); final list = new CallbackList();//TODO: get rid of the list ... currently primarily here to guarantee stable callback order #if tink_state.debug static var counter = 0; @@ -37,6 +37,8 @@ class Invalidator implements OwnedDisposable { public function ondispose(d:()->Void) list.ondispose(d); + function retain() {} + function release() {} public inline function dispose() { list.dispose(); @@ -51,9 +53,9 @@ class Invalidator implements OwnedDisposable { public function onInvalidate(i:Invalidatable):CallbackLink return - if (observers.get(i) || list.disposed) null; + if (observers.exists(i) || list.disposed) null; else { - observers.set(i, true); + observers[i] = i; list.add( #if tink_state.debug _ -> { @@ -69,7 +71,7 @@ class Invalidator implements OwnedDisposable { #if tink_state.debug public function getObservers() - return observers.keys(); + return observers.iterator(); #end function fire() { diff --git a/src/tink/state/internal/ObjectMap.hx b/src/tink/state/internal/ObjectMap.hx index eb8ec2b..ebd064b 100644 --- a/src/tink/state/internal/ObjectMap.hx +++ b/src/tink/state/internal/ObjectMap.hx @@ -1,6 +1,6 @@ package tink.state.internal; -@:forward(keys, exists, clear) +@:forward(keys, iterator, exists, clear) abstract ObjectMap(haxe.ds.ObjectMap) { public inline function new() this = new haxe.ds.ObjectMap(); diff --git a/src/tink/state/internal/ObjectMap.js.hx b/src/tink/state/internal/ObjectMap.js.hx index b79979c..b9891f4 100644 --- a/src/tink/state/internal/ObjectMap.js.hx +++ b/src/tink/state/internal/ObjectMap.js.hx @@ -22,9 +22,18 @@ abstract ObjectMap(Map) { return try new HaxeIterator(this.keys()) catch (e:Dynamic) {// because IE11 - var keys = []; - forEach((_, k, _) -> keys.push(k)); - keys.iterator(); + var ret = []; + forEach((_, k, _) -> ret.push(k)); + ret.iterator(); + } + + public function iterator():Iterator + return + try new HaxeIterator(this.values()) + catch (e:Dynamic) {// because IE11 + var ret = []; + forEach((v, _, _) -> ret.push(v)); + ret.iterator(); } public inline function remove(key) diff --git a/src/tink/state/internal/ObservableObject.hx b/src/tink/state/internal/ObservableObject.hx index c388ecd..cabca85 100644 --- a/src/tink/state/internal/ObservableObject.hx +++ b/src/tink/state/internal/ObservableObject.hx @@ -1,6 +1,9 @@ package tink.state.internal; +@:allow(tink.state.internal) interface ObservableObject { + private function retain():Void; + private function release():Void; function getValue():T; function getRevision():Revision; function isValid():Bool; diff --git a/src/tink/state/internal/SignalObservable.hx b/src/tink/state/internal/SignalObservable.hx index 62a4afd..7c9ae75 100644 --- a/src/tink/state/internal/SignalObservable.hx +++ b/src/tink/state/internal/SignalObservable.hx @@ -53,6 +53,9 @@ class SignalObservable implements ObservableObject { public function getComparator():Comparator return null; + function retain() {} + function release() {} + public function onInvalidate(i:Invalidatable):CallbackLink // TODO: this largely duplicates Invalidatable.onInvalidate return diff --git a/src/tink/state/internal/TransformObservable.hx b/src/tink/state/internal/TransformObservable.hx index 5d87377..5d7e74a 100644 --- a/src/tink/state/internal/TransformObservable.hx +++ b/src/tink/state/internal/TransformObservable.hx @@ -7,14 +7,19 @@ class TransformObservable implements ObservableObject { final transform:Transform; final source:ObservableObject; final comparator:Comparator; + var dispose:()->Void; #if tink_state.debug final _toString:()->String; #end - public function new(source, transform, ?comparator #if tink_state.debug , toString #end) { + public function new(source, transform, ?comparator, ?dispose #if tink_state.debug , toString #end) { this.source = source; this.transform = transform; this.comparator = comparator; + this.dispose = switch dispose { + case null: noop; + case v: v; + } #if tink_state.debug this._toString = toString; #end @@ -26,18 +31,28 @@ class TransformObservable implements ObservableObject { public function isValid() return lastSeenRevision == source.getRevision(); - public function onInvalidate(i) - return source.onInvalidate(i); - #if tink_state.debug - public function getObservers() - return source.getObservers(); + final observers = new ObjectMap(); + + public function onInvalidate(i) + return switch observers[i] { + case null: + observers[i] = i; + source.onInvalidate(i) & () -> observers.remove(i); + default: null; + } - public function getDependencies() - return [source].iterator(); + public function getObservers() + return observers.iterator(); - public function toString():String - return _toString(); + public function getDependencies() + return [cast source].iterator(); + + public function toString():String + return _toString(); + #else + public function onInvalidate(i) + return source.onInvalidate(i); #end public function getValue() { @@ -54,4 +69,11 @@ class TransformObservable implements ObservableObject { public function canFire():Bool return source.canFire(); + + var retainCount = 0; + function retain() retainCount++; + function release() + if (--retainCount == 0) dispose(); + + static function noop() {} } \ No newline at end of file diff --git a/tests/TestArrays.hx b/tests/TestArrays.hx index 533b7cb..b69a3ce 100644 --- a/tests/TestArrays.hx +++ b/tests/TestArrays.hx @@ -22,7 +22,7 @@ class TestArrays { function getLog() return log.join(',').replace('undefined', '-').replace('null', '-'); - function report(name:String) return (v:Null) -> log.push('$name:$v'); + function report(name:String) { return (v:Null) -> log.push('$name:$v'); } Observable.auto(() -> a.length).bind(report('l'), direct); @@ -64,6 +64,38 @@ class TestArrays { return asserts.done(); } + public function issue49() { + final arr = new ObservableArray([for (i in 0...10) i]); + + var computations = 0; + + final sum = Observable.auto(() -> { + computations++; + arr[2] + arr[5]; + }); + + function checkSum(?pos:haxe.PosInfos) + asserts.assert(sum.value == arr[2] + arr[5], null, pos); + + checkSum(); + asserts.assert(computations == 1); + + checkSum(); + asserts.assert(computations == 1); + + arr[1] = 0; + checkSum(); + asserts.assert(computations == 1); + asserts.assert(arr[1] == 0); + + arr[2] = 123; + checkSum(); + asserts.assert(computations == 2); + + + return asserts.done(); + } + public function iteration() { var counter = 0, a = new ObservableArray(); diff --git a/tests/TestMaps.hx b/tests/TestMaps.hx index 7e3e085..d6191d7 100644 --- a/tests/TestMaps.hx +++ b/tests/TestMaps.hx @@ -1,5 +1,6 @@ package ; +import tink.state.internal.ObjectMap; import tink.state.Scheduler.direct; import tink.state.*; @@ -11,7 +12,7 @@ class TestMaps { public function new() {} public function testEntries() { - final o = new ObservableMap([5 => 0, 6 => 0]); + final o = ObservableMap.of([5 => 0, 6 => 0]); var a = []; @@ -74,7 +75,7 @@ class TestMaps { } public function testIterators() { - final map = new ObservableMap(new Map()); + final map = new ObservableMap(); map.set('key', 'value'); var count = 0; @@ -111,4 +112,59 @@ class TestMaps { return asserts.done(); } + + public function of() { + ObservableMap.of(new haxe.ds.IntMap()).set(1, 'foo'); + ObservableMap.of(new haxe.ds.StringMap()).set('1', 'foo'); + ObservableMap.of([{ foo: 213 } => '123']).set({ foo: 123 }, 'foo'); + + return asserts.done(); + } + + public function issue49() { + var o = ObservableMap.of([1 => 2]), + computations = 0; + + + final sum = Observable.auto(() -> { + computations++; + var ret = 0; + if (o.exists(2)) ret += o[2]; + if (o.exists(3)) ret += o[3]; + return ret; + }); + + asserts.assert(sum.value == 0); + asserts.assert(computations == 1); + + asserts.assert(sum.value == 0); + asserts.assert(computations == 1); + + o[5] = 5; + + asserts.assert(sum.value == 0); + asserts.assert(computations == 1); + + o[2] = 2; + + asserts.assert(sum.value == 2); + asserts.assert(computations == 2); + + o[3] = 3; + + asserts.assert(sum.value == 5); + asserts.assert(computations == 3); + + o[4] = 4; + o.remove(5); + + asserts.assert(sum.value == 5); + asserts.assert(computations == 3); + + o.remove(2); + asserts.assert(sum.value == 3); + asserts.assert(computations == 4); + + return asserts.done(); + } } \ No newline at end of file diff --git a/tests/issues/Issue51.hx b/tests/issues/Issue51.hx index b924726..5102e26 100644 --- a/tests/issues/Issue51.hx +++ b/tests/issues/Issue51.hx @@ -9,7 +9,7 @@ class Issue51 { public function new() {} public function testNested() { - final baseMap = new ObservableMap([]); + final baseMap = new ObservableMap(); function query(key:String) { final entityQueries = { @@ -75,7 +75,7 @@ class Issue51 { private class Entity { public final id:Int; - public final subMap:ObservableMap = new ObservableMap([]); + public final subMap:ObservableMap = new ObservableMap(); public function new(id) { this.id = id; @@ -85,4 +85,3 @@ private class Entity { return '$id'; } } - \ No newline at end of file