mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-06 06:10:54 +00:00
Source cleanup, and some string copies
This commit is contained in:
parent
387ada9c9c
commit
7654dcdf8d
10 changed files with 47 additions and 89 deletions
|
@ -17,7 +17,7 @@ import (
|
||||||
// The Commands sent from Client -> Server and Server -> Client are disjoint sets.
|
// The Commands sent from Client -> Server and Server -> Client are disjoint sets.
|
||||||
type Command string
|
type Command string
|
||||||
|
|
||||||
// CommandHandler is a RPC handler assosciated with a Command.
|
// CommandHandler is a RPC handler associated with a Command.
|
||||||
type CommandHandler func(*websocket.Conn, *ClientInfo, ClientMessage) (ClientMessage, error)
|
type CommandHandler func(*websocket.Conn, *ClientInfo, ClientMessage) (ClientMessage, error)
|
||||||
|
|
||||||
var commandHandlers = map[Command]CommandHandler{
|
var commandHandlers = map[Command]CommandHandler{
|
||||||
|
@ -43,10 +43,10 @@ var commandHandlers = map[Command]CommandHandler{
|
||||||
|
|
||||||
func internCommands() {
|
func internCommands() {
|
||||||
CommandPool = NewStringPool()
|
CommandPool = NewStringPool()
|
||||||
CommandPool._Intern_Setup(HelloCommand)
|
CommandPool._Intern_Setup(string(HelloCommand))
|
||||||
CommandPool._Intern_Setup("ping")
|
CommandPool._Intern_Setup("ping")
|
||||||
CommandPool._Intern_Setup(SetUserCommand)
|
CommandPool._Intern_Setup(string(SetUserCommand))
|
||||||
CommandPool._Intern_Setup(ReadyCommand)
|
CommandPool._Intern_Setup(string(ReadyCommand))
|
||||||
CommandPool._Intern_Setup("sub")
|
CommandPool._Intern_Setup("sub")
|
||||||
CommandPool._Intern_Setup("unsub")
|
CommandPool._Intern_Setup("unsub")
|
||||||
CommandPool._Intern_Setup("track_follow")
|
CommandPool._Intern_Setup("track_follow")
|
||||||
|
@ -155,8 +155,8 @@ func C2SSetUser(conn *websocket.Conn, client *ClientInfo, msg ClientMessage) (rm
|
||||||
}
|
}
|
||||||
|
|
||||||
client.Mutex.Lock()
|
client.Mutex.Lock()
|
||||||
client.TwitchUsername = username
|
|
||||||
client.UsernameValidated = false
|
client.UsernameValidated = false
|
||||||
|
client.TwitchUsername = username
|
||||||
client.Mutex.Unlock()
|
client.Mutex.Unlock()
|
||||||
|
|
||||||
if Configuration.SendAuthToNewClients {
|
if Configuration.SendAuthToNewClients {
|
||||||
|
@ -262,7 +262,7 @@ func C2STrackFollow(conn *websocket.Conn, client *ClientInfo, msg ClientMessage)
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
followEventsLock.Lock()
|
followEventsLock.Lock()
|
||||||
followEvents = append(followEvents, followEvent{client.TwitchUsername, channel, following, now})
|
followEvents = append(followEvents, followEvent{User: client.TwitchUsername, Channel: channel, NowFollowing: following, Timestamp: now})
|
||||||
followEventsLock.Unlock()
|
followEventsLock.Unlock()
|
||||||
|
|
||||||
return ResponseSuccess, nil
|
return ResponseSuccess, nil
|
||||||
|
|
|
@ -60,7 +60,7 @@ var Configuration *ConfigFile
|
||||||
|
|
||||||
var janitorsOnce sync.Once
|
var janitorsOnce sync.Once
|
||||||
|
|
||||||
var CommandPool StringPool
|
var CommandPool *StringPool
|
||||||
|
|
||||||
// SetupServerAndHandle starts all background goroutines and registers HTTP listeners on the given ServeMux.
|
// SetupServerAndHandle starts all background goroutines and registers HTTP listeners on the given ServeMux.
|
||||||
// Essentially, this function completely preps the server for a http.ListenAndServe call.
|
// Essentially, this function completely preps the server for a http.ListenAndServe call.
|
||||||
|
@ -573,7 +573,7 @@ func MarshalClientMessage(clientMessage interface{}) (payloadType int, data []by
|
||||||
return websocket.TextMessage, []byte(dataStr), nil
|
return websocket.TextMessage, []byte(dataStr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method: Parse the arguments of the ClientMessage as a single string.
|
// ArgumentsAsString parses the arguments of the ClientMessage as a single string.
|
||||||
func (cm *ClientMessage) ArgumentsAsString() (string1 string, err error) {
|
func (cm *ClientMessage) ArgumentsAsString() (string1 string, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
string1, ok = cm.Arguments.(string)
|
string1, ok = cm.Arguments.(string)
|
||||||
|
@ -585,7 +585,7 @@ func (cm *ClientMessage) ArgumentsAsString() (string1 string, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method: Parse the arguments of the ClientMessage as a single int.
|
// ArgumentsAsInt parses the arguments of the ClientMessage as a single int.
|
||||||
func (cm *ClientMessage) ArgumentsAsInt() (int1 int64, err error) {
|
func (cm *ClientMessage) ArgumentsAsInt() (int1 int64, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
var num float64
|
var num float64
|
||||||
|
@ -599,7 +599,7 @@ func (cm *ClientMessage) ArgumentsAsInt() (int1 int64, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method: Parse the arguments of the ClientMessage as an array of two strings.
|
// ArgumentsAsTwoStrings parses the arguments of the ClientMessage as an array of two strings.
|
||||||
func (cm *ClientMessage) ArgumentsAsTwoStrings() (string1, string2 string, err error) {
|
func (cm *ClientMessage) ArgumentsAsTwoStrings() (string1, string2 string, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
var ary []interface{}
|
var ary []interface{}
|
||||||
|
@ -630,7 +630,7 @@ func (cm *ClientMessage) ArgumentsAsTwoStrings() (string1, string2 string, err e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method: Parse the arguments of the ClientMessage as an array of a string and an int.
|
// ArgumentsAsStringAndInt parses the arguments of the ClientMessage as an array of a string and an int.
|
||||||
func (cm *ClientMessage) ArgumentsAsStringAndInt() (string1 string, int int64, err error) {
|
func (cm *ClientMessage) ArgumentsAsStringAndInt() (string1 string, int int64, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
var ary []interface{}
|
var ary []interface{}
|
||||||
|
@ -663,7 +663,7 @@ func (cm *ClientMessage) ArgumentsAsStringAndInt() (string1 string, int int64, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience method: Parse the arguments of the ClientMessage as an array of a string and an int.
|
// ArgumentsAsStringAndBool parses the arguments of the ClientMessage as an array of a string and an int.
|
||||||
func (cm *ClientMessage) ArgumentsAsStringAndBool() (str string, flag bool, err error) {
|
func (cm *ClientMessage) ArgumentsAsStringAndBool() (str string, flag bool, err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
var ary []interface{}
|
var ary []interface{}
|
||||||
|
|
|
@ -32,6 +32,7 @@ func (p *StringPool) Intern(s string) Command {
|
||||||
if exists {
|
if exists {
|
||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
p.lookup[s] = Command(string([]byte(s)))
|
ss = Command(string([]byte(s))) // make a copy
|
||||||
return s
|
p.lookup[s] = ss
|
||||||
|
return ss
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
irc "github.com/fluffle/goirc/client"
|
irc "github.com/fluffle/goirc/client"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -87,10 +86,6 @@ const AuthChannelName = "frankerfacezauthorizer"
|
||||||
const AuthChannel = "#" + AuthChannelName
|
const AuthChannel = "#" + AuthChannelName
|
||||||
const AuthCommand = "AUTH"
|
const AuthCommand = "AUTH"
|
||||||
|
|
||||||
const DEBUG = "DEBUG"
|
|
||||||
|
|
||||||
var errChallengeNotFound = errors.New("did not find a challenge solved by that message")
|
|
||||||
|
|
||||||
// is_init_func
|
// is_init_func
|
||||||
func ircConnection() {
|
func ircConnection() {
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -33,36 +32,37 @@ var S2CCommandsCacheInfo = map[Command]PushCommandCacheInfo{
|
||||||
type BacklogCacheType int
|
type BacklogCacheType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// This is not a cache type.
|
// CacheTypeInvalid is the sentinel value.
|
||||||
CacheTypeInvalid BacklogCacheType = iota
|
CacheTypeInvalid BacklogCacheType = iota
|
||||||
// This message cannot be cached.
|
// CacheTypeNever is a message that cannot be cached.
|
||||||
CacheTypeNever
|
CacheTypeNever
|
||||||
// Save only the last copy of this message, and always send it when the backlog is requested.
|
// CacheTypeLastOnly means to save only the last copy of this message,
|
||||||
|
// and always send it when the backlog is requested.
|
||||||
CacheTypeLastOnly
|
CacheTypeLastOnly
|
||||||
// Save this backlog data to disk with its timestamp.
|
// CacheTypePersistent means to save the last copy of this message,
|
||||||
// Send it when the backlog is requested, or after a reconnect if it was updated.
|
// and always send it when the backlog is requested, but do not clean it periodically.
|
||||||
CacheTypePersistent
|
CacheTypePersistent
|
||||||
)
|
)
|
||||||
|
|
||||||
type MessageTargetType int
|
type MessageTargetType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// This is not a message target.
|
// MsgTargetTypeInvalid is the sentinel value.
|
||||||
MsgTargetTypeInvalid MessageTargetType = iota
|
MsgTargetTypeInvalid MessageTargetType = iota
|
||||||
// This message is targeted to all users in a chat
|
// MsgTargetTypeChat is a message is targeted to all users in a particular chat.
|
||||||
MsgTargetTypeChat
|
MsgTargetTypeChat
|
||||||
// This message is targeted to all users in multiple chats
|
// MsgTargetTypeMultichat is a message is targeted to all users in multiple chats.
|
||||||
MsgTargetTypeMultichat
|
MsgTargetTypeMultichat
|
||||||
// This message is sent to all FFZ users.
|
// MsgTargetTypeGlobal is a message sent to all FFZ users.
|
||||||
MsgTargetTypeGlobal
|
MsgTargetTypeGlobal
|
||||||
)
|
)
|
||||||
|
|
||||||
// note: see types.go for methods on these
|
// note: see types.go for methods on these
|
||||||
|
|
||||||
// Returned by BacklogCacheType.UnmarshalJSON()
|
// ErrorUnrecognizedCacheType is returned by BacklogCacheType.UnmarshalJSON()
|
||||||
var ErrorUnrecognizedCacheType = errors.New("Invalid value for cachetype")
|
var ErrorUnrecognizedCacheType = errors.New("Invalid value for cachetype")
|
||||||
|
|
||||||
// Returned by MessageTargetType.UnmarshalJSON()
|
// ErrorUnrecognizedTargetType is returned by MessageTargetType.UnmarshalJSON()
|
||||||
var ErrorUnrecognizedTargetType = errors.New("Invalid value for message target")
|
var ErrorUnrecognizedTargetType = errors.New("Invalid value for message target")
|
||||||
|
|
||||||
type LastSavedMessage struct {
|
type LastSavedMessage struct {
|
||||||
|
@ -72,11 +72,11 @@ type LastSavedMessage struct {
|
||||||
|
|
||||||
// map is command -> channel -> data
|
// map is command -> channel -> data
|
||||||
|
|
||||||
// CacheTypeLastOnly. Cleaned up by reaper goroutine every ~hour.
|
// CachedLastMessages is of CacheTypeLastOnly. Cleaned up by reaper goroutine every ~hour.
|
||||||
var CachedLastMessages = make(map[Command]map[string]LastSavedMessage)
|
var CachedLastMessages = make(map[Command]map[string]LastSavedMessage)
|
||||||
var CachedLSMLock sync.RWMutex
|
var CachedLSMLock sync.RWMutex
|
||||||
|
|
||||||
// CacheTypePersistent. Never cleaned.
|
// PersistentLastMessages is of CacheTypePersistent. Never cleaned.
|
||||||
var PersistentLastMessages = make(map[Command]map[string]LastSavedMessage)
|
var PersistentLastMessages = make(map[Command]map[string]LastSavedMessage)
|
||||||
var PersistentLSMLock sync.RWMutex
|
var PersistentLSMLock sync.RWMutex
|
||||||
|
|
||||||
|
@ -135,50 +135,11 @@ func SendBacklogForNewClient(client *ClientInfo) {
|
||||||
CachedLSMLock.RUnlock()
|
CachedLSMLock.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// insertionSort implements insertion sort.
|
|
||||||
// CacheTypeTimestamps should use insertion sort for O(N) average performance.
|
|
||||||
// (The average case is the array is still sorted after insertion of the new item.)
|
|
||||||
func insertionSort(ary sort.Interface) {
|
|
||||||
for i := 1; i < ary.Len(); i++ {
|
|
||||||
for j := i; j > 0 && ary.Less(j, j-1); j-- {
|
|
||||||
ary.Swap(j, j-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type timestampArray interface {
|
type timestampArray interface {
|
||||||
Len() int
|
Len() int
|
||||||
GetTime(int) time.Time
|
GetTime(int) time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFirstNewMessage(ary timestampArray, disconnectTime time.Time) (idx int) {
|
|
||||||
len := ary.Len()
|
|
||||||
i := len
|
|
||||||
|
|
||||||
// Walk backwards until we find GetTime() before disconnectTime
|
|
||||||
step := 1
|
|
||||||
for i > 0 {
|
|
||||||
i -= step
|
|
||||||
if i < 0 {
|
|
||||||
i = 0
|
|
||||||
}
|
|
||||||
if !ary.GetTime(i).After(disconnectTime) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
step = int(float64(step)*1.5) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk forwards until we find GetTime() after disconnectTime
|
|
||||||
for i < len && !ary.GetTime(i).After(disconnectTime) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == len {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveLastMessage(which map[Command]map[string]LastSavedMessage, locker sync.Locker, cmd Command, channel string, timestamp time.Time, data string, deleting bool) {
|
func SaveLastMessage(which map[Command]map[string]LastSavedMessage, locker sync.Locker, cmd Command, channel string, timestamp time.Time, data string, deleting bool) {
|
||||||
locker.Lock()
|
locker.Lock()
|
||||||
defer locker.Unlock()
|
defer locker.Unlock()
|
||||||
|
@ -195,7 +156,7 @@ func SaveLastMessage(which map[Command]map[string]LastSavedMessage, locker sync.
|
||||||
if deleting {
|
if deleting {
|
||||||
delete(chanMap, channel)
|
delete(chanMap, channel)
|
||||||
} else {
|
} else {
|
||||||
chanMap[channel] = LastSavedMessage{timestamp, data}
|
chanMap[channel] = LastSavedMessage{Timestamp: timestamp, Data: data}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +185,8 @@ func HTTPBackendDropBacklog(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish a message to clients, and update the in-server cache for the message.
|
// HTTPBackendCachedPublish handles the /cached_pub route.
|
||||||
|
// It publishes a message to clients, and then updates the in-server cache for the message.
|
||||||
// notes:
|
// notes:
|
||||||
// `scope` is implicit in the command
|
// `scope` is implicit in the command
|
||||||
func HTTPBackendCachedPublish(w http.ResponseWriter, r *http.Request) {
|
func HTTPBackendCachedPublish(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -73,7 +73,7 @@ func commandCounter() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatsDataVersion
|
// StatsDataVersion is the version of the StatsData struct.
|
||||||
const StatsDataVersion = 5
|
const StatsDataVersion = 5
|
||||||
const pageSize = 4096
|
const pageSize = 4096
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,9 @@ func UnsubscribeSingleChat(client *ClientInfo, channelName string) {
|
||||||
ChatSubscriptionLock.RUnlock()
|
ChatSubscriptionLock.RUnlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsubscribe the client from all channels, AND clear the CurrentChannels / WatchingChannels fields.
|
// UnsubscribeAll will unsubscribe the client from all channels,
|
||||||
|
// AND clear the CurrentChannels / WatchingChannels fields.
|
||||||
|
//
|
||||||
// Locks:
|
// Locks:
|
||||||
// - read lock to top-level maps
|
// - read lock to top-level maps
|
||||||
// - write lock to SubscriptionInfos
|
// - write lock to SubscriptionInfos
|
||||||
|
|
|
@ -30,9 +30,9 @@ func TestSubscriptionAndPublish(t *testing.T) {
|
||||||
const TestData3 = false
|
const TestData3 = false
|
||||||
var TestData4 = []interface{}{"str1", "str2", "str3"}
|
var TestData4 = []interface{}{"str1", "str2", "str3"}
|
||||||
|
|
||||||
S2CCommandsCacheInfo[TestCommandChan] = PushCommandCacheInfo{CacheTypeLastOnly, MsgTargetTypeChat}
|
S2CCommandsCacheInfo[TestCommandChan] = PushCommandCacheInfo{Caching: CacheTypeLastOnly, Target: MsgTargetTypeChat}
|
||||||
S2CCommandsCacheInfo[TestCommandMulti] = PushCommandCacheInfo{CacheTypeLastOnly, MsgTargetTypeMultichat}
|
S2CCommandsCacheInfo[TestCommandMulti] = PushCommandCacheInfo{Caching: CacheTypeLastOnly, Target: MsgTargetTypeMultichat}
|
||||||
S2CCommandsCacheInfo[TestCommandGlobal] = PushCommandCacheInfo{CacheTypeLastOnly, MsgTargetTypeGlobal}
|
S2CCommandsCacheInfo[TestCommandGlobal] = PushCommandCacheInfo{Caching: CacheTypeLastOnly, Target: MsgTargetTypeGlobal}
|
||||||
|
|
||||||
var server *httptest.Server
|
var server *httptest.Server
|
||||||
var urls TURLs
|
var urls TURLs
|
||||||
|
|
|
@ -9,8 +9,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CryptoBoxKeyLength = 32
|
|
||||||
|
|
||||||
const NegativeOne = ^uint64(0)
|
const NegativeOne = ^uint64(0)
|
||||||
|
|
||||||
type ConfigFile struct {
|
type ConfigFile struct {
|
||||||
|
@ -94,7 +92,7 @@ type ClientInfo struct {
|
||||||
// If it seems to be a performance problem, we can split this.
|
// If it seems to be a performance problem, we can split this.
|
||||||
Mutex sync.Mutex
|
Mutex sync.Mutex
|
||||||
|
|
||||||
// TODO(riking) - does this need to be protected cross-thread?
|
// Info about the client's username and whether or not we have verified it.
|
||||||
AuthInfo
|
AuthInfo
|
||||||
|
|
||||||
RemoteAddr net.Addr
|
RemoteAddr net.Addr
|
||||||
|
@ -187,15 +185,15 @@ func BacklogCacheTypeByName(name string) (bct BacklogCacheType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements Stringer
|
// String implements Stringer
|
||||||
func (bct BacklogCacheType) String() string { return bct.Name() }
|
func (bct BacklogCacheType) String() string { return bct.Name() }
|
||||||
|
|
||||||
// Implements json.Marshaler
|
// MarshalJSON implements json.Marshaler
|
||||||
func (bct BacklogCacheType) MarshalJSON() ([]byte, error) {
|
func (bct BacklogCacheType) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(bct.Name())
|
return json.Marshal(bct.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements json.Unmarshaler
|
// UnmarshalJSON implements json.Unmarshaler
|
||||||
func (bct *BacklogCacheType) UnmarshalJSON(data []byte) error {
|
func (bct *BacklogCacheType) UnmarshalJSON(data []byte) error {
|
||||||
var str string
|
var str string
|
||||||
err := json.Unmarshal(data, &str)
|
err := json.Unmarshal(data, &str)
|
||||||
|
@ -240,15 +238,15 @@ func MessageTargetTypeByName(name string) (mtt MessageTargetType) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements Stringer
|
// String implements Stringer
|
||||||
func (mtt MessageTargetType) String() string { return mtt.Name() }
|
func (mtt MessageTargetType) String() string { return mtt.Name() }
|
||||||
|
|
||||||
// Implements json.Marshaler
|
// MarshalJSON implements json.Marshaler
|
||||||
func (mtt MessageTargetType) MarshalJSON() ([]byte, error) {
|
func (mtt MessageTargetType) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(mtt.Name())
|
return json.Marshal(mtt.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implements json.Unmarshaler
|
// UnmarshalJSON implements json.Unmarshaler
|
||||||
func (mtt *MessageTargetType) UnmarshalJSON(data []byte) error {
|
func (mtt *MessageTargetType) UnmarshalJSON(data []byte) error {
|
||||||
var str string
|
var str string
|
||||||
err := json.Unmarshal(data, &str)
|
err := json.Unmarshal(data, &str)
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// uuidHash implements a hash for uuid.UUID by XORing the random bits.
|
// UuidHash implements a hash for uuid.UUID by XORing the random bits.
|
||||||
type UuidHash uuid.UUID
|
type UuidHash uuid.UUID
|
||||||
|
|
||||||
func (u UuidHash) Sum64() uint64 {
|
func (u UuidHash) Sum64() uint64 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue