Commit b78fb2776fa75902fd8505a5defc377fbbf37101
contact.obs: use additional cache to make computing reverse lookups faster
#33Matt McKegg committed on 9/29/2017, 9:51:56 AM
Parent: 4f3e54eaa5e60f7e8551798b984fbf3d4e0886c1
Files changed
contact/obs.js | changed |
contact/obs.js | ||
---|---|---|
@@ -1,6 +1,6 @@ | ||
1 | 1 | var nest = require('depnest') |
2 | -var { Value, Dict, computed } = require('mutant') | |
2 | +var { Value, computed } = require('mutant') | |
3 | 3 | var pull = require('pull-stream') |
4 | 4 | var ref = require('ssb-ref') |
5 | 5 | |
6 | 6 | exports.needs = nest({ |
@@ -13,17 +13,25 @@ | ||
13 | 13 | }) |
14 | 14 | |
15 | 15 | exports.create = function (api) { |
16 | 16 | var cacheLoading = false |
17 | - var cache = Dict() | |
17 | + var cache = {} | |
18 | + var reverseCache = {} | |
19 | + | |
18 | 20 | var sync = Value(false) |
19 | 21 | |
20 | 22 | return nest({ |
21 | 23 | 'contact.obs': { |
22 | - following: following, | |
23 | - followers: followers, | |
24 | - blocking: blocking, | |
25 | - blockers: blockers | |
24 | + | |
25 | + // states: | |
26 | + // true = following, | |
27 | + // null = neutral (may have unfollowed), | |
28 | + // false = blocking | |
29 | + | |
30 | + following: (key) => matchingValueKeys(get(key, cache), true), | |
31 | + followers: (key) => matchingValueKeys(get(key, reverseCache), true), | |
32 | + blocking: (key) => matchingValueKeys(get(key, cache), false), | |
33 | + blockers: (key) => matchingValueKeys(get(key, reverseCache), true) | |
26 | 34 | }, |
27 | 35 | 'sbot.hook.publish': function (msg) { |
28 | 36 | if (!isContact(msg)) return |
29 | 37 | |
@@ -35,98 +43,69 @@ | ||
35 | 43 | : msg.value.content.flagged || msg.value.content.blocking ? false |
36 | 44 | : null |
37 | 45 | ) |
38 | 46 | |
39 | - update(source, { [dest]: tristate }) | |
47 | + update(source, { [dest]: tristate }, cache) | |
48 | + update(dest, { [source]: tristate }, reverseCache) | |
40 | 49 | } |
41 | 50 | }) |
42 | 51 | |
43 | - // states: | |
44 | - // true = following, | |
45 | - // null = neutral (may have unfollowed), | |
46 | - // false = blocking | |
47 | - | |
48 | - function following (key) { | |
49 | - var obs = computed(get(key), state => { | |
50 | - return Object.keys(state) | |
51 | - .reduce((sofar, next) => { | |
52 | - if (state[next] === true) return [...sofar, next] | |
53 | - else return sofar | |
54 | - }, []) | |
52 | + function matchingValueKeys (state, value) { | |
53 | + var obs = computed(state, state => { | |
54 | + return Object.keys(state).filter(key => { | |
55 | + return state[key] === value | |
56 | + }) | |
55 | 57 | }) |
56 | 58 | |
57 | 59 | obs.sync = sync |
58 | 60 | return obs |
59 | 61 | } |
60 | 62 | |
61 | - function followers (key) { | |
62 | - var obs = computed(cache, cache => { | |
63 | - return Object.keys(cache) | |
64 | - .reduce((sofar, next) => { | |
65 | - if (cache[next][key] === true) return [...sofar, next] | |
66 | - else return sofar | |
67 | - }, []) | |
68 | - }) | |
69 | - | |
70 | - obs.sync = sync | |
71 | - return obs | |
72 | - } | |
73 | - | |
74 | - function blocking (key) { | |
75 | - var obs = computed(get(key), state => { | |
76 | - return Object.keys(state) | |
77 | - .reduce((sofar, next) => { | |
78 | - if (state[next] === false) return [...sofar, next] | |
79 | - else return sofar | |
80 | - }, []) | |
81 | - }) | |
82 | - | |
83 | - obs.sync = sync | |
84 | - return obs | |
85 | - } | |
86 | - | |
87 | - function blockers (key) { | |
88 | - var obs = computed(cache, cache => { | |
89 | - return Object.keys(cache) | |
90 | - .reduce((sofar, next) => { | |
91 | - if (cache[next][key] === false) return [...sofar, next] | |
92 | - else return sofar | |
93 | - }, []) | |
94 | - }) | |
95 | - | |
96 | - obs.sync = sync | |
97 | - return obs | |
98 | - } | |
99 | - | |
100 | - | |
101 | 63 | function loadCache () { |
102 | 64 | pull( |
103 | 65 | api.sbot.pull.stream(sbot => sbot.friends.stream({live: true})), |
104 | 66 | pull.drain(item => { |
105 | 67 | if (!sync()) { |
106 | - // initial dump | |
68 | + // populate observable cache | |
69 | + var reverse = {} | |
107 | 70 | for (var source in item) { |
108 | - if (ref.isFeed(source)) update(source, item[source]) | |
71 | + if (ref.isFeed(source)) { | |
72 | + update(source, item[source], cache) | |
73 | + | |
74 | + // generate reverse lookup | |
75 | + for (var dest in item[source]) { | |
76 | + reverse[dest] = reverse[dest] || {} | |
77 | + reverse[dest][source] = item[source][dest] | |
78 | + } | |
79 | + } | |
109 | 80 | } |
81 | + | |
82 | + // populate reverse observable cache | |
83 | + for (var dest in reverse) { | |
84 | + update(dest, reverse[dest], reverseCache) | |
85 | + } | |
86 | + | |
110 | 87 | sync.set(true) |
111 | 88 | } else { |
112 | 89 | // handle realtime updates |
113 | - update(item.from, {[item.to]: item.value}) | |
90 | + update(item.from, {[item.to]: item.value}, cache) | |
91 | + update(item.to, {[item.from]: item.value}, reverseCache) | |
114 | 92 | } |
115 | 93 | }) |
116 | 94 | ) |
117 | 95 | } |
118 | 96 | |
119 | - function update (sourceId, values) { | |
97 | + function update (sourceId, values, lookup) { | |
120 | 98 | // ssb-friends: values = { |
121 | 99 | // keyA: true|null|false (friend, neutral, block) |
122 | 100 | // keyB: true|null|false (friend, neutral, block) |
123 | 101 | // } |
124 | - var state = get(sourceId) | |
102 | + var state = get(sourceId, lookup) | |
125 | 103 | var lastState = state() |
126 | 104 | var changed = false |
105 | + | |
127 | 106 | for (var targetId in values) { |
128 | - if (values[targetId] != lastState[targetId]) { | |
107 | + if (values[targetId] !== lastState[targetId]) { | |
129 | 108 | lastState[targetId] = values[targetId] |
130 | 109 | changed = true |
131 | 110 | } |
132 | 111 | } |
@@ -135,21 +114,20 @@ | ||
135 | 114 | state.set(lastState) |
136 | 115 | } |
137 | 116 | } |
138 | 117 | |
139 | - function get (id) { | |
118 | + function get (id, lookup) { | |
140 | 119 | if (!ref.isFeed(id)) throw new Error('Contact state requires an id!') |
141 | 120 | if (!cacheLoading) { |
142 | 121 | cacheLoading = true |
143 | 122 | loadCache() |
144 | 123 | } |
145 | - if (!cache.has(id)) { | |
146 | - cache.put(id, Value({})) | |
124 | + if (!lookup[id]) { | |
125 | + lookup[id] = Value({}) | |
147 | 126 | } |
148 | - return cache.get(id) | |
127 | + return lookup[id] | |
149 | 128 | } |
150 | 129 | } |
151 | 130 | |
152 | 131 | function isContact (msg) { |
153 | 132 | return msg.value && msg.value.content && msg.value.content.type === 'contact' |
154 | 133 | } |
155 | - |
Built with git-ssb-web