Files: 4ba1c170208823d1b95c307167d3ad155056905b / map.js
4357 bytesRaw
1 | var resolve = require('./resolve') |
2 | var LazyWatcher = require('./lib/lazy-watcher') |
3 | var isReferenceType = require('./lib/is-reference-type') |
4 | |
5 | module.exports = Map |
6 | |
7 | function Map (obs, lambda, opts) { |
8 | var releases = [] |
9 | var binder = LazyWatcher(update, listen, unlisten) |
10 | |
11 | var lastValues = new global.Map() |
12 | var items = [] |
13 | |
14 | var raw = [] |
15 | var values = [] |
16 | var watches = [] |
17 | |
18 | binder.value = values |
19 | |
20 | // incremental update |
21 | var queue = [] |
22 | var maxTime = null |
23 | if (opts && opts.maxTime) { |
24 | maxTime = opts.maxTime |
25 | } |
26 | |
27 | var result = function MutantMap (listener) { |
28 | if (!listener) { |
29 | return binder.getValue() |
30 | } |
31 | return binder.addListener(listener) |
32 | } |
33 | |
34 | result.get = function (index) { |
35 | return raw[index] |
36 | } |
37 | |
38 | result.getLength = function (index) { |
39 | return raw.length |
40 | } |
41 | |
42 | result.includes = function (valueOrObs) { |
43 | return !!~raw.indexOf(valueOrObs) |
44 | } |
45 | |
46 | result.indexOf = function (valueOrObs) { |
47 | return raw.indexOf(valueOrObs) |
48 | } |
49 | |
50 | return result |
51 | |
52 | // scoped |
53 | |
54 | function listen () { |
55 | if (typeof obs === 'function') { |
56 | releases.push(obs(binder.onUpdate)) |
57 | } |
58 | rebindAll() |
59 | } |
60 | |
61 | function unlisten () { |
62 | while (releases.length) { |
63 | releases.pop()() |
64 | } |
65 | rebindAll() |
66 | lastValues.clear() |
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 (typeof item === 'object' || item !== currentItem) { |
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 = items.length |
96 | Array.from(lastValues.keys()).filter(notIncluded, obs).forEach(deleteEntry, lastValues) |
97 | items.length = getLength(obs) |
98 | values.length = items.length |
99 | raw.length = items.length |
100 | for (var index = items.length; index < oldLength; index++) { |
101 | rebind(index) |
102 | } |
103 | } |
104 | |
105 | return changed |
106 | } |
107 | |
108 | function queueUpdateItem (i) { |
109 | if (!queue.length) { |
110 | setImmediate(flushQueue) |
111 | } |
112 | if (!~queue.indexOf(i)) { |
113 | queue.push(i) |
114 | } |
115 | } |
116 | |
117 | function flushQueue () { |
118 | var startedAt = Date.now() |
119 | while (queue.length && (!maxTime || Date.now() - startedAt < maxTime)) { |
120 | updateItem(queue.pop()) |
121 | } |
122 | binder.broadcast() |
123 | if (queue.length) { |
124 | setImmediate(flushQueue) |
125 | } |
126 | } |
127 | |
128 | function updateItem (i) { |
129 | var item = get(obs, i) |
130 | if (isReferenceType(item)) { |
131 | raw[i] = lambda(item) |
132 | } else { |
133 | if (!lastValues.has(item)) { |
134 | lastValues.set(item, lambda(item)) |
135 | } |
136 | raw[i] = lastValues.get(item) |
137 | } |
138 | values[i] = resolve(raw[i]) |
139 | rebind(i) |
140 | } |
141 | |
142 | function rebind (index) { |
143 | if (watches[index]) { |
144 | watches[index]() |
145 | watches[index] = null |
146 | } |
147 | |
148 | if (binder.live) { |
149 | if (typeof raw[index] === 'function') { |
150 | watches[index] = updateValue(raw[index], index) |
151 | } |
152 | } |
153 | } |
154 | |
155 | function rebindAll () { |
156 | for (var i = 0; i < raw.length; i++) { |
157 | rebind(i) |
158 | } |
159 | } |
160 | |
161 | function updateValue (obs, index) { |
162 | return obs(function (value) { |
163 | if (values[index] !== value || typeof value === 'object') { |
164 | values[index] = value |
165 | binder.broadcast() |
166 | } |
167 | }) |
168 | } |
169 | } |
170 | |
171 | function get (target, index) { |
172 | if (typeof target === 'function' && !target.get) { |
173 | target = target() |
174 | } |
175 | |
176 | if (Array.isArray(target)) { |
177 | return target[index] |
178 | } else if (target && target.get) { |
179 | return target.get(index) |
180 | } |
181 | } |
182 | |
183 | function getLength (target) { |
184 | if (typeof target === 'function' && !target.getLength) { |
185 | target = target() |
186 | } |
187 | |
188 | if (Array.isArray(target)) { |
189 | return target.length |
190 | } else if (target && target.get) { |
191 | return target.getLength() |
192 | } |
193 | |
194 | return 0 |
195 | } |
196 | |
197 | function notIncluded (value) { |
198 | if (this.includes) { |
199 | return !this.includes(value) |
200 | } else if (this.indexOf) { |
201 | return !~this.indexOf(value) |
202 | } else if (typeof this === 'function') { |
203 | var array = this() |
204 | if (array && array.includes) { |
205 | return !array.includes(value) |
206 | } |
207 | } |
208 | return true |
209 | } |
210 | |
211 | function deleteEntry (entry) { |
212 | this.delete(entry) |
213 | } |
214 |
Built with git-ssb-web