github.com/whoyao/protocol@v0.0.0-20230519045905-2d8ace718ca5/utils/timed_version.go (about)

     1  package utils
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	"go.uber.org/atomic"
     9  
    10  	"github.com/whoyao/protocol/livekit"
    11  )
    12  
    13  const tickBits uint64 = 13
    14  const tickMask uint64 = (1 << tickBits) - 1
    15  
    16  var epoch = time.Date(2000, 0, 0, 0, 0, 0, 0, time.UTC).UnixMicro()
    17  
    18  type TimedVersionGenerator interface {
    19  	New() *TimedVersion
    20  	Next() TimedVersion
    21  }
    22  
    23  func timedVersionComponents(v uint64) (ts int64, ticks int32) {
    24  	return int64(v>>tickBits) + epoch, int32(v & tickMask)
    25  }
    26  
    27  func timedVersionFromComponents(ts int64, ticks int32) TimedVersion {
    28  	if ts < epoch {
    29  		ts = epoch
    30  	}
    31  	return TimedVersion{v: *atomic.NewUint64((uint64(ts-epoch) << tickBits) | uint64(ticks))}
    32  }
    33  
    34  type timedVersionGenerator struct {
    35  	mu    sync.Mutex
    36  	ts    int64
    37  	ticks uint64
    38  }
    39  
    40  func NewDefaultTimedVersionGenerator() TimedVersionGenerator {
    41  	return new(timedVersionGenerator)
    42  }
    43  
    44  func (g *timedVersionGenerator) New() *TimedVersion {
    45  	v := g.Next()
    46  	return &v
    47  }
    48  
    49  func (g *timedVersionGenerator) Next() TimedVersion {
    50  	ts := time.Now().UnixMicro()
    51  
    52  	g.mu.Lock()
    53  	defer g.mu.Unlock()
    54  
    55  	for {
    56  		if ts < g.ts {
    57  			ts = g.ts
    58  		}
    59  		if g.ts == ts {
    60  			// if incrementing the ticks would overflow the version sleep for a
    61  			// microsecond then try again.
    62  			if g.ticks == tickMask {
    63  				time.Sleep(time.Microsecond)
    64  				ts = time.Now().UnixMicro()
    65  				continue
    66  			}
    67  			g.ticks++
    68  		} else {
    69  			g.ts = ts
    70  			g.ticks = 0
    71  		}
    72  		return timedVersionFromComponents(g.ts, int32(g.ticks))
    73  	}
    74  }
    75  
    76  type TimedVersion struct {
    77  	v atomic.Uint64
    78  }
    79  
    80  func NewTimedVersionFromProto(proto *livekit.TimedVersion) *TimedVersion {
    81  	v := timedVersionFromComponents(proto.GetUnixMicro(), proto.GetTicks())
    82  	return &v
    83  }
    84  
    85  func NewTimedVersionFromTime(t time.Time) *TimedVersion {
    86  	v := timedVersionFromComponents(t.UnixMicro(), 0)
    87  	return &v
    88  }
    89  
    90  func TimedVersionFromProto(proto *livekit.TimedVersion) TimedVersion {
    91  	return timedVersionFromComponents(proto.GetUnixMicro(), proto.GetTicks())
    92  }
    93  
    94  func TimedVersionFromTime(t time.Time) TimedVersion {
    95  	return timedVersionFromComponents(t.UnixMicro(), 0)
    96  }
    97  
    98  func (t *TimedVersion) Update(other *TimedVersion) bool {
    99  	ov := other.v.Load()
   100  	for {
   101  		prev := t.v.Load()
   102  		if ov <= prev {
   103  			return false
   104  		}
   105  		if t.v.CompareAndSwap(prev, ov) {
   106  			return true
   107  		}
   108  	}
   109  }
   110  
   111  func (t *TimedVersion) Store(other *TimedVersion) {
   112  	t.v.Store(other.v.Load())
   113  }
   114  
   115  func (t *TimedVersion) Load() TimedVersion {
   116  	return TimedVersion{v: *atomic.NewUint64(t.v.Load())}
   117  }
   118  
   119  func (t *TimedVersion) After(other *TimedVersion) bool {
   120  	return t.v.Load() > other.v.Load()
   121  }
   122  
   123  func (t *TimedVersion) Compare(other *TimedVersion) int {
   124  	ov := other.v.Load()
   125  	v := t.v.Load()
   126  	if v < ov {
   127  		return -1
   128  	}
   129  	if v == ov {
   130  		return 0
   131  	}
   132  	return 1
   133  }
   134  
   135  func (t *TimedVersion) IsZero() bool {
   136  	return t.v.Load() == 0
   137  }
   138  
   139  func (t *TimedVersion) ToProto() *livekit.TimedVersion {
   140  	ts, ticks := timedVersionComponents(t.v.Load())
   141  	return &livekit.TimedVersion{
   142  		UnixMicro: ts,
   143  		Ticks:     ticks,
   144  	}
   145  }
   146  
   147  func (t *TimedVersion) Time() time.Time {
   148  	ts, _ := timedVersionComponents(t.v.Load())
   149  	return time.UnixMicro(ts)
   150  }
   151  
   152  func (t *TimedVersion) String() string {
   153  	ts, ticks := timedVersionComponents(t.v.Load())
   154  	return fmt.Sprintf("%d.%d", ts, ticks)
   155  }