Raynos / observ-array

An array containing observable values
MIT License
26 stars 4 forks source link

Same observable in multiple ObservArrays can result in inconsistent state #21

Open jamesamcl opened 8 years ago

jamesamcl commented 8 years ago

Best explained by an example:

var Observ = require('observ')
var ObservStruct = require('observ-struct')
var ObservArray = require('observ-array')

var instance = ObservStruct({
    x: Observ(0),
    y: Observ(0)
})

var state = ObservStruct({

    arr1: ObservArray([ instance ]),
    arr2: ObservArray([ instance ])

})

state.arr1(function() {

    if(instance.x() !== 1) {

        console.trace('arr1 has changed and x is not 1 yet.  setting x to 1')
        instance.x.set(1)

    } else {

        console.trace('arr1 has changed and x is already 1')
    }

})

state.arr2(function() {

    if(instance.y() !== 1) {

        console.trace('arr2 has changed and y is not 1 yet.  setting y to 1')
        instance.y.set(1)

    } else {

        console.trace('arr2 has changed and y is already 1')
    }
})

/* fire off the observables for both arrays
 */
instance.x.set(0)

console.log(state.arr1())  // [ { x: 1, y: 1 } ]
console.log(state.arr2())  // [ { x: 0, y: 0 } ]

Output:

Trace: arr1 has changed and x is not 1 yet.  setting x to 1
    at /usr/home/james/observ-array-test/test.js:22:21
    at /usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Function.Observable.observable.set [as _observSet] (/usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:9:19)
    at /usr/home/james/observ-array-test/node_modules/observ-array/add-listener.js:26:21
    at /usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Observable.observable.set (/usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:9:19)
    at Function.trackDiff [as set] (/usr/home/james/observ-array-test/node_modules/observ-struct/index.js:77:20)
    at /usr/home/james/observ-array-test/node_modules/observ-struct/index.js:69:21
Trace: arr1 has changed and x is already 1
    at /usr/home/james/observ-array-test/test.js:27:21
    at /usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Function.Observable.observable.set [as _observSet] (/usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:9:19)
    at /usr/home/james/observ-array-test/node_modules/observ-array/add-listener.js:26:21
    at /usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Observable.observable.set (/usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:9:19)
    at Function.trackDiff [as set] (/usr/home/james/observ-array-test/node_modules/observ-struct/index.js:77:20)
    at /usr/home/james/observ-array-test/node_modules/observ-struct/index.js:69:21
Trace: arr2 has changed and y is not 1 yet.  setting y to 1
    at /usr/home/james/observ-array-test/test.js:36:21
    at /usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Function.Observable.observable.set [as _observSet] (/usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:9:19)
    at /usr/home/james/observ-array-test/node_modules/observ-array/add-listener.js:26:21
    at /usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Observable.observable.set (/usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:9:19)
    at Function.trackDiff [as set] (/usr/home/james/observ-array-test/node_modules/observ-struct/index.js:77:20)
    at /usr/home/james/observ-array-test/node_modules/observ-struct/index.js:69:21
Trace: arr1 has changed and x is already 1
    at /usr/home/james/observ-array-test/test.js:27:21
    at /usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Function.Observable.observable.set [as _observSet] (/usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:9:19)
    at /usr/home/james/observ-array-test/node_modules/observ-array/add-listener.js:26:21
    at /usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Observable.observable.set (/usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:9:19)
    at Function.trackDiff [as set] (/usr/home/james/observ-array-test/node_modules/observ-struct/index.js:77:20)
    at /usr/home/james/observ-array-test/node_modules/observ-struct/index.js:69:21
Trace: arr2 has changed and y is already 1
    at /usr/home/james/observ-array-test/test.js:41:21
    at /usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Function.Observable.observable.set [as _observSet] (/usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:9:19)
    at /usr/home/james/observ-array-test/node_modules/observ-array/add-listener.js:26:21
    at /usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Observable.observable.set (/usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:9:19)
    at Function.trackDiff [as set] (/usr/home/james/observ-array-test/node_modules/observ-struct/index.js:77:20)
    at /usr/home/james/observ-array-test/node_modules/observ-struct/index.js:69:21
Trace: arr2 has changed and y is already 1
    at /usr/home/james/observ-array-test/test.js:41:21
    at /usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Function.Observable.observable.set [as _observSet] (/usr/home/james/observ-array-test/node_modules/observ-array/node_modules/observ/index.js:9:19)
    at /usr/home/james/observ-array-test/node_modules/observ-array/add-listener.js:26:21
    at /usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:10:13
    at Array.forEach (native)
    at Observable.observable.set (/usr/home/james/observ-array-test/node_modules/observ-struct/node_modules/observ/index.js:9:19)
    at Function.trackDiff [as set] (/usr/home/james/observ-array-test/node_modules/observ-struct/index.js:77:20)
    at /usr/home/james/observ-array-test/node_modules/observ-struct/index.js:69:21
[ { x: 1, y: 1 } ]
[ { x: 0, y: 0 } ]

Once x -> 0 happens, the state is further mutated by the callback, but after this the first mutation is emitted to arr2, which stores the old state back in the array. Consequently both arrays contain the same observable, but track a different version of it.

Not really sure how to fix this. Maybe some kind of transaction ID that gets incremented for each change to prevent older copies of the state overwriting newer ones?

jamesamcl commented 8 years ago

Currently thinking something like

obs(function(obj, versionID) {
    if(versionID > cachedVersionID) {
        // update and emit

for array and struct. Then increment the versionID and emit for each set on an Observ.

jamesamcl commented 8 years ago

(Using something appropriate for versionID that can't overflow.)