diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb59c3c --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# Repeater + +A Go library, for easy creation of repeating function calls. Use it for things like checking updates and others periodic actions. + +It is also supports aggregation of several repeaters in one manager (see [`MultiRepeater`](#multirepeater)) + +## Exmple usage + +### StartRepeater + +```Go +package main + +import ( + "time" + + "github.com/nickname76/repeater" +) + +func main() { + stop := repeater.StartRepeater(time.Second, func() { + println(1) + time.Sleep(time.Second) + println(2) + time.Sleep(time.Second) + println(3) + }) + + time.Sleep(time.Second * 10) + + println("STOPPIN") + stop() +} + +``` + +### MultiRepeater + +```Go +package main + +import ( + "time" + + "github.com/nickname76/repeater" +) + +func main() { + mr := repeater.NewMultiRepeater[int]() + + mr.StartRepeater(1, time.Second, func() { + println(1) + }) + + mr.StartRepeater(2, time.Second, func() { + println(2) + }) + + created := mr.StartRepeater(2, time.Second, func() { + println(2) + }) + if !created { + println("oops, using same id with the second one repeater") + } + + mr.StartRepeater(3, 0, func() { + println(3) + time.Sleep(time.Second) + println(33) + }) + + time.Sleep(time.Second * 1) + + stopped := mr.StopRepeater(1) + if stopped { + println("repeater with id 1 is now stopped") + } + + time.Sleep(time.Second * 2) + + println("Now stopping repeaters with ids 2 and 3") + + mr.StopAllRepeaters() + + println("All repeaters are now stopped") + +} + +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2606f5a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/nickname76/repeater + +go 1.18 diff --git a/multirepeater.go b/multirepeater.go new file mode 100644 index 0000000..0ca8127 --- /dev/null +++ b/multirepeater.go @@ -0,0 +1,64 @@ +package repeater + +import ( + "sync" + "time" +) + +// Holds any amount of repeaters. Uses StartRepeater. Use this to manage several repeaters by ids. Use NewMultiRepeater to instance MultiRepeater. +type MultiRepeater[ID comparable] struct { + repeaters map[ID]func() + mux sync.Mutex +} + +// Instances MultiRepeater. Pass ID type you'd like to use to manage repeaters. +func NewMultiRepeater[ID comparable]() *MultiRepeater[ID] { + return &MultiRepeater[ID]{ + repeaters: make(map[ID]func()), + mux: sync.Mutex{}, + } +} + +// Works almost same as original StartRepeater, but delegates stop function managment to MultiRepeater and requires id for repeater. +// Returns `true` on success, and `false` if given id is occupied. +func (mr *MultiRepeater[ID]) StartRepeater(id ID, frequency time.Duration, fnToCall func()) bool { + mr.mux.Lock() + if mr.repeaters[id] != nil { + mr.mux.Unlock() + return false + } + mr.repeaters[id] = StartRepeater(frequency, fnToCall) + mr.mux.Unlock() + return true +} + +// Stops repeater by ID. Return `true` on success, and `false` if given id is not found. +func (mr *MultiRepeater[ID]) StopRepeater(id ID) bool { + mr.mux.Lock() + if mr.repeaters[id] == nil { + mr.mux.Unlock() + return false + } + mr.repeaters[id]() + delete(mr.repeaters, id) + mr.mux.Unlock() + return true +} + +// Stops all repeaters managed by this MultiRepeater. Waits until all repeaters are stopped. +func (mr *MultiRepeater[ID]) StopAllRepeaters() { + wg := &sync.WaitGroup{} + + mr.mux.Lock() + wg.Add(len(mr.repeaters)) + for _, stop := range mr.repeaters { + go func(stop func()) { + stop() + wg.Done() + }(stop) + } + mr.repeaters = make(map[ID]func()) + mr.mux.Unlock() + + wg.Wait() +} diff --git a/repeater.go b/repeater.go new file mode 100644 index 0000000..d0e6571 --- /dev/null +++ b/repeater.go @@ -0,0 +1,48 @@ +package repeater + +import ( + "time" +) + +// Start repeating fnToCall with given frequency, frequency can be 0, if so, ticker won't be used. +// Use returned function to stop repeater, this function waits until repeater is stopped. +func StartRepeater(frequency time.Duration, fnToCall func()) (stop func()) { + if fnToCall == nil { + panic("fnToCall must not be nil") + } + + stopCh := make(chan struct{}) + waitCh := make(chan struct{}) + + go func() { + defer close(waitCh) + + if frequency != 0 { + ticker := time.NewTicker(frequency) + + for { + select { + case <-stopCh: + ticker.Stop() + return + case <-ticker.C: + fnToCall() + } + } + } else { + for { + select { + case <-stopCh: + return + default: + fnToCall() + } + } + } + }() + + return func() { + close(stopCh) + <-waitCh + } +}