gobot.io/x/gobot/v2@v2.1.0/robot_work.go (about) 1 package gobot 2 3 import ( 4 "context" 5 "fmt" 6 "time" 7 8 "sync" 9 10 "github.com/gofrs/uuid" 11 ) 12 13 // RobotWorkRegistry contains all the work units registered on a Robot 14 type RobotWorkRegistry struct { 15 sync.RWMutex 16 17 r map[string]*RobotWork 18 } 19 20 const ( 21 EveryWorkKind = "every" 22 AfterWorkKind = "after" 23 ) 24 25 // RobotWork and the RobotWork registry represent units of executing computation 26 // managed at the Robot level. Unlike the utility functions gobot.After and gobot.Every, 27 // RobotWork units require a context.Context, and can be cancelled externally by calling code. 28 // 29 // Usage: 30 // 31 // someWork := myRobot.Every(context.Background(), time.Second * 2, func(){ 32 // fmt.Println("Here I am doing work") 33 // }) 34 // 35 // someWork.CallCancelFunc() // Cancel next tick and remove from work registry 36 // 37 // goroutines for Every and After are run on their own WaitGroups for synchronization: 38 // 39 // someWork2 := myRobot.Every(context.Background(), time.Second * 2, func(){ 40 // fmt.Println("Here I am doing more work") 41 // }) 42 // 43 // somework2.CallCancelFunc() 44 // 45 // // wait for both Every calls to finish 46 // robot.WorkEveryWaitGroup().Wait() 47 type RobotWork struct { 48 id uuid.UUID 49 kind string 50 tickCount int 51 ctx context.Context 52 cancelFunc context.CancelFunc 53 function func() 54 ticker *time.Ticker 55 duration time.Duration 56 } 57 58 // ID returns the UUID of the RobotWork 59 func (rw *RobotWork) ID() uuid.UUID { 60 return rw.id 61 } 62 63 // CancelFunc returns the context.CancelFunc used to cancel the work 64 func (rw *RobotWork) CancelFunc() context.CancelFunc { 65 return rw.cancelFunc 66 } 67 68 // CallCancelFunc calls the context.CancelFunc used to cancel the work 69 func (rw *RobotWork) CallCancelFunc() { 70 rw.cancelFunc() 71 } 72 73 // Ticker returns the time.Ticker used in an Every so that calling code can sync on the same channel 74 func (rw *RobotWork) Ticker() *time.Ticker { 75 if rw.kind == AfterWorkKind { 76 return nil 77 } 78 return rw.ticker 79 } 80 81 // TickCount returns the number of times the function successfully ran 82 func (rw *RobotWork) TickCount() int { 83 return rw.tickCount 84 } 85 86 // Duration returns the timeout until an After fires or the period of an Every 87 func (rw *RobotWork) Duration() time.Duration { 88 return rw.duration 89 } 90 91 func (rw *RobotWork) String() string { 92 format := `ID: %s 93 Kind: %s 94 TickCount: %d 95 96 ` 97 return fmt.Sprintf(format, rw.id, rw.kind, rw.tickCount) 98 } 99 100 // WorkRegistry returns the Robot's WorkRegistry 101 func (r *Robot) WorkRegistry() *RobotWorkRegistry { 102 return r.workRegistry 103 } 104 105 // Every calls the given function for every tick of the provided duration. 106 func (r *Robot) Every(ctx context.Context, d time.Duration, f func()) *RobotWork { 107 rw := r.workRegistry.registerEvery(ctx, d, f) 108 r.WorkEveryWaitGroup.Add(1) 109 go func() { 110 EVERYWORK: 111 for { 112 select { 113 case <-rw.ctx.Done(): 114 r.workRegistry.delete(rw.id) 115 rw.ticker.Stop() 116 break EVERYWORK 117 case <-rw.ticker.C: 118 f() 119 rw.tickCount++ 120 } 121 } 122 r.WorkEveryWaitGroup.Done() 123 }() 124 return rw 125 } 126 127 // After calls the given function after the provided duration has elapsed 128 func (r *Robot) After(ctx context.Context, d time.Duration, f func()) *RobotWork { 129 rw := r.workRegistry.registerAfter(ctx, d, f) 130 ch := time.After(d) 131 r.WorkAfterWaitGroup.Add(1) 132 go func() { 133 AFTERWORK: 134 for { 135 select { 136 case <-rw.ctx.Done(): 137 r.workRegistry.delete(rw.id) 138 break AFTERWORK 139 case <-ch: 140 f() 141 } 142 } 143 r.WorkAfterWaitGroup.Done() 144 }() 145 return rw 146 } 147 148 // Get returns the RobotWork specified by the provided ID. To delete something from the registry, it's 149 // necessary to call its context.CancelFunc, which will perform a goroutine-safe delete on the underlying 150 // map. 151 func (rwr *RobotWorkRegistry) Get(id uuid.UUID) *RobotWork { 152 rwr.Lock() 153 defer rwr.Unlock() 154 return rwr.r[id.String()] 155 } 156 157 // Delete returns the RobotWork specified by the provided ID 158 func (rwr *RobotWorkRegistry) delete(id uuid.UUID) { 159 rwr.Lock() 160 defer rwr.Unlock() 161 delete(rwr.r, id.String()) 162 } 163 164 // registerAfter creates a new unit of RobotWork and sets up its context/cancellation 165 func (rwr *RobotWorkRegistry) registerAfter(ctx context.Context, d time.Duration, f func()) *RobotWork { 166 rwr.Lock() 167 defer rwr.Unlock() 168 169 id, _ := uuid.NewV4() 170 rw := &RobotWork{ 171 id: id, 172 kind: AfterWorkKind, 173 function: f, 174 duration: d, 175 } 176 177 rw.ctx, rw.cancelFunc = context.WithCancel(ctx) 178 rwr.r[id.String()] = rw 179 return rw 180 } 181 182 // registerEvery creates a new unit of RobotWork and sets up its context/cancellation 183 func (rwr *RobotWorkRegistry) registerEvery(ctx context.Context, d time.Duration, f func()) *RobotWork { 184 rwr.Lock() 185 defer rwr.Unlock() 186 187 id, _ := uuid.NewV4() 188 rw := &RobotWork{ 189 id: id, 190 kind: EveryWorkKind, 191 function: f, 192 duration: d, 193 ticker: time.NewTicker(d), 194 } 195 196 rw.ctx, rw.cancelFunc = context.WithCancel(ctx) 197 198 rwr.r[id.String()] = rw 199 return rw 200 }