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 }