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  }