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