mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-29 04:22:24 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			141 lines
		
	
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
	
		
			3.2 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 {
 | |
| 	noCopy noCopy //nolint noCopy because a copy will fuck with atomics
 | |
| 
 | |
| 	// 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
 | |
| 
 | |
| 	// Format is an unsafe pointer to the last-updated time format string
 | |
| 	Format unsafe.Pointer
 | |
| 
 | |
| 	// Time is an unsafe pointer to the last-updated time.Time object
 | |
| 	Time unsafe.Pointer
 | |
| }
 | |
| 
 | |
| // Start starts the clock with the provided precision, the
 | |
| // returned function is the stop function for the underlying timer
 | |
| func (c *Clock) Start(precision time.Duration) func() {
 | |
| 	// Create ticker from duration
 | |
| 	tick := time.NewTicker(precision)
 | |
| 
 | |
| 	// Set initial time
 | |
| 	t := time.Now()
 | |
| 	atomic.StorePointer(&c.Time, unsafe.Pointer(&t))
 | |
| 
 | |
| 	// Set initial format
 | |
| 	s := ""
 | |
| 	atomic.StorePointer(&c.Format, 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.Time, 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.Time))
 | |
| }
 | |
| 
 | |
| // 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.Format))
 | |
| 	}
 | |
| 
 | |
| 	// Get mutex lock
 | |
| 	c.mutex.Lock()
 | |
| 
 | |
| 	// Double check still invalid
 | |
| 	if atomic.LoadUint32(&c.valid) == 1 {
 | |
| 		c.mutex.Unlock()
 | |
| 		return *(*string)(atomic.LoadPointer(&c.Format))
 | |
| 	}
 | |
| 
 | |
| 	// Calculate time format
 | |
| 	b := c.Now().AppendFormat(
 | |
| 		make([]byte, 0, len(c.format)),
 | |
| 		c.format,
 | |
| 	)
 | |
| 
 | |
| 	// Update the stored value and set valid!
 | |
| 	atomic.StorePointer(&c.Format, unsafe.Pointer(&b))
 | |
| 	atomic.StoreUint32(&c.valid, 1)
 | |
| 
 | |
| 	// Unlock and return
 | |
| 	c.mutex.Unlock()
 | |
| 
 | |
| 	// Note:
 | |
| 	// it's safe to do this conversion here
 | |
| 	// because this byte slice will never change.
 | |
| 	// and we have the direct pointer to it, we're
 | |
| 	// not requesting it atomicly via c.Format
 | |
| 	return *(*string)(unsafe.Pointer(&b))
 | |
| }
 | |
| 
 | |
| // 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()
 | |
| }
 |