-
Notifications
You must be signed in to change notification settings - Fork 33
/
rooms_builder.go
133 lines (122 loc) · 4.42 KB
/
rooms_builder.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package handler
import (
"context"
"fmt"
"sort"
"github.com/matrix-org/sliding-sync/internal"
"github.com/matrix-org/sliding-sync/sync3"
)
// RoomsBuilder gradually accumulates and mixes data required in order to populate the top-level rooms key
// in the Response. It is not thread-safe and should only be called by the ConnState thread.
//
// The top-level `rooms` key is an amalgamation of:
// - Room subscriptions
// - Rooms within all sliding lists.
//
// The purpose of this builder is to remember which rooms we will be returning data for, along with the
// room subscription for that room. This then allows efficient database accesses. For example:
// - List A will return !a, !b, !c with Room Subscription X
// - List B will return !b, !c, !d with Room Subscription Y
// - Room sub for !a with Room Subscription Z
// Rather than performing each operation in isolation and query for rooms multiple times (where the
// response data will inevitably be dropped), we can instead amalgamate this into:
// - Room Subscription X+Z -> !a
// - Room Subscription X+Y -> !b, !c
// - Room Subscription Y -> !d
// This data will not be wasted when it has been retrieved from the database.
type RoomsBuilder struct {
subs []sync3.RoomSubscription
subToRooms map[int][]string
}
func NewRoomsBuilder() *RoomsBuilder {
return &RoomsBuilder{
subToRooms: make(map[int][]string),
}
}
func (rb *RoomsBuilder) IncludesRoom(roomID string) bool {
for _, roomIDs := range rb.subToRooms {
for _, rid := range roomIDs {
if roomID == rid {
return true
}
}
}
return false
}
// Add a room subscription to the builder, e.g from a list or room subscription. This should NOT
// be a combined subscription.
func (rb *RoomsBuilder) AddSubscription(rs sync3.RoomSubscription) (id int) {
rb.subs = append(rb.subs, rs)
return len(rb.subs) - 1
}
// Add rooms to the subscription ID previously added. E.g rooms from a list.
func (rb *RoomsBuilder) AddRoomsToSubscription(ctx context.Context, id int, roomIDs []string) {
internal.AssertWithContext(ctx, "subscription ID is unknown", id < len(rb.subs))
rb.subToRooms[id] = append(rb.subToRooms[id], roomIDs...)
}
// Work out which subscriptions need to be combined and produce a new set of subscriptions -> room IDs.
// Any given room ID will appear in exactly one BuiltSubscription.
func (rb *RoomsBuilder) BuildSubscriptions() (result []BuiltSubscription) {
// calculate the inverse (room -> subs)
roomToSubIDs := make(map[string]map[int]struct{}) // room_id to set of ints
for subID, roomIDs := range rb.subToRooms {
for _, roomID := range roomIDs {
if _, ok := roomToSubIDs[roomID]; !ok {
roomToSubIDs[roomID] = make(map[int]struct{})
}
roomToSubIDs[roomID][subID] = struct{}{}
}
}
// for each room, create a combined subscription (or use an existing subscription), remembering
// which combined subscriptions we have so we can reuse them
subToRoomIDs := make(map[*sync3.RoomSubscription][]string) // incl. combined subs
subIDToSub := make(map[string]*sync3.RoomSubscription)
for i := range rb.subs {
subIDToSub[subKey(i)] = &rb.subs[i] // uncombined subscriptions
}
for roomID, subIDs := range roomToSubIDs {
// convert the set into a list
subIDList := make([]int, 0, len(subIDs))
for subID := range subIDs {
subIDList = append(subIDList, subID)
}
// calculate the combined sub key and see if it exists already
sk := subKey(subIDList...)
combinedSub, ok := subIDToSub[sk]
if !ok {
// make a combined room subscription
var combinedRoomSub *sync3.RoomSubscription
for _, subID := range subIDList {
roomSub := rb.subs[subID]
if combinedRoomSub == nil {
combinedRoomSub = &roomSub
} else {
crs := combinedRoomSub.Combine(roomSub)
combinedRoomSub = &crs
}
}
// assign it
subIDToSub[sk] = combinedRoomSub
combinedSub = combinedRoomSub
}
// add this room to this combined sub. Relies on the pointers being the same for combined subs
subToRoomIDs[combinedSub] = append(subToRoomIDs[combinedSub], roomID)
}
// make the subscriptions
for sub, roomIDs := range subToRoomIDs {
result = append(result, BuiltSubscription{
RoomSubscription: *sub,
RoomIDs: roomIDs,
})
}
return result
}
func subKey(subIDs ...int) string {
// we must sort so subscriptions are commutative
sort.Ints(subIDs)
return fmt.Sprintf("%v", subIDs)
}
type BuiltSubscription struct {
RoomSubscription sync3.RoomSubscription
RoomIDs []string
}