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  }