git ssb

1+

Matt McKegg / mutant



Tree: 099e49713653c62ab39055b073d7b9d37da7d803

Files: 099e49713653c62ab39055b073d7b9d37da7d803 / map.js

7249 bytesRaw
1var resolve = require('./resolve')
2var LazyWatcher = require('./lib/lazy-watcher')
3var isSame = require('./lib/is-same')
4var addCollectionMethods = require('./lib/add-collection-methods')
5
6module.exports = Map
7
8function Map (obs, lambda, opts) {
9 // opts: comparer, maxTime, onRemove
10
11 var comparer = opts && opts.comparer || null
12 var releases = []
13 var binder = LazyWatcher(update, listen, unlisten)
14
15 if (opts && opts.nextTick) {
16 binder.nextTick = true
17 }
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((index) => { raw[index] = null })
207 if (!raw.includes(rawValue)) {
208 removeMapped(rawValue)
209 }
210 changed.forEach(updateItem)
211 binder.broadcast()
212 }
213 }
214
215 function updateItem (i) {
216 if (i < getLength(obs)) {
217 var item = get(obs, i)
218 if (!lastValues.has(item) || !isSame(item, item, comparer)) {
219 if (itemInvalidators.has(item)) {
220 itemInvalidators.get(item).forEach(invokeRelease)
221 itemInvalidators.delete(item)
222 }
223 var newValue = lambda(item, addInvalidateCallback(item))
224 if (newValue !== raw[i]) {
225 raw[i] = newValue
226 }
227 rawSet.add(newValue)
228 lastValues.set(item, raw[i])
229 rebind(i)
230 } else {
231 raw[i] = lastValues.get(item)
232 }
233 values[i] = resolve(raw[i])
234 }
235 }
236
237 function rebind (index) {
238 if (watches[index]) {
239 watches[index]()
240 watches[index] = null
241 }
242
243 if (binder.live) {
244 if (typeof raw[index] === 'function') {
245 watches[index] = updateValue(raw[index], index)
246 }
247 }
248 }
249
250 function rebindAll () {
251 for (var i = 0; i < raw.length; i++) {
252 rebind(i)
253 }
254 }
255
256 function updateValue (obs, index) {
257 return obs(function (value) {
258 if (!isSame(values[index], value, comparer)) {
259 values[index] = value
260 binder.broadcast()
261 }
262 })
263 }
264
265 function doSoon (fn) {
266 if (opts.delayTime) {
267 setTimeout(fn, opts.delayTime)
268 } else {
269 setImmediate(fn)
270 }
271 }
272}
273
274function get (target, index) {
275 if (typeof target === 'function' && !target.get) {
276 target = target()
277 }
278
279 if (Array.isArray(target)) {
280 return target[index]
281 } else if (target && target.get) {
282 return target.get(index)
283 }
284}
285
286function getLength (target) {
287 if (typeof target === 'function' && !target.getLength) {
288 target = target()
289 }
290
291 if (Array.isArray(target)) {
292 return target.length
293 } else if (target && target.get) {
294 return target.getLength()
295 }
296
297 return 0
298}
299
300function notIncluded (value) {
301 if (this.includes) {
302 return !this.includes(value)
303 } else if (this.indexOf) {
304 return !~this.indexOf(value)
305 } else if (typeof this === 'function') {
306 var array = this()
307 if (array && array.includes) {
308 return !array.includes(value)
309 }
310 }
311 return true
312}
313
314function invokeRelease (item) {
315 if (item.release) {
316 item.release()
317 item.release = null
318 }
319}
320

Built with git-ssb-web