Files
sumi/internal/ids/ids.go
2025-12-15 01:25:28 -06:00

94 lines
1.8 KiB
Go

// k ordered id generator
package ids
import (
"errors"
"sync"
"time"
)
const (
// 2024-07-03T00:00:00Z
customEpochMs int64 = 1719964800000
seqBits = 8
workerBits = 16
timeBits = 40
seqMask = (1 << seqBits) - 1 // 0xFF
workerMask = (1 << workerBits) - 1 // 0xFFFF
timeMask = (int64(1) << timeBits) - 1 // low 40 bits
workerShift = seqBits
timeShift = workerBits + seqBits
)
// Generator is safe for concurrent use.
type Generator struct {
workerID uint64
mu sync.Mutex
lastMs int64
sequence uint64
}
func NewGenerator(workerID uint32) (*Generator, error) {
if workerID > workerMask {
return nil, errors.New("workerID too large for 16 bits")
}
return &Generator{workerID: uint64(workerID)}, nil
}
func (g *Generator) Next() (uint64, error) {
nowMs := time.Now().UTC().UnixMilli()
delta := nowMs - customEpochMs
if delta < 0 {
return 0, errors.New("time is before custom epoch")
}
if delta > timeMask {
return 0, errors.New("timestamp overflow (40-bit ms range exceeded)")
}
g.mu.Lock()
defer g.mu.Unlock()
if delta == g.lastMs {
g.sequence = (g.sequence + 1) & seqMask
if g.sequence == 0 {
// sequence wrapped; wait for next millisecond
for delta == g.lastMs {
nowMs = time.Now().UTC().UnixMilli()
delta = nowMs - customEpochMs
}
}
} else {
g.sequence = 0
g.lastMs = delta
}
id := (uint64(delta) << timeShift) |
((g.workerID & workerMask) << workerShift) |
(g.sequence & seqMask)
return id, nil
}
// ---- Base62 ----
const base62Alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
func Base62Encode(x uint64) string {
if x == 0 {
return "0"
}
var buf [11]byte // 62^11 > 2^64, so max 11 chars
i := len(buf)
for x > 0 {
r := x % 62
x /= 62
i--
buf[i] = base62Alphabet[r]
}
return string(buf[i:])
}