Skip to content

Commit

Permalink
feat(examples): add p/demo/seqid (#1378)
Browse files Browse the repository at this point in the history
A very simple ID generation package, designed to be used in combination
with `avl.Tree`s to push values in order.

The name was originally `seqid` (sequential IDs), but after saying it a
few times I realised it was close to "squid" and probably would be more
fun if I named it that way ;)

There's another piece of functionality that I want to add, which is a
way to create simple base32-encoded IDs. This depends on #1290. These
would also guarantee alphabetical ordering, so a list of them can be
easily sorted and you'd get it in the same order they were created. They
would likely be 13 characters long, but I'm also thinking of making a
compact version which works from [0,2^35) which is 7 chracters, and then
smoothly transitions over to the 13 characters version when the ID is
reached.

(I've experience with both base64 and base32 encoded IDs as 64-bit
numbers, as I used both systems. The advantage of base32 is that it
makes IDs case insensitive, all the while being at most 13 bytes instead
of 11 for base64.)

In GnoChess, we used simple sequential IDs combined with
[`zeroPad9`](https://github.com/gnolang/gnochess/blob/7e841191a4a0a94c0d46bc977458bd6b757eab5e/realm/chess.gno#L287-L296)
to create IDs which were both readable and sortable. I want to make a
more "canonical" solution to this which does not have a upper limit at 1
billion entries.
  • Loading branch information
thehowl authored Nov 16, 2023
1 parent 7d66398 commit 24d89a4
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 0 deletions.
36 changes: 36 additions & 0 deletions examples/gno.land/p/demo/seqid/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# seqid

```
package seqid // import "gno.land/p/demo/seqid"
Package seqid provides a simple way to have sequential IDs which will be ordered
correctly when inserted in an AVL tree.
Sample usage:
var id seqid.ID
var users avl.Tree
func NewUser() {
users.Set(id.Next().Binary(), &User{ ... })
}
TYPES
type ID uint64
An ID is a simple sequential ID generator.
func FromBinary(b string) (ID, bool)
FromBinary creates a new ID from the given string.
func (i ID) Binary() string
Binary returns a big-endian binary representation of the ID, suitable to be
used as an AVL key.
func (i *ID) Next() ID
Next advances the ID i. It will panic if increasing ID would overflow.
func (i *ID) TryNext() (ID, bool)
TryNext increases i by 1 and returns its value. It returns true if
successful, or false if the increment would result in an overflow.
```
1 change: 1 addition & 0 deletions examples/gno.land/p/demo/seqid/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/demo/seqid
57 changes: 57 additions & 0 deletions examples/gno.land/p/demo/seqid/seqid.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Package seqid provides a simple way to have sequential IDs which will be
// ordered correctly when inserted in an AVL tree.
//
// Sample usage:
//
// var id seqid.ID
// var users avl.Tree
//
// func NewUser() {
// users.Set(id.Next().Binary(), &User{ ... })
// }
package seqid

import "encoding/binary"

// An ID is a simple sequential ID generator.
type ID uint64

// Next advances the ID i.
// It will panic if increasing ID would overflow.
func (i *ID) Next() ID {
next, ok := i.TryNext()
if !ok {
panic("seqid: next ID overflows uint64")
}
return next
}

const maxID ID = 1<<64 - 1

// TryNext increases i by 1 and returns its value.
// It returns true if successful, or false if the increment would result in
// an overflow.
func (i *ID) TryNext() (ID, bool) {
if *i == maxID {
// Addition will overflow.
return 0, false
}
*i++
return *i, true
}

// Binary returns a big-endian binary representation of the ID,
// suitable to be used as an AVL key.
func (i ID) Binary() string {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(i))
return string(buf)
}

// FromBinary creates a new ID from the given string.
func FromBinary(b string) (ID, bool) {
if len(b) != 8 {
return 0, false
}
return ID(binary.BigEndian.Uint64([]byte(b))), true
}
43 changes: 43 additions & 0 deletions examples/gno.land/p/demo/seqid/seqid_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package seqid

import (
"fmt"
"strings"
"testing"
)

func TestID(t *testing.T) {
var i ID

for j := 0; j < 100; j++ {
i.Next()
}
if i != 100 {
t.Fatalf("invalid: wanted %d got %d", 100, i)
}
}

func TestID_Overflow(t *testing.T) {
i := ID(maxID)

defer func() {
err := recover()
if !strings.Contains(fmt.Sprint(err), "next ID overflows") {
t.Errorf("did not overflow")
}
}()

i.Next()
}

func TestID_Binary(t *testing.T) {
var i ID
prev := i.Binary()

for j := 0; j < 1000; j++ {
cur := i.Next().Binary()
if cur <= prev {
t.Fatalf("cur %x <= prev %x", cur, prev)
}
}
}

0 comments on commit 24d89a4

Please sign in to comment.