git ssb

1+

Matt McKegg / mutant



Tree: ae89aab540caa1cee5f40dcc78fc2210e40b024e

Files: ae89aab540caa1cee5f40dcc78fc2210e40b024e / map.js

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

Built with git-ssb-web