Files: b424e0dd69696f9db8b920d2b503320da01b5f98 / map.js
6760 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 | 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 | |
252 | function 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 | |
264 | function 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 | |
278 | function 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 | |
292 | function deleteEntry (entry) { |
293 | this.delete(entry) |
294 | } |
295 | |
296 | function invokeRelease (item) { |
297 | if (item.release) { |
298 | item.release() |
299 | item.release = null |
300 | } |
301 | } |
302 |
Built with git-ssb-web