github.com/emitter-io/go/v2@v2.1.0/subtrie.go (about) 1 package emitter 2 3 import ( 4 "strings" 5 "sync" 6 ) 7 8 // route is a value associated with a subscription. 9 type route struct { 10 Topic string 11 Action MessageHandler 12 } 13 14 func newRoute(topic string, handler MessageHandler) ([]string, route) { 15 query := strings.FieldsFunc(topic, func(c rune) bool { 16 return c == '/' 17 }) 18 19 return query, route{ 20 Topic: topic, 21 Action: handler, 22 } 23 } 24 25 // ------------------------------------------------------------------------------------ 26 27 type node struct { 28 word string 29 routes map[string]route 30 parent *node 31 children map[string]*node 32 } 33 34 func (n *node) orphan() { 35 if n.parent == nil { 36 return 37 } 38 39 delete(n.parent.children, n.word) 40 if len(n.parent.routes) == 0 && len(n.parent.children) == 0 { 41 n.parent.orphan() 42 } 43 } 44 45 // trie represents an efficient collection of subscriptions with lookup capability. 46 type trie struct { 47 sync.RWMutex 48 root *node // The root node of the tree. 49 lookup func(query []string, result *[]MessageHandler, node *node) 50 } 51 52 // newTrie creates a new trie without a lookup function. 53 func newTrie() *trie { 54 return &trie{ 55 root: &node{ 56 children: make(map[string]*node), 57 }, 58 } 59 } 60 61 // NewTrie creates a new subscriptions matcher using standard emitter strategy. 62 func NewTrie() *trie { 63 t := newTrie() 64 t.lookup = t.lookupEmitter 65 return t 66 } 67 68 // NewTrieMQTT creates a new subscriptions matcher using standard MQTT strategy. 69 func NewTrieMQTT() *trie { 70 t := newTrie() 71 t.lookup = t.lookupMqtt 72 return t 73 } 74 75 // AddHandler adds a message handler to a topic. 76 func (t *trie) AddHandler(topic string, handler MessageHandler) error { 77 query, rt := newRoute(topic, handler) 78 79 t.Lock() 80 curr := t.root 81 for _, word := range query { 82 child, ok := curr.children[word] 83 if !ok { 84 child = &node{ 85 word: word, 86 parent: curr, 87 routes: make(map[string]route), 88 children: make(map[string]*node), 89 } 90 curr.children[word] = child 91 } 92 curr = child 93 } 94 95 // Add the handler 96 curr.routes[rt.Topic] = rt 97 t.Unlock() 98 return nil 99 } 100 101 // RemoveHandler removes a message handler from a topic. 102 func (t *trie) RemoveHandler(topic string) { 103 query, _ := newRoute(topic, nil) 104 105 t.Lock() 106 curr := t.root 107 for _, word := range query { 108 child, ok := curr.children[word] 109 if !ok { 110 // Subscription doesn't exist. 111 t.Unlock() 112 return 113 } 114 curr = child 115 } 116 117 // Remove the route 118 delete(curr.routes, topic) 119 120 // Remove orphans 121 if len(curr.routes) == 0 && len(curr.children) == 0 { 122 curr.orphan() 123 } 124 t.Unlock() 125 } 126 127 // Lookup returns the handlers for the given topic. 128 func (t *trie) Lookup(topic string) []MessageHandler { 129 query, _ := newRoute(topic, nil) 130 var result []MessageHandler 131 132 t.RLock() 133 t.lookup(query, &result, t.root) 134 t.RUnlock() 135 return result 136 } 137 138 func (t *trie) lookupEmitter(query []string, result *[]MessageHandler, node *node) { 139 140 // Add routes from the current branch 141 for _, route := range node.routes { 142 *result = append(*result, route.Action) 143 } 144 145 // If we're not yet done, continue 146 if len(query) > 0 { 147 148 // Go through the exact match branch 149 if n, ok := node.children[query[0]]; ok { 150 t.lookupEmitter(query[1:], result, n) 151 } 152 153 // Go through wildcard match branch 154 if n, ok := node.children["+"]; ok { 155 t.lookupEmitter(query[1:], result, n) 156 } 157 } 158 } 159 160 func (t *trie) lookupMqtt(query []string, result *[]MessageHandler, node *node) { 161 if len(query) == 0 { 162 // Add routes from the current branch 163 for _, route := range node.routes { 164 *result = append(*result, route.Action) 165 } 166 } 167 168 // If we're not yet done, continue 169 if len(query) > 0 { 170 // Go through the exact match branch 171 if n, ok := node.children[query[0]]; ok { 172 t.lookupMqtt(query[1:], result, n) 173 } 174 175 // Go through wildcard match branch 176 if n, ok := node.children["+"]; ok { 177 t.lookupMqtt(query[1:], result, n) 178 } 179 180 // Go through wildcard match branch 181 if n, ok := node.children["#"]; ok { 182 for _, route := range n.routes { 183 *result = append(*result, route.Action) 184 } 185 } 186 } 187 }