Files: 5be33c01238870a66c608fed4ece3fbc43069a51 / map.js
4871 bytesRaw
1 | var resolve = require('./resolve') |
2 | |
3 | module.exports = Map |
4 | |
5 | function Map (obs, lambda, opts) { |
6 | var releases = [] |
7 | var live = false |
8 | var lazy = false |
9 | var listeners = [] |
10 | |
11 | var lastValues = new global.Map() |
12 | var items = [] |
13 | |
14 | var raw = [] |
15 | var values = [] |
16 | var watches = [] |
17 | |
18 | // incremental update |
19 | var queue = [] |
20 | var maxTime = null |
21 | if (opts && opts.maxTime) { |
22 | maxTime = opts.maxTime |
23 | } |
24 | |
25 | var result = function MutantMap (listener) { |
26 | if (!listener) { |
27 | return getValue() |
28 | } |
29 | |
30 | if (typeof listener !== 'function') { |
31 | throw new Error('Listeners must be functions.') |
32 | } |
33 | |
34 | listeners.push(listener) |
35 | listen() |
36 | |
37 | return function remove () { |
38 | for (var i = 0, len = listeners.length; i < len; i++) { |
39 | if (listeners[i] === listener) { |
40 | listeners.splice(i, 1) |
41 | break |
42 | } |
43 | } |
44 | if (!listeners.length) { |
45 | unlisten() |
46 | } |
47 | } |
48 | } |
49 | |
50 | result.get = function (index) { |
51 | return raw[index] |
52 | } |
53 | |
54 | result.getLength = function (index) { |
55 | return raw.length |
56 | } |
57 | |
58 | result.includes = function (valueOrObs) { |
59 | return !!~raw.indexOf(valueOrObs) |
60 | } |
61 | |
62 | result.indexOf = function (valueOrObs) { |
63 | return raw.indexOf(valueOrObs) |
64 | } |
65 | |
66 | return result |
67 | |
68 | // scoped |
69 | |
70 | function listen () { |
71 | if (!live) { |
72 | live = true |
73 | lazy = true |
74 | if (typeof obs === 'function') { |
75 | releases.push(obs(onUpdate)) |
76 | } |
77 | rebindAll() |
78 | } |
79 | } |
80 | |
81 | function unlisten () { |
82 | if (live) { |
83 | live = false |
84 | while (releases.length) { |
85 | releases.pop()() |
86 | } |
87 | rebindAll() |
88 | } |
89 | } |
90 | |
91 | function onUpdate () { |
92 | if (update()) { |
93 | broadcast() |
94 | } |
95 | } |
96 | |
97 | function update () { |
98 | var changed = false |
99 | |
100 | if (items.length !== getLength(obs)) { |
101 | changed = true |
102 | } |
103 | |
104 | var startedAt = Date.now() |
105 | |
106 | for (var i = 0, len = getLength(obs); i < len; i++) { |
107 | var item = get(obs, i) |
108 | var currentItem = items[i] |
109 | items[i] = item |
110 | |
111 | if (typeof item === 'object' || item !== currentItem) { |
112 | if (maxTime && Date.now() - startedAt > maxTime) { |
113 | queueUpdateItem(i) |
114 | } else { |
115 | updateItem(i) |
116 | } |
117 | changed = true |
118 | } |
119 | } |
120 | |
121 | if (changed) { |
122 | // clean up cache |
123 | Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(deleteEntry, lastValues) |
124 | items.length = getLength(obs) |
125 | values.length = items.length |
126 | } |
127 | |
128 | return changed |
129 | } |
130 | |
131 | function queueUpdateItem (i) { |
132 | if (!queue.length) { |
133 | setImmediate(flushQueue) |
134 | } |
135 | if (!~queue.indexOf(i)) { |
136 | queue.push(i) |
137 | } |
138 | } |
139 | |
140 | function flushQueue () { |
141 | var startedAt = Date.now() |
142 | while (queue.length && (!maxTime || Date.now() - startedAt < maxTime)) { |
143 | updateItem(queue.pop()) |
144 | } |
145 | broadcast() |
146 | if (queue.length) { |
147 | setImmediate(flushQueue) |
148 | } |
149 | } |
150 | |
151 | function updateItem (i) { |
152 | var item = get(obs, i) |
153 | if (typeof item === 'object') { |
154 | raw[i] = lambda(item) |
155 | } else { |
156 | if (!lastValues.has(item)) { |
157 | lastValues.set(item, lambda(item)) |
158 | } |
159 | raw[i] = lastValues.get(item) |
160 | } |
161 | values[i] = resolve(raw[i]) |
162 | rebind(i) |
163 | } |
164 | |
165 | function rebind (index) { |
166 | if (watches[index]) { |
167 | watches[index]() |
168 | watches[index] = null |
169 | } |
170 | |
171 | if (live) { |
172 | if (typeof raw[index] === 'function') { |
173 | watches[index] = updateValue(raw[index], index) |
174 | } |
175 | } |
176 | } |
177 | |
178 | function rebindAll () { |
179 | for (var i = 0; i < raw.length; i++) { |
180 | rebind(i) |
181 | } |
182 | } |
183 | |
184 | function updateValue (obs, index) { |
185 | return obs(function (value) { |
186 | if (values[index] !== value || typeof value === 'object') { |
187 | values[index] = value |
188 | broadcast() |
189 | } |
190 | }) |
191 | } |
192 | |
193 | function broadcast () { |
194 | var cachedListeners = listeners.slice(0) |
195 | for (var i = 0, len = cachedListeners.length; i < len; i++) { |
196 | cachedListeners[i](values) |
197 | } |
198 | } |
199 | |
200 | function getValue () { |
201 | if (!live || lazy) { |
202 | lazy = false |
203 | update() |
204 | } |
205 | return values |
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