github.com/livekit/protocol@v1.16.1-0.20240517185851-47e4c6bba773/utils/timed_version.go (about) 1 // Copyright 2023 LiveKit, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package utils 16 17 import ( 18 "database/sql/driver" 19 "encoding/binary" 20 "errors" 21 "fmt" 22 "sync" 23 "time" 24 25 "github.com/livekit/protocol/livekit" 26 ) 27 28 const tickBits uint64 = 13 29 const tickMask uint64 = (1 << tickBits) - 1 30 31 var epoch = time.Date(2000, 0, 0, 0, 0, 0, 0, time.UTC).UnixMicro() 32 33 type TimedVersionGenerator interface { 34 Next() TimedVersion 35 } 36 37 func timedVersionComponents(v TimedVersion) (ts int64, ticks int32) { 38 return int64(v>>tickBits) + epoch, int32(uint64(v) & tickMask) 39 } 40 41 func timedVersionFromComponents(ts int64, ticks int32) TimedVersion { 42 if ts < epoch { 43 ts = epoch 44 } 45 return TimedVersion((uint64(ts-epoch) << tickBits) | uint64(ticks)) 46 } 47 48 type timedVersionGenerator struct { 49 mu sync.Mutex 50 ts int64 51 ticks uint64 52 } 53 54 func NewDefaultTimedVersionGenerator() TimedVersionGenerator { 55 return new(timedVersionGenerator) 56 } 57 58 func (g *timedVersionGenerator) New() *TimedVersion { 59 v := g.Next() 60 return &v 61 } 62 63 func (g *timedVersionGenerator) Next() TimedVersion { 64 ts := time.Now().UnixMicro() 65 66 g.mu.Lock() 67 defer g.mu.Unlock() 68 69 for { 70 if ts < g.ts { 71 ts = g.ts 72 } 73 if g.ts == ts { 74 // if incrementing the ticks would overflow the version sleep for a 75 // microsecond then try again. 76 if g.ticks == tickMask { 77 time.Sleep(time.Microsecond) 78 ts = time.Now().UnixMicro() 79 continue 80 } 81 g.ticks++ 82 } else { 83 g.ts = ts 84 g.ticks = 0 85 } 86 return timedVersionFromComponents(g.ts, int32(g.ticks)) 87 } 88 } 89 90 type TimedVersion uint64 91 92 func TimedVersionFromProto(proto *livekit.TimedVersion) TimedVersion { 93 return timedVersionFromComponents(proto.GetUnixMicro(), proto.GetTicks()) 94 } 95 96 func TimedVersionFromTime(t time.Time) TimedVersion { 97 return timedVersionFromComponents(t.UnixMicro(), 0) 98 } 99 100 func (t *TimedVersion) Update(other TimedVersion) bool { 101 return t.Upgrade(other) 102 } 103 104 func (t *TimedVersion) Upgrade(other TimedVersion) bool { 105 if *t < other { 106 *t = other 107 return true 108 } 109 return false 110 } 111 112 func (t *TimedVersion) Downgrade(other TimedVersion) bool { 113 if *t > other { 114 *t = other 115 return true 116 } 117 return false 118 } 119 120 func (t *TimedVersion) Store(other TimedVersion) { 121 *t = other 122 } 123 124 func (t TimedVersion) Load() TimedVersion { 125 return t 126 } 127 128 func (t TimedVersion) After(other TimedVersion) bool { 129 return t > other 130 } 131 132 func (t TimedVersion) Compare(other TimedVersion) int { 133 if t < other { 134 return -1 135 } 136 if t == other { 137 return 0 138 } 139 return 1 140 } 141 142 func (t TimedVersion) IsZero() bool { 143 return t == 0 144 } 145 146 func (t TimedVersion) ToProto() *livekit.TimedVersion { 147 ts, ticks := timedVersionComponents(t) 148 return &livekit.TimedVersion{ 149 UnixMicro: ts, 150 Ticks: ticks, 151 } 152 } 153 154 func (t TimedVersion) Time() time.Time { 155 ts, _ := timedVersionComponents(t) 156 return time.UnixMicro(ts) 157 } 158 159 func (t TimedVersion) String() string { 160 ts, ticks := timedVersionComponents(t) 161 return fmt.Sprintf("%d.%d", ts, ticks) 162 } 163 164 func (t TimedVersion) Value() (driver.Value, error) { 165 if t.IsZero() { 166 return nil, nil 167 } 168 169 ts, ticks := timedVersionComponents(t) 170 b := make([]byte, 0, 12) 171 b = binary.BigEndian.AppendUint64(b, uint64(ts)) 172 b = binary.BigEndian.AppendUint32(b, uint32(ticks)) 173 return b, nil 174 } 175 176 func (t *TimedVersion) Scan(src interface{}) (err error) { 177 switch b := src.(type) { 178 case []byte: 179 switch len(b) { 180 case 0: 181 *t = 0 182 case 12: 183 ts := int64(binary.BigEndian.Uint64(b)) 184 ticks := int32(binary.BigEndian.Uint32(b[8:])) 185 *t = timedVersionFromComponents(ts, ticks) 186 default: 187 return errors.New("(*TimedVersion).Scan: unsupported format") 188 } 189 case nil: 190 *t = 0 191 default: 192 return errors.New("(*TimedVersion).Scan: unsupported data type") 193 } 194 return nil 195 } 196 197 func (t TimedVersion) GormDataType() string { 198 return "bytes" 199 }