git ssb

1+

Matt McKegg / mutant



Tree: c37d5c903c0d88bbbfc9daa1bf50aa2436aed954

Files: c37d5c903c0d88bbbfc9daa1bf50aa2436aed954 / map.js

7386 bytesRaw
1var resolve = require('./resolve')
2var LazyWatcher = require('./lib/lazy-watcher')
3var isSame = require('./lib/is-same')
4var addCollectionMethods = require('./lib/add-collection-methods')
5var onceIdle = require('./once-idle')
6
7module.exports = Map
8
9function Map (obs, lambda, opts) {
10 // opts: comparer, maxTime, onRemove
11
12 var comparer = opts && opts.comparer || null
13 var releases = []
14 var binder = LazyWatcher(update, listen, unlisten)
15
16 if (opts && opts.nextTick) binder.nextTick = true
17 if (opts && opts.idle) binder.idle = true
18
19 var itemInvalidators = new global.Map()
20 var lastValues = new global.Map()
21 var rawSet = new global.Set()
22
23 var items = []
24
25 var raw = []
26 var values = []
27 var watches = []
28
29 binder.value = values
30
31 // incremental update
32 var queue = []
33 var maxTime = null
34 if (opts && opts.maxTime) {
35 maxTime = opts.maxTime
36 }
37
38 var result = function MutantMap (listener) {
39 if (!listener) {
40 return binder.getValue()
41 }
42 return binder.addListener(listener)
43 }
44
45 addCollectionMethods(result, raw, binder.checkUpdated)
46
47 return result
48
49 // scoped
50
51 function listen () {
52 if (typeof obs === 'function') {
53 releases.push(obs(binder.onUpdate))
54 }
55 rebindAll()
56
57 Array.from(itemInvalidators.values()).forEach(function (invalidators) {
58 invalidators.forEach(function (invalidator) {
59 invalidator.release = invalidator.observable(invalidate.bind(null, invalidator))
60 })
61 })
62
63 if (opts && opts.onListen) {
64 var release = opts.onListen()
65 if (typeof release === 'function') {
66 releases.push(release)
67 }
68 }
69 }
70
71 function unlisten () {
72 while (releases.length) {
73 releases.pop()()
74 }
75 rebindAll()
76
77 Array.from(itemInvalidators.values()).forEach(function (invalidators) {
78 invalidators.forEach(invokeRelease)
79 })
80
81 if (opts && opts.onUnlisten) {
82 opts.onUnlisten()
83 }
84 }
85
86 function update () {
87 var changed = false
88
89 if (items.length !== getLength(obs)) {
90 changed = true
91 }
92
93 var startedAt = Date.now()
94
95 for (var i = 0, len = getLength(obs); i < len; i++) {
96 var item = get(obs, i)
97 var currentItem = items[i]
98 items[i] = item
99
100 if (!isSame(item, currentItem, comparer) || (!binder.live && checkInvalidated(item))) {
101 if (maxTime && Date.now() - startedAt > maxTime) {
102 queueUpdateItem(i)
103 } else {
104 updateItem(i)
105 }
106 changed = true
107 }
108 }
109
110 if (changed) {
111 // clean up cache
112 var oldLength = raw.length
113 var newLength = getLength(obs)
114 Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(removeItem)
115 items.length = newLength
116 values.length = newLength
117 raw.length = newLength
118 for (var index = newLength; index < oldLength; index++) {
119 rebind(index)
120 }
121 Array.from(rawSet.values()).filter(notIncluded, raw).forEach(removeMapped)
122 }
123
124 return changed
125 }
126
127 function checkInvalidated (item) {
128 if (itemInvalidators.has(item)) {
129 return itemInvalidators.get(item).some(function (invalidator) {
130 lastValues.delete(invalidator.item)
131 return !isSame(invalidator.currentValue, resolve(invalidator.observable), comparer)
132 })
133 }
134 }
135
136 function queueUpdateItem (i) {
137 if (!queue.length) {
138 doSoon(flushQueue)
139 }
140 if (!~queue.indexOf(i)) {
141 queue.push(i)
142 }
143 }
144
145 function flushQueue () {
146 var startedAt = Date.now()
147 while (queue.length && (!maxTime || Date.now() - startedAt < maxTime)) {
148 updateItem(queue.shift())
149 }
150 binder.broadcast()
151 if (queue.length) {
152 doSoon(flushQueue)
153 }
154 }
155
156 function invalidateOn (item, obs) {
157 if (!itemInvalidators.has(item)) {
158 itemInvalidators.set(item, [])
159 }
160
161 var invalidators = itemInvalidators.get(item)
162 var invalidator = {
163 currentValue: resolve(obs),
164 observable: obs,
165 item: item,
166 release: null
167 }
168
169 invalidators.push(invalidator)
170
171 if (binder.live) {
172 invalidator.release = invalidator.observable(invalidate.bind(null, invalidator))
173 }
174 }
175
176 function addInvalidateCallback (item) {
177 return invalidateOn.bind(null, item)
178 }
179
180 function removeItem (item) {
181 lastValues.delete(item)
182 if (itemInvalidators.has(item)) {
183 itemInvalidators.get(item).forEach(invokeRelease)
184 itemInvalidators.delete(item)
185 }
186 }
187
188 function removeMapped (mappedItem) {
189 rawSet.delete(mappedItem)
190 if (opts && opts.onRemove) {
191 opts.onRemove(mappedItem)
192 }
193 }
194
195 function invalidate (entry) {
196 var changed = []
197 var length = getLength(obs)
198 lastValues.delete(entry.item)
199 for (var i = 0; i < length; i++) {
200 if (get(obs, i) === entry.item) {
201 changed.push(i)
202 }
203 }
204 if (changed.length) {
205 var rawValue = raw[changed[0]]
206 changed.forEach(function (index) {
207 raw[index] = null
208 })
209 if (!raw.includes(rawValue)) {
210 removeMapped(rawValue)
211 }
212 changed.forEach(updateItem)
213 binder.broadcast()
214 }
215 }
216
217 function updateItem (i) {
218 if (i < getLength(obs)) {
219 var item = get(obs, i)
220 if (!lastValues.has(item) || !isSame(item, item, comparer)) {
221 if (itemInvalidators.has(item)) {
222 itemInvalidators.get(item).forEach(invokeRelease)
223 itemInvalidators.delete(item)
224 }
225 var newValue = lambda(item, addInvalidateCallback(item))
226 if (newValue !== raw[i]) {
227 raw[i] = newValue
228 }
229 rawSet.add(newValue)
230 lastValues.set(item, raw[i])
231 } else {
232 raw[i] = lastValues.get(item)
233 }
234 rebind(i)
235 values[i] = resolve(raw[i])
236 }
237 }
238
239 function rebind (index) {
240 if (watches[index]) {
241 watches[index]()
242 watches[index] = null
243 }
244
245 if (binder.live) {
246 if (typeof raw[index] === 'function') {
247 watches[index] = updateValue(raw[index], index)
248 }
249 }
250 }
251
252 function rebindAll () {
253 for (var i = 0; i < raw.length; i++) {
254 rebind(i)
255 }
256 }
257
258 function updateValue (obs, index) {
259 return obs(function (value) {
260 if (!isSame(values[index], value, comparer)) {
261 values[index] = value
262 binder.broadcast()
263 }
264 })
265 }
266
267 function doSoon (fn) {
268 if (opts.idle) {
269 onceIdle(fn)
270 } else if (opts.delayTime) {
271 setTimeout(fn, opts.delayTime)
272 } else {
273 setImmediate(fn)
274 }
275 }
276}
277
278function get (target, index) {
279 if (typeof target === 'function' && !target.get) {
280 target = target()
281 }
282
283 if (Array.isArray(target)) {
284 return target[index]
285 } else if (target && target.get) {
286 return target.get(index)
287 }
288}
289
290function getLength (target) {
291 if (typeof target === 'function' && !target.getLength) {
292 target = target()
293 }
294
295 if (Array.isArray(target)) {
296 return target.length
297 } else if (target && target.get) {
298 return target.getLength()
299 }
300
301 return 0
302}
303
304function notIncluded (value) {
305 if (this.includes) {
306 return !this.includes(value)
307 } else if (this.indexOf) {
308 return !~this.indexOf(value)
309 } else if (typeof this === 'function') {
310 var array = this()
311 if (array && array.includes) {
312 return !array.includes(value)
313 }
314 }
315 return true
316}
317
318function invokeRelease (item) {
319 if (item.release) {
320 item.release()
321 item.release = null
322 }
323}
324

Built with git-ssb-web