github.com/grahambrereton-form3/tilt@v0.10.18/internal/store/subscriber.go (about) 1 package store 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 ) 8 9 // A subscriber is notified whenever the state changes. 10 // 11 // Subscribers do not need to be thread-safe. The Store will only 12 // call OnChange for a given subscriber when the last call completes. 13 // 14 // Subscribers are only allowed to read state. If they want to 15 // modify state, they should call store.Dispatch() 16 type Subscriber interface { 17 OnChange(ctx context.Context, st RStore) 18 } 19 20 // Some subscribers need to do SetUp or TearDown. 21 // Both hold the subscriber lock, so should return quickly. 22 type SetUpper interface { 23 SetUp(ctx context.Context) 24 } 25 type TearDowner interface { 26 TearDown(ctx context.Context) 27 } 28 29 // Convenience interface for subscriber fulfilling both SetUpper and TearDowner 30 type SubscriberLifecycle interface { 31 SetUpper 32 TearDowner 33 } 34 35 type subscriberList struct { 36 subscribers []*subscriberEntry 37 setup bool 38 mu sync.Mutex 39 } 40 41 func (l *subscriberList) Add(ctx context.Context, s Subscriber) { 42 l.mu.Lock() 43 defer l.mu.Unlock() 44 45 e := &subscriberEntry{ 46 subscriber: s, 47 dirtyBit: NewDirtyBit(), 48 } 49 l.subscribers = append(l.subscribers, e) 50 if l.setup { 51 // the rest of the subscriberList has already been set up, so set up this subscriber directly 52 e.maybeSetUp(ctx) 53 } 54 } 55 56 func (l *subscriberList) Remove(ctx context.Context, s Subscriber) error { 57 l.mu.Lock() 58 defer l.mu.Unlock() 59 60 for i, current := range l.subscribers { 61 if s == current.subscriber { 62 l.subscribers = append(l.subscribers[:i], l.subscribers[i+1:]...) 63 if l.setup { 64 current.maybeTeardown(ctx) 65 } 66 return nil 67 } 68 } 69 70 return fmt.Errorf("Subscriber not found: %T: %+v", s, s) 71 } 72 73 func (l *subscriberList) SetUp(ctx context.Context) { 74 l.mu.Lock() 75 subscribers := append([]*subscriberEntry{}, l.subscribers...) 76 l.setup = true 77 l.mu.Unlock() 78 79 for _, s := range subscribers { 80 s.maybeSetUp(ctx) 81 } 82 } 83 84 func (l *subscriberList) TeardownAll(ctx context.Context) { 85 l.mu.Lock() 86 subscribers := append([]*subscriberEntry{}, l.subscribers...) 87 l.setup = false 88 l.mu.Unlock() 89 90 for _, s := range subscribers { 91 s.maybeTeardown(ctx) 92 } 93 } 94 95 func (l *subscriberList) NotifyAll(ctx context.Context, store *Store) { 96 l.mu.Lock() 97 subscribers := append([]*subscriberEntry{}, l.subscribers...) 98 l.mu.Unlock() 99 100 for _, s := range subscribers { 101 s.dirtyBit.MarkDirty() 102 103 go s.notify(ctx, store) 104 } 105 } 106 107 type subscriberEntry struct { 108 subscriber Subscriber 109 mu sync.Mutex 110 dirtyBit *DirtyBit 111 } 112 113 func (e *subscriberEntry) notify(ctx context.Context, store *Store) { 114 e.mu.Lock() 115 defer e.mu.Unlock() 116 117 startToken, isDirty := e.dirtyBit.StartBuildIfDirty() 118 if !isDirty { 119 return 120 } 121 122 e.subscriber.OnChange(ctx, store) 123 e.dirtyBit.FinishBuild(startToken) 124 } 125 126 func (e *subscriberEntry) maybeSetUp(ctx context.Context) { 127 s, ok := e.subscriber.(SetUpper) 128 if ok { 129 e.mu.Lock() 130 defer e.mu.Unlock() 131 s.SetUp(ctx) 132 } 133 } 134 135 func (e *subscriberEntry) maybeTeardown(ctx context.Context) { 136 s, ok := e.subscriber.(TearDowner) 137 if ok { 138 e.mu.Lock() 139 defer e.mu.Unlock() 140 s.TearDown(ctx) 141 } 142 }