Files: 21e21c93b334c79676b367cacbd519536c968bd2 / map.js
5384 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 invalidateReleases = new global.WeakMap() |
14 | var binder = LazyWatcher(update, listen, unlisten) |
15 | |
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 | if (opts && opts.onListen) { |
54 | opts.onListen() |
55 | } |
56 | } |
57 | |
58 | function unlisten () { |
59 | while (releases.length) { |
60 | releases.pop()() |
61 | } |
62 | rebindAll() |
63 | |
64 | if (opts && opts.onUnlisten) { |
65 | opts.onUnlisten() |
66 | } |
67 | } |
68 | |
69 | function update () { |
70 | var changed = false |
71 | |
72 | if (items.length !== getLength(obs)) { |
73 | changed = true |
74 | } |
75 | |
76 | var startedAt = Date.now() |
77 | |
78 | for (var i = 0, len = getLength(obs); i < len; i++) { |
79 | var item = get(obs, i) |
80 | var currentItem = items[i] |
81 | items[i] = item |
82 | |
83 | if (!isSame(item, currentItem, comparer)) { |
84 | if (maxTime && Date.now() - startedAt > maxTime) { |
85 | queueUpdateItem(i) |
86 | } else { |
87 | updateItem(i) |
88 | } |
89 | changed = true |
90 | } |
91 | } |
92 | |
93 | if (changed) { |
94 | // clean up cache |
95 | var oldLength = raw.length |
96 | var newLength = getLength(obs) |
97 | Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(deleteEntry, lastValues) |
98 | items.length = newLength |
99 | values.length = newLength |
100 | raw.length = newLength |
101 | for (var index = newLength; index < oldLength; index++) { |
102 | rebind(index) |
103 | } |
104 | Array.from(rawSet.values()).filter(notIncluded, raw).forEach(notifyRemoved) |
105 | } |
106 | |
107 | return changed |
108 | } |
109 | |
110 | function queueUpdateItem (i) { |
111 | if (!queue.length) { |
112 | setImmediate(flushQueue) |
113 | } |
114 | if (!~queue.indexOf(i)) { |
115 | queue.push(i) |
116 | } |
117 | } |
118 | |
119 | function flushQueue () { |
120 | var startedAt = Date.now() |
121 | while (queue.length && (!maxTime || Date.now() - startedAt < maxTime)) { |
122 | updateItem(queue.pop()) |
123 | } |
124 | binder.broadcast() |
125 | if (queue.length) { |
126 | setImmediate(flushQueue) |
127 | } |
128 | } |
129 | |
130 | function invalidateOn (item, obs) { |
131 | if (!invalidateReleases.has(item)) { |
132 | invalidateReleases.set(item, []) |
133 | } |
134 | invalidateReleases.get(item).push(obs(invalidate.bind(null, item))) |
135 | } |
136 | |
137 | function addInvalidateCallback (item) { |
138 | return invalidateOn.bind(null, item) |
139 | } |
140 | |
141 | function notifyRemoved (item) { |
142 | rawSet.delete(item) |
143 | invalidateReleases.delete(item) |
144 | if (opts && opts.onRemove) { |
145 | opts.onRemove(item) |
146 | } |
147 | } |
148 | |
149 | function invalidate (item) { |
150 | var changed = false |
151 | var length = getLength(obs) |
152 | lastValues.delete(item) |
153 | for (var i = 0; i < length; i++) { |
154 | if (get(obs, i) === item) { |
155 | updateItem(i) |
156 | changed = true |
157 | } |
158 | } |
159 | if (changed) { |
160 | binder.broadcast() |
161 | } |
162 | } |
163 | |
164 | function updateItem (i) { |
165 | var item = get(obs, i) |
166 | if (!lastValues.has(item) || !isSame(item, item, comparer)) { |
167 | var newValue = lambda(item, addInvalidateCallback(item)) |
168 | if (newValue !== raw[i]) { |
169 | raw[i] = newValue |
170 | } |
171 | rawSet.add(newValue) |
172 | lastValues.set(item, raw[i]) |
173 | rebind(i) |
174 | } else { |
175 | raw[i] = lastValues.get(item) |
176 | } |
177 | values[i] = resolve(raw[i]) |
178 | } |
179 | |
180 | function rebind (index) { |
181 | if (watches[index]) { |
182 | watches[index]() |
183 | watches[index] = null |
184 | } |
185 | |
186 | if (binder.live) { |
187 | if (typeof raw[index] === 'function') { |
188 | watches[index] = updateValue(raw[index], index) |
189 | } |
190 | } |
191 | } |
192 | |
193 | function rebindAll () { |
194 | for (var i = 0; i < raw.length; i++) { |
195 | rebind(i) |
196 | } |
197 | } |
198 | |
199 | function updateValue (obs, index) { |
200 | return obs(function (value) { |
201 | if (!isSame(values[index], value, comparer)) { |
202 | values[index] = value |
203 | binder.broadcast() |
204 | } |
205 | }) |
206 | } |
207 | } |
208 | |
209 | function get (target, index) { |
210 | if (typeof target === 'function' && !target.get) { |
211 | target = target() |
212 | } |
213 | |
214 | if (Array.isArray(target)) { |
215 | return target[index] |
216 | } else if (target && target.get) { |
217 | return target.get(index) |
218 | } |
219 | } |
220 | |
221 | function getLength (target) { |
222 | if (typeof target === 'function' && !target.getLength) { |
223 | target = target() |
224 | } |
225 | |
226 | if (Array.isArray(target)) { |
227 | return target.length |
228 | } else if (target && target.get) { |
229 | return target.getLength() |
230 | } |
231 | |
232 | return 0 |
233 | } |
234 | |
235 | function notIncluded (value) { |
236 | if (this.includes) { |
237 | return !this.includes(value) |
238 | } else if (this.indexOf) { |
239 | return !~this.indexOf(value) |
240 | } else if (typeof this === 'function') { |
241 | var array = this() |
242 | if (array && array.includes) { |
243 | return !array.includes(value) |
244 | } |
245 | } |
246 | return true |
247 | } |
248 | |
249 | function deleteEntry (entry) { |
250 | this.delete(entry) |
251 | } |
252 |
Built with git-ssb-web