github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/internal/eventlog/cursor/cursor.go (about) 1 // Package cursor implements time-ordered item cursors for an event log. 2 package cursor 3 4 import ( 5 "errors" 6 "fmt" 7 "strconv" 8 "strings" 9 "time" 10 ) 11 12 // A Source produces cursors based on a time index generator and a sequence 13 // counter. A zero-valued Source is ready for use with defaults as described. 14 type Source struct { 15 // This function is called to produce the current time index. 16 // If nil, it defaults to time.Now().UnixNano(). 17 TimeIndex func() int64 18 19 // The current counter value used for sequence number generation. It is 20 // incremented in-place each time a cursor is generated. 21 Counter int64 22 } 23 24 func (s *Source) timeIndex() int64 { 25 if s.TimeIndex == nil { 26 return time.Now().UnixNano() 27 } 28 return s.TimeIndex() 29 } 30 31 func (s *Source) nextCounter() int64 { 32 s.Counter++ 33 return s.Counter 34 } 35 36 // Cursor produces a fresh cursor from s at the current time index and counter. 37 func (s *Source) Cursor() Cursor { 38 return Cursor{ 39 timestamp: uint64(s.timeIndex()), 40 sequence: uint16(s.nextCounter() & 0xffff), 41 } 42 } 43 44 // A Cursor is a unique identifier for an item in a time-ordered event log. 45 // It is safe to copy and compare cursors by value. 46 type Cursor struct { 47 timestamp uint64 // ns since Unix epoch 48 sequence uint16 // sequence number 49 } 50 51 // Before reports whether c is prior to o in time ordering. This comparison 52 // ignores sequence numbers. 53 func (c Cursor) Before(o Cursor) bool { return c.timestamp < o.timestamp } 54 55 // Diff returns the time duration between c and o. The duration is negative if 56 // c is before o in time order. 57 func (c Cursor) Diff(o Cursor) time.Duration { 58 return time.Duration(c.timestamp) - time.Duration(o.timestamp) 59 } 60 61 // IsZero reports whether c is the zero cursor. 62 func (c Cursor) IsZero() bool { return c == Cursor{} } 63 64 // MarshalText implements the encoding.TextMarshaler interface. 65 // A zero cursor marshals as "", otherwise the format used by the String method. 66 func (c Cursor) MarshalText() ([]byte, error) { 67 if c.IsZero() { 68 return nil, nil 69 } 70 return []byte(c.String()), nil 71 } 72 73 // UnmarshalText implements the encoding.TextUnmarshaler interface. 74 // An empty text unmarshals without error to a zero cursor. 75 func (c *Cursor) UnmarshalText(data []byte) error { 76 if len(data) == 0 { 77 *c = Cursor{} // set zero 78 return nil 79 } 80 ps := strings.SplitN(string(data), "-", 2) 81 if len(ps) != 2 { 82 return errors.New("invalid cursor format") 83 } 84 ts, err := strconv.ParseUint(ps[0], 16, 64) 85 if err != nil { 86 return fmt.Errorf("invalid timestamp: %w", err) 87 } 88 sn, err := strconv.ParseUint(ps[1], 16, 16) 89 if err != nil { 90 return fmt.Errorf("invalid sequence: %w", err) 91 } 92 c.timestamp = ts 93 c.sequence = uint16(sn) 94 return nil 95 } 96 97 // String returns a printable text representation of a cursor. 98 func (c Cursor) String() string { 99 return fmt.Sprintf("%016x-%04x", c.timestamp, c.sequence) 100 }