Use hashids for player IDs, drop UUID uses

zikaeroh created

Change summary

go.mod                                 |  1 
go.sum                                 |  2 -
helpers.go                             | 14 -------
internal/game/room.go                  |  3 -
internal/protocol/protocol_easyjson.go | 12 ++----
internal/server/server.go              | 52 ++++++++++++---------------
internal/uid/uid.go                    | 40 +++++++++++++++++++++
7 files changed, 68 insertions(+), 56 deletions(-)

Detailed changes

go.mod 🔗

@@ -4,7 +4,6 @@ go 1.14
 
 require (
 	github.com/go-chi/chi v4.1.2+incompatible
-	github.com/gofrs/uuid v3.3.0+incompatible
 	github.com/jessevdk/go-flags v1.4.1-0.20181221193153-c0795c8afcf4
 	github.com/mailru/easyjson v0.7.1
 	github.com/markbates/pkger v0.17.0

go.sum 🔗

@@ -41,8 +41,6 @@ github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
-github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
-github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=

helpers.go 🔗

@@ -1,19 +1,5 @@
 package main
 
-import (
-	"reflect"
-
-	"github.com/gofrs/uuid"
-	"github.com/tomwright/queryparam/v4"
-)
-
 func stringPtr(s string) *string {
 	return &s
 }
-
-func init() {
-	queryparam.DefaultParser.ValueParsers[reflect.TypeOf(uuid.UUID{})] = func(value string, _ string) (reflect.Value, error) {
-		id, err := uuid.FromString(value)
-		return reflect.ValueOf(id), err
-	}
-}

internal/game/room.go 🔗

@@ -1,12 +1,11 @@
 package game
 
 import (
-	"github.com/gofrs/uuid"
 	"github.com/zikaeroh/codies/internal/words"
 	"github.com/zikaeroh/codies/internal/words/static"
 )
 
-type PlayerID = uuid.UUID
+type PlayerID = string
 
 type WordList struct {
 	Name   string

internal/protocol/protocol_easyjson.go 🔗

@@ -537,9 +537,7 @@ func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol6(in *jlexer.L
 		}
 		switch key {
 		case "playerID":
-			if data := in.UnsafeBytes(); in.Ok() {
-				in.AddError((out.PlayerID).UnmarshalText(data))
-			}
+			out.PlayerID = string(in.String())
 		case "nickname":
 			out.Nickname = string(in.String())
 		case "spymaster":
@@ -565,7 +563,7 @@ func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol6(out *jwriter
 	{
 		const prefix string = ",\"playerID\":"
 		out.RawString(prefix[1:])
-		out.RawText((in.PlayerID).MarshalText())
+		out.String(string(in.PlayerID))
 	}
 	{
 		const prefix string = ",\"nickname\":"
@@ -623,9 +621,7 @@ func easyjsonE4425964DecodeGithubComZikaerohCodiesInternalProtocol7(in *jlexer.L
 		}
 		switch key {
 		case "playerID":
-			if data := in.UnsafeBytes(); in.Ok() {
-				in.AddError((out.PlayerID).UnmarshalText(data))
-			}
+			out.PlayerID = string(in.String())
 		case "roomState":
 			if in.IsNull() {
 				in.Skip()
@@ -657,7 +653,7 @@ func easyjsonE4425964EncodeGithubComZikaerohCodiesInternalProtocol7(out *jwriter
 	{
 		const prefix string = ",\"playerID\":"
 		out.RawString(prefix[1:])
-		out.RawText((in.PlayerID).MarshalText())
+		out.String(string(in.PlayerID))
 	}
 	{
 		const prefix string = ",\"roomState\":"

internal/server/server.go 🔗

@@ -4,13 +4,13 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
+	"strconv"
 	"sync"
 	"time"
 
-	"github.com/gofrs/uuid"
-	"github.com/speps/go-hashids"
 	"github.com/zikaeroh/codies/internal/game"
 	"github.com/zikaeroh/codies/internal/protocol"
+	"github.com/zikaeroh/codies/internal/uid"
 	"github.com/zikaeroh/ctxjoin"
 	"github.com/zikaeroh/ctxlog"
 	"go.uber.org/atomic"
@@ -33,34 +33,30 @@ type Server struct {
 	doPrune     chan struct{}
 	ready       chan struct{}
 
-	mu sync.Mutex
+	genRoomID *uid.Generator
 
-	ctx     context.Context
+	ctx context.Context
+
+	mu      sync.Mutex
 	rooms   map[string]*Room
 	roomIDs map[string]*Room
-
-	hid    *hashids.HashID
-	nextID int64
 }
 
 func NewServer() *Server {
-	hd := hashids.NewData()
-	hd.MinLength = 8
-	hd.Salt = uuid.Must(uuid.NewV4()).String() // IDs are only valid for this server instance; ok to randomize salt.
-	hid, err := hashids.NewWithData(hd)
-	if err != nil {
-		panic(err)
-	}
-
 	return &Server{
-		ready:   make(chan struct{}),
-		doPrune: make(chan struct{}, 1),
-		rooms:   make(map[string]*Room),
-		roomIDs: make(map[string]*Room),
-		hid:     hid,
+		ready:     make(chan struct{}),
+		doPrune:   make(chan struct{}, 1),
+		genRoomID: uid.NewGenerator(salt()), // IDs are only valid for this server instance; ok to randomize salt.
+		rooms:     make(map[string]*Room),
+		roomIDs:   make(map[string]*Room),
 	}
 }
 
+func salt() string {
+	x := time.Now().Unix()
+	return strconv.FormatInt(x, 10)
+}
+
 func (s *Server) Run(ctx context.Context) error {
 	s.ctx = ctx
 
@@ -113,11 +109,7 @@ func (s *Server) CreateRoom(ctx context.Context, name, password string) (*Room,
 		return nil, ErrTooManyRooms
 	}
 
-	id, err := s.hid.EncodeInt64([]int64{s.nextID})
-	if err != nil {
-		return nil, err
-	}
-	s.nextID++
+	id, idRaw := s.genRoomID.Next()
 
 	roomCtx, roomCancel := context.WithCancel(s.ctx)
 
@@ -127,6 +119,7 @@ func (s *Server) CreateRoom(ctx context.Context, name, password string) (*Room,
 		ID:          id,
 		clientCount: &s.clientCount,
 		roomCount:   &s.roomCount,
+		genPlayerID: uid.NewGenerator(id),
 		ctx:         roomCtx,
 		cancel:      roomCancel,
 		room:        game.NewRoom(nil),
@@ -143,9 +136,9 @@ func (s *Server) CreateRoom(ctx context.Context, name, password string) (*Room,
 	s.roomCount.Inc()
 	metricRooms.Inc()
 
-	ctxlog.Info(ctx, "created new room", zap.String("name", name), zap.String("id", room.ID))
+	ctxlog.Info(ctx, "created new room", zap.String("roomName", name), zap.String("roomID", room.ID))
 
-	if s.nextID%100 == 0 {
+	if idRaw%100 == 0 {
 		s.triggerPrune()
 	}
 
@@ -207,6 +200,7 @@ type Room struct {
 	cancel      context.CancelFunc
 	clientCount *atomic.Int64
 	roomCount   *atomic.Int64
+	genPlayerID *uid.Generator
 
 	mu       sync.Mutex
 	room     *game.Room
@@ -225,12 +219,12 @@ type Room struct {
 type noteSender func(protocol.ServerNote)
 
 func (r *Room) HandleConn(ctx context.Context, nickname string, c *websocket.Conn) {
-	playerID := uuid.Must(uuid.NewV4())
+	playerID, _ := r.genPlayerID.Next()
 
 	ctx, cancel := ctxjoin.AddCancel(ctx, r.ctx)
 	defer cancel()
 
-	ctx = ctxlog.With(ctx, zap.String("roomName", r.Name), zap.String("roomID", r.ID), zap.String("nickname", nickname))
+	ctx = ctxlog.With(ctx, zap.String("roomName", r.Name), zap.String("roomID", r.ID), zap.String("playerID", playerID), zap.String("nickname", nickname))
 
 	metricClients.Inc()
 	defer metricClients.Dec()

internal/uid/uid.go 🔗

@@ -0,0 +1,40 @@
+// Package uid generates unique IDs.
+package uid
+
+import (
+	"github.com/speps/go-hashids"
+	"go.uber.org/atomic"
+)
+
+// Generator generates unique incrementing IDs. These IDs are only comparable
+// with other IDs from this generator.
+type Generator struct {
+	hid  *hashids.HashID
+	next atomic.Int64
+}
+
+// NewGenerator creates a new Generator with the specified salt. Generators
+// with the same salt generate the same IDs in order.
+func NewGenerator(salt string) *Generator {
+	hd := hashids.NewData()
+	hd.MinLength = 8
+	hd.Salt = salt
+	hid, err := hashids.NewWithData(hd)
+	if err != nil {
+		panic(err)
+	}
+
+	return &Generator{
+		hid: hid,
+	}
+}
+
+// Next gets the next ID, in both an encoded string form and the raw integer form.
+func (g *Generator) Next() (string, int64) {
+	v := g.next.Inc()
+	id, err := g.hid.EncodeInt64([]int64{v})
+	if err != nil {
+		panic(err)
+	}
+	return id, v
+}