git ssb

1+

Matt McKegg / mutant



Tree: 6cbb0cdc8dd11c5f29e9c875e9f602f1d9a21f2a

Files: 6cbb0cdc8dd11c5f29e9c875e9f602f1d9a21f2a / map.js

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

Built with git-ssb-web