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