Skip to content

Commit 76d29fb

Browse files
committed
All tests passing for reactor refactor
1 parent c40e99d commit 76d29fb

File tree

8 files changed

+127
-123
lines changed

8 files changed

+127
-123
lines changed

src/key-path.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Immutable from 'immutable'
12
var isArray = require('./utils').isArray
23
var isFunction = require('./utils').isFunction
34

@@ -12,3 +13,16 @@ exports.isKeyPath = function(toTest) {
1213
!isFunction(toTest[toTest.length - 1])
1314
)
1415
}
16+
17+
/**
18+
* Checks if two keypaths are equal by value
19+
* @param {KeyPath} a
20+
* @param {KeyPath} a
21+
* @return {Boolean}
22+
*/
23+
exports.isEqual = function(a, b) {
24+
const iA = Immutable.List(a)
25+
const iB = Immutable.List(b)
26+
27+
return Immutable.is(iA, iB)
28+
}

src/reactor.js

Lines changed: 27 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import { toJS } from './immutable-helpers'
77
import { isArray, toFactory } from './utils'
88
import { ReactorState, ObserverStoreMap } from './reactor/records'
99

10-
// keep a singleton app state identity getter so we can leverage by reference keying
11-
const APP_STATE_IDENTITY_GETTER = [[], x => x]
1210
/**
1311
* State is stored in NuclearJS Reactors. Reactors
1412
* contain a 'state' object which is an Immutable.Map
@@ -19,12 +17,7 @@ const APP_STATE_IDENTITY_GETTER = [[], x => x]
1917
* state based on the message
2018
*/
2119
class Reactor {
22-
constructor(config) {
23-
if (!(this instanceof Reactor)) {
24-
return new Reactor(config)
25-
}
26-
config = config || {}
27-
20+
constructor(config = {}) {
2821
var initialReactorState = new ReactorState({
2922
debug: config.debug
3023
})
@@ -37,8 +30,6 @@ class Reactor {
3730

3831
// keep track of the depth of batch nesting
3932
this.__batchDepth = 0
40-
// number of dispatches in the top most batch cycle
41-
this.__batchDispatchCount = 0
4233

4334
// keep track if we are currently dispatching
4435
this.__isDispatching = false
@@ -83,20 +74,12 @@ class Reactor {
8374
observe(getter, handler) {
8475
if (arguments.length === 1) {
8576
handler = getter
86-
getter = APP_STATE_IDENTITY_GETTER
87-
}
88-
// by value check if getter === []
89-
if (isArray(getter) && getter.length === 0) {
90-
getter = APP_STATE_IDENTITY_GETTER
77+
getter = []
9178
}
92-
// TODO dont mutate getter to allow by reference unobservation of keypaths (since they are getting transformed into identity getter)
93-
let { observerStoreMap, unwatchEntry } = fns.addObserver(this.observerStoreMap, getter, handler)
79+
let { observerStoreMap, entry } = fns.addObserver(this.observerStoreMap, getter, handler)
9480
this.observerStoreMap = observerStoreMap;
9581
return () => {
96-
this.unobserve(
97-
unwatchEntry.get('getter'),
98-
unwatchEntry.get('hander')
99-
)
82+
this.observerStoreMap = fns.removeObserverByEntry(this.observerStoreMap, entry)
10083
}
10184
}
10285

@@ -108,11 +91,6 @@ class Reactor {
10891
throw new Error('Must call unobserve with a Getter')
10992
}
11093

111-
// by value check if getter === []
112-
if (isArray(getter) && getter.length === 0) {
113-
getter = APP_STATE_IDENTITY_GETTER
114-
}
115-
11694
this.observerStoreMap = fns.removeObserver(this.observerStoreMap, getter, handler)
11795
}
11896

@@ -137,14 +115,9 @@ class Reactor {
137115
throw e
138116
}
139117

140-
if (this.__batchDepth > 0) {
141-
this.__batchDispatchCount++
142-
}
143-
144118
try {
145119
this.__notify()
146120
} finally {
147-
console.log('DISPATCHING FALSE')
148121
this.__isDispatching = false
149122
}
150123
}
@@ -154,9 +127,9 @@ class Reactor {
154127
* @param {Function} fn
155128
*/
156129
batch(fn) {
157-
this.__batchStart()
130+
this.batchStart()
158131
fn()
159-
this.__batchEnd()
132+
this.batchEnd()
160133
}
161134

162135
/**
@@ -188,7 +161,7 @@ class Reactor {
188161
* @return {Object}
189162
*/
190163
serialize() {
191-
return this.reactorState.serialize()
164+
return fns.serialize(this.reactorState)
192165
}
193166

194167
/**
@@ -217,30 +190,22 @@ class Reactor {
217190
// in the middle of batch, dont notify
218191
return
219192
}
220-
const dirtyStores = this.reactorState.get('dirtyStores')
221193

194+
const dirtyStores = this.reactorState.get('dirtyStores')
222195
if (dirtyStores.size === 0) {
223196
return
224197
}
225198

226199
let observerIdsToNotify = Immutable.Set().withMutations(set => {
227200
// notify all observers
228-
this.observerStoreMap.get('any').forEach(handlerMap => {
229-
handlerMap.forEach((observerId) => {
230-
set.add(observerId);
231-
})
232-
})
201+
set.union(this.observerStoreMap.get('any'))
233202

234203
dirtyStores.forEach(id => {
235204
const entries = this.observerStoreMap.getIn(['stores', id])
236205
if (!entries) {
237206
return
238207
}
239-
entries.forEach((handlerMap, getter) => {
240-
handlerMap.forEach((observerId) => {
241-
set.add(observerId)
242-
})
243-
})
208+
set.union(entries)
244209
})
245210
})
246211

@@ -271,26 +236,31 @@ class Reactor {
271236
this.reactorState = nextReactorState
272237
}
273238

274-
__batchStart() {
239+
/**
240+
* Starts batching, ie pausing notifies and batching up changes
241+
* to be notified when batchEnd() is called
242+
*/
243+
batchStart() {
275244
this.__batchDepth++
276245
}
277246

278-
__batchEnd() {
247+
/**
248+
* Ends a batch cycle and will notify obsevers of all changes if
249+
* the batch depth is back to 0 (outer most batch completed)
250+
*/
251+
batchEnd() {
279252
this.__batchDepth--
280253

281254
if (this.__batchDepth <= 0) {
282-
if (this.__batchDispatchCount > 0) {
283-
// set to true to catch if dispatch called from observer
284-
this.__isDispatching = true
285-
try {
286-
this.__notify()
287-
} catch (e) {
288-
this.__isDispatching = false
289-
throw e
290-
}
255+
// set to true to catch if dispatch called from observer
256+
this.__isDispatching = true
257+
try {
258+
this.__notify()
259+
} catch (e) {
291260
this.__isDispatching = false
261+
throw e
292262
}
293-
this.__batchDispatchCount = 0
263+
this.__isDispatching = false
294264
}
295265
}
296266
}

src/reactor/fns.js

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import logging from '../logging'
33
import { isImmutableValue } from '../immutable-helpers'
44
import { toImmutable } from '../immutable-helpers'
55
import { fromKeyPath, getStoreDeps, getComputeFn, getDeps, isGetter } from '../getter'
6-
import { isKeyPath } from '../key-path'
6+
import { isEqual, isKeyPath } from '../key-path'
77
import { each } from '../utils'
8-
import isEqual from '../is-equal'
98

109
/**
1110
* Immutable Types
@@ -96,20 +95,24 @@ exports.dispatch = function(reactorState, actionType, payload) {
9695
* @return {ReactorState}
9796
*/
9897
exports.loadState = function(reactorState, state) {
99-
var stateToLoad = toImmutable({}).withMutations(stateToLoad => {
98+
let dirtyStores = []
99+
const stateToLoad = toImmutable({}).withMutations(stateToLoad => {
100100
each(state, (serializedStoreState, storeId) => {
101101
var store = reactorState.getIn(['stores', storeId])
102102
if (store) {
103103
var storeState = store.deserialize(serializedStoreState)
104104
if (storeState !== undefined) {
105105
stateToLoad.set(storeId, storeState)
106+
dirtyStores.push(storeId)
106107
}
107108
}
108109
})
109110
})
110111

111-
var newState = reactorState.get('state').merge(stateToLoad)
112-
return reactorState.set('state', newState)
112+
var dirtyStoresSet = Immutable.Set(dirtyStores)
113+
return reactorState
114+
.update('state', state => state.merge(stateToLoad))
115+
.update('dirtyStores', stores => stores.union(dirtyStoresSet))
113116
}
114117

115118
/**
@@ -171,33 +174,35 @@ exports.addObserver = function(observerStoreMap, getter, handler) {
171174
const currId = observerStoreMap.get('nextId')
172175
const storeDeps = getStoreDeps(getter)
173176
const entry = Immutable.Map({
177+
id: currId,
174178
storeDeps: storeDeps,
175179
getterKey: getterKey,
176180
getter: getter,
177181
handler: handler,
178-
unwatched: false,
179182
})
180183

181184
let updatedObserverStoreMap
182185
if (storeDeps.size === 0) {
183-
updatedObserverStoreMap = observerStoreMap.setIn(['any', getterKey, handler], currId)
186+
updatedObserverStoreMap = observerStoreMap.update('any', observerIds => observerIds.add(currId))
184187
} else {
185188
updatedObserverStoreMap = observerStoreMap.withMutations(map => {
186189
storeDeps.forEach(storeId => {
187-
map.setIn(['stores', storeId, getterKey, handler], currId);
190+
let path = ['stores', storeId]
191+
if (!map.hasIn(path)) {
192+
map.setIn(path, Immutable.Set([]))
193+
}
194+
map.updateIn(['stores', storeId], observerIds => observerIds.add(currId));
188195
})
189196
})
190197
}
191198

192199
updatedObserverStoreMap = updatedObserverStoreMap
193200
.set('nextId', currId + 1)
194-
.update('observersMap', entries => {
195-
return entries.set(currId, entry)
196-
})
201+
.setIn(['observersMap', currId], entry)
197202

198203
return {
199204
observerStoreMap: updatedObserverStoreMap,
200-
unwatchEntry: entry,
205+
entry: entry,
201206
}
202207
}
203208

@@ -211,43 +216,47 @@ exports.addObserver = function(observerStoreMap, getter, handler) {
211216
* removeObserver(observerStoreMap, getter, handler)
212217
* @param {ObserverStoreMap} observerStoreMap
213218
* @param {KeyPath|Getter} getter
214-
* @param {function} handler
219+
* @param {Function} handler
215220
* @return {ObserverStoreMap}
216221
*/
217222
exports.removeObserver = function(observerStoreMap, getter, handler) {
218-
debugger;
219-
const getterKey = getter
220-
if (isKeyPath(getter)) {
221-
getter = fromKeyPath(getter)
222-
}
223-
const storeDeps = getStoreDeps(getter)
224-
let pathsToRemove = []
225-
226-
if (storeDeps.size === 0) {
227-
let path = ['any', getterKey]
228-
if (handler) {
229-
path.push(handler)
223+
const entriesToRemove = observerStoreMap.get('observersMap').filter(entry => {
224+
// use the getterKey in the case of a keyPath is transformed to a getter in addObserver
225+
let entryGetter = entry.get('getterKey')
226+
let handlersMatch = (!handler || entry.get('handler') === handler)
227+
if (!handlersMatch) {
228+
return false
230229
}
231-
pathsToRemove.push(path)
232-
} else {
233-
storeDeps.forEach(storeId => {
234-
let path = ['stores', storeId, getterKey]
235-
if (handler) {
236-
path.push(handler)
237-
}
238-
pathsToRemove.push(path)
239-
})
240-
}
230+
// check for a by-value equality of keypaths
231+
if (isKeyPath(getter) && isKeyPath(entryGetter)) {
232+
return isEqual(getter, entryGetter)
233+
}
234+
// we are comparing two getters do it by reference
235+
return (getter === entryGetter)
236+
})
241237

242238
return observerStoreMap.withMutations(map => {
243-
pathsToRemove.forEach(path => {
244-
var observerId = observerStoreMap.getIn(path)
239+
entriesToRemove.forEach(entry => exports.removeObserverByEntry(map, entry))
240+
})
241+
}
245242

246-
map.removeIn(path)
247-
if (observerId) {
248-
map.removeIn(['observersMap', observerId])
249-
}
250-
})
243+
/**
244+
* Removes an observer entry by id from the observerStoreMap
245+
*/
246+
exports.removeObserverByEntry = function(observerStoreMap, entry) {
247+
return observerStoreMap.withMutations(map => {
248+
const id = entry.get('id')
249+
const storeDeps = entry.get('storeDeps')
250+
251+
if (storeDeps.size === 0) {
252+
map.update('any', anyObsevers => anyObsevers.remove(id))
253+
} else {
254+
storeDeps.forEach(storeId => {
255+
map.updateIn(['stores', storeId], observers => observers.remove(id))
256+
})
257+
}
258+
259+
map.removeIn(['observersMap', id])
251260
})
252261
}
253262

@@ -334,6 +343,23 @@ exports.evaluate = function evaluate(reactorState, keyPathOrGetter) {
334343
)
335344
}
336345

346+
/**
347+
* Returns serialized state for all stores
348+
* @param {ReactorState} reactorState
349+
* @return {Object}
350+
*/
351+
exports.serialize = function(reactorState) {
352+
let serialized = {}
353+
reactorState.get('stores').forEach((store, id) => {
354+
let storeState = reactorState.getIn(['state', id])
355+
let serializedState = store.serialize(storeState)
356+
if (serializedState !== undefined) {
357+
serialized[id] = serializedState
358+
}
359+
})
360+
return serialized
361+
}
362+
337363
/**
338364
* @param {ReactorState} reactorState
339365
* @param {Getter} getter

src/reactor/records.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const ReactorState = Immutable.Record({
1111

1212
const ObserverStoreMap = Immutable.Record({
1313
// observers registered to any store change
14-
any: Immutable.Map({}),
14+
any: Immutable.Set([]),
1515
// observers registered to specific store changes
1616
stores: Immutable.Map({}),
1717

0 commit comments

Comments
 (0)