git ssb

1+

Matt McKegg / mutant



Tree: dacc7137a972984d5f5adfe48e91c35b8056adb9

Files: dacc7137a972984d5f5adfe48e91c35b8056adb9 / map.js

6941 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 Array.from(rawSet.values()).filter(notIncluded, raw).forEach(removeMapped)
203 binder.broadcast()
204 }
205 }
206
207 function updateItem (i) {
208 var item = get(obs, i)
209 if (!lastValues.has(item) || !isSame(item, item, comparer)) {
210 if (itemInvalidators.has(item)) {
211 itemInvalidators.get(item).forEach(invokeRelease)
212 itemInvalidators.delete(item)
213 }
214 var newValue = lambda(item, addInvalidateCallback(item))
215 if (newValue !== raw[i]) {
216 raw[i] = newValue
217 }
218 rawSet.add(newValue)
219 lastValues.set(item, raw[i])
220 rebind(i)
221 } else {
222 raw[i] = lastValues.get(item)
223 }
224 values[i] = resolve(raw[i])
225 }
226
227 function rebind (index) {
228 if (watches[index]) {
229 watches[index]()
230 watches[index] = null
231 }
232
233 if (binder.live) {
234 if (typeof raw[index] === 'function') {
235 watches[index] = updateValue(raw[index], index)
236 }
237 }
238 }
239
240 function rebindAll () {
241 for (var i = 0; i < raw.length; i++) {
242 rebind(i)
243 }
244 }
245
246 function updateValue (obs, index) {
247 return obs(function (value) {
248 if (!isSame(values[index], value, comparer)) {
249 values[index] = value
250 binder.broadcast()
251 }
252 })
253 }
254}
255
256function get (target, index) {
257 if (typeof target === 'function' && !target.get) {
258 target = target()
259 }
260
261 if (Array.isArray(target)) {
262 return target[index]
263 } else if (target && target.get) {
264 return target.get(index)
265 }
266}
267
268function getLength (target) {
269 if (typeof target === 'function' && !target.getLength) {
270 target = target()
271 }
272
273 if (Array.isArray(target)) {
274 return target.length
275 } else if (target && target.get) {
276 return target.getLength()
277 }
278
279 return 0
280}
281
282function notIncluded (value) {
283 if (this.includes) {
284 return !this.includes(value)
285 } else if (this.indexOf) {
286 return !~this.indexOf(value)
287 } else if (typeof this === 'function') {
288 var array = this()
289 if (array && array.includes) {
290 return !array.includes(value)
291 }
292 }
293 return true
294}
295
296function deleteEntry (entry) {
297 this.delete(entry)
298}
299
300function invokeRelease (item) {
301 if (item.release) {
302 item.release()
303 item.release = null
304 }
305}
306

Built with git-ssb-web