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