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