forked from mirrors/gotosocial
635ad2a42f
* update codeberg.org/gruf/ libraries Signed-off-by: kim <grufwub@gmail.com> * another update Signed-off-by: kim <grufwub@gmail.com>
132 lines
3 KiB
Go
132 lines
3 KiB
Go
package nowish
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
// Start returns a new Clock instance initialized and
|
|
// started with the provided precision, along with the
|
|
// stop function for it's underlying timer
|
|
func Start(precision time.Duration) (*Clock, func()) {
|
|
c := Clock{}
|
|
return &c, c.Start(precision)
|
|
}
|
|
|
|
type Clock struct {
|
|
// format stores the time formatting style string
|
|
format string
|
|
|
|
// valid indicates whether the current value stored in .Format is valid
|
|
valid uint32
|
|
|
|
// mutex protects writes to .Format, not because it would be unsafe, but
|
|
// because we want to minimize unnnecessary allocations
|
|
mutex sync.Mutex
|
|
|
|
// nowfmt is an unsafe pointer to the last-updated time format string
|
|
nowfmt unsafe.Pointer
|
|
|
|
// now is an unsafe pointer to the last-updated time.Time object
|
|
now unsafe.Pointer
|
|
}
|
|
|
|
// Start starts the clock with the provided precision, the returned
|
|
// function is the stop function for the underlying timer. For >= 2ms,
|
|
// actual precision is usually within AT LEAST 10% of requested precision,
|
|
// less than this and the actual precision very quickly deteriorates.
|
|
func (c *Clock) Start(precision time.Duration) func() {
|
|
// Create ticker from duration
|
|
tick := time.NewTicker(precision / 10)
|
|
|
|
// Set initial time
|
|
t := time.Now()
|
|
atomic.StorePointer(&c.now, unsafe.Pointer(&t))
|
|
|
|
// Set initial format
|
|
s := ""
|
|
atomic.StorePointer(&c.nowfmt, unsafe.Pointer(&s))
|
|
|
|
// If formatting string unset, set default
|
|
c.mutex.Lock()
|
|
if c.format == "" {
|
|
c.format = time.RFC822
|
|
}
|
|
c.mutex.Unlock()
|
|
|
|
// Start main routine
|
|
go c.run(tick)
|
|
|
|
// Return stop fn
|
|
return tick.Stop
|
|
}
|
|
|
|
// run is the internal clock ticking loop.
|
|
func (c *Clock) run(tick *time.Ticker) {
|
|
for {
|
|
// Wait on tick
|
|
_, ok := <-tick.C
|
|
|
|
// Channel closed
|
|
if !ok {
|
|
break
|
|
}
|
|
|
|
// Update time
|
|
t := time.Now()
|
|
atomic.StorePointer(&c.now, unsafe.Pointer(&t))
|
|
|
|
// Invalidate format string
|
|
atomic.StoreUint32(&c.valid, 0)
|
|
}
|
|
}
|
|
|
|
// Now returns a good (ish) estimate of the current 'now' time.
|
|
func (c *Clock) Now() time.Time {
|
|
return *(*time.Time)(atomic.LoadPointer(&c.now))
|
|
}
|
|
|
|
// NowFormat returns the formatted "now" time, cached until next tick and "now" updates.
|
|
func (c *Clock) NowFormat() string {
|
|
// If format still valid, return this
|
|
if atomic.LoadUint32(&c.valid) == 1 {
|
|
return *(*string)(atomic.LoadPointer(&c.nowfmt))
|
|
}
|
|
|
|
// Get mutex lock
|
|
c.mutex.Lock()
|
|
|
|
// Double check still invalid
|
|
if atomic.LoadUint32(&c.valid) == 1 {
|
|
c.mutex.Unlock()
|
|
return *(*string)(atomic.LoadPointer(&c.nowfmt))
|
|
}
|
|
|
|
// Calculate time format
|
|
nowfmt := c.Now().Format(c.format)
|
|
|
|
// Update the stored value and set valid!
|
|
atomic.StorePointer(&c.nowfmt, unsafe.Pointer(&nowfmt))
|
|
atomic.StoreUint32(&c.valid, 1)
|
|
|
|
// Unlock and return
|
|
c.mutex.Unlock()
|
|
return nowfmt
|
|
}
|
|
|
|
// SetFormat sets the time format string used by .NowFormat().
|
|
func (c *Clock) SetFormat(format string) {
|
|
// Get mutex lock
|
|
c.mutex.Lock()
|
|
|
|
// Update time format
|
|
c.format = format
|
|
|
|
// Invalidate current format string
|
|
atomic.StoreUint32(&c.valid, 0)
|
|
|
|
// Unlock
|
|
c.mutex.Unlock()
|
|
}
|