git ssb

1+

Matt McKegg / mutant



Tree: 21e21c93b334c79676b367cacbd519536c968bd2

Files: 21e21c93b334c79676b367cacbd519536c968bd2 / map.js

5384 bytesRaw
1var resolve = require('./resolve')
2var LazyWatcher = require('./lib/lazy-watcher')
3var isSame = require('./lib/is-same')
4var addCollectionMethods = require('./lib/add-collection-methods')
5
6module.exports = Map
7
8function 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
209function 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
221function 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
235function 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
249function deleteEntry (entry) {
250 this.delete(entry)
251}
252

Built with git-ssb-web