From 4295d5c19adec9baf0cc0994b4d4ce1eb3db03be Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Sat, 30 Dec 2023 18:59:29 -0500 Subject: [PATCH 01/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1d20ceeb..97a9b38c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ It has the following features: * Client/server connection management and timeouts * Encrypted and signed packets sent over UDP * Packet fragmentation and reassembly +* Bitpacker and serialization system * Reliable-ordered messages and data blocks * Estimates of packet loss, latency and bandwidth usage @@ -31,6 +32,8 @@ The author of this library is Glenn Fiedler. Open source libraries by the same author include: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) +If you find this software useful, please consider [sponsoring it](). Thanks! + ## Sponsors **yojimbo** was generously sponsored by: From fe996bebb0244a2f8fd1188aaa197e7699f4b338 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Sat, 30 Dec 2023 19:00:18 -0500 Subject: [PATCH 02/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/README.md b/README.md index 97a9b38c..c4d6c041 100644 --- a/README.md +++ b/README.md @@ -32,25 +32,7 @@ The author of this library is Glenn Fiedler. Open source libraries by the same author include: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) -If you find this software useful, please consider [sponsoring it](). Thanks! - -## Sponsors - -**yojimbo** was generously sponsored by: - -* **Gold Sponsors** - * [Remedy Entertainment](http://www.remedygames.com/) - * [Cloud Imperium Games](https://cloudimperiumgames.com) - -* **Silver Sponsors** - * [Moon Studios](http://www.oriblindforest.com/#!moon-3/) - * The Network Protocol Company - -* **Bronze Sponsors** - * Kite & Lightning - * [Data Realms](http://datarealms.com) - -And by individual supporters on Patreon. Thank you. You made this possible! +If you find this software useful, please consider [sponsoring it](https://github.com/sponsors/mas-bandwidth). Thanks! ## License From 2e06e2f714e586a92fbe0cafd3d877d48545a6f2 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Sat, 30 Dec 2023 19:00:43 -0500 Subject: [PATCH 03/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4d6c041..479033cd 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ You can get the latest source code by cloning it from github: git clone https://github.com/mas-bandwidth/yojimbo.git -Alternatively, you can download one of the latest [releases](https://github.com/mas-bandwidth/yojimbo/releases) +Alternatively, you can download the latest [releases](https://github.com/mas-bandwidth/yojimbo/releases) ## Author From 56433a960b4e22213a3c89352fb7c392272d5c40 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Sat, 30 Dec 2023 19:01:35 -0500 Subject: [PATCH 04/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 479033cd..6eb7fbc4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Alternatively, you can download the latest [releases](https://github.com/mas-ban The author of this library is Glenn Fiedler. -Open source libraries by the same author include: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) +Other open source libraries by the same author include: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) If you find this software useful, please consider [sponsoring it](https://github.com/sponsors/mas-bandwidth). Thanks! From 0559780fe121962384a3e386a0a79daab1221296 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 5 Jan 2024 09:56:23 -0500 Subject: [PATCH 05/39] #if YOJIMBO_DEBUG -> #ifdef YOJIMBO_DEBUG --- source/yojimbo_reliable_ordered_channel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/yojimbo_reliable_ordered_channel.cpp b/source/yojimbo_reliable_ordered_channel.cpp index 41492860..711e861a 100644 --- a/source/yojimbo_reliable_ordered_channel.cpp +++ b/source/yojimbo_reliable_ordered_channel.cpp @@ -264,7 +264,7 @@ namespace yojimbo uint16_t previousMessageId = 0; int usedBits = ConservativeMessageHeaderBits; int giveUpCounter = 0; -#if YOJIMBO_DEBUG +#ifdef YOJIMBO_DEBUG const int maxBits = availableBits; #endif // YOJIMBO_DEBUG From 9ada0fe78cc328897aeb75f7f3969e5a5937b334 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 5 Jan 2024 09:57:22 -0500 Subject: [PATCH 06/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6eb7fbc4..8dbf8b9a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ You can get the latest source code by cloning it from github: git clone https://github.com/mas-bandwidth/yojimbo.git -Alternatively, you can download the latest [releases](https://github.com/mas-bandwidth/yojimbo/releases) +Alternatively, you can download the latest [release](https://github.com/mas-bandwidth/yojimbo/releases). ## Author From 2b312a8e3db765402bc9655ef62dfc04258aeb88 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 9 Jan 2024 22:58:13 -0500 Subject: [PATCH 07/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8dbf8b9a..7dc34bf6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # yojimbo +https://www.google.com/url?sa=i&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FYojimbo&psig=AOvVaw3nh6QfHQnTVDN1n9NCJj-T&ust=1704945425105000&source=images&cd=vfe&opi=89978449&ved=0CBEQjRxqFwoTCLC_mOj20YMDFQAAAAAdAAAAABAD![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/984e8591-3dba-4f93-b22c-426646518796) + **yojimbo** is a network library for client/server games written in C++. It's designed around the networking requirements of competitive multiplayer games like first person shooters. From 7a88dfea9d931b68676323ac9e90dd204759631e Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 9 Jan 2024 22:59:07 -0500 Subject: [PATCH 08/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7dc34bf6..74d7ae75 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # yojimbo -https://www.google.com/url?sa=i&url=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FYojimbo&psig=AOvVaw3nh6QfHQnTVDN1n9NCJj-T&ust=1704945425105000&source=images&cd=vfe&opi=89978449&ved=0CBEQjRxqFwoTCLC_mOj20YMDFQAAAAAdAAAAABAD![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/984e8591-3dba-4f93-b22c-426646518796) +![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/337099f4-e115-4060-8b88-e78ff2477466) **yojimbo** is a network library for client/server games written in C++. From fa8f6f61b57fddc8484db66a7733713b40f72989 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 9 Jan 2024 23:00:58 -0500 Subject: [PATCH 09/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74d7ae75..cc6953cd 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # yojimbo -![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/337099f4-e115-4060-8b88-e78ff2477466) +![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) **yojimbo** is a network library for client/server games written in C++. From 27c136bd733b775bc9e69317823817fc6d26d8b8 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 9 Jan 2024 23:01:14 -0500 Subject: [PATCH 10/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cc6953cd..71441fcd 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ # yojimbo -![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) - **yojimbo** is a network library for client/server games written in C++. +![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) + It's designed around the networking requirements of competitive multiplayer games like first person shooters. It has the following features: From 3e9259d261d66048128ef12fd0db47e7b301cd3b Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 9 Jan 2024 23:01:27 -0500 Subject: [PATCH 11/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 71441fcd..746804fd 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ **yojimbo** is a network library for client/server games written in C++. -![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) - It's designed around the networking requirements of competitive multiplayer games like first person shooters. +![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) + It has the following features: * Cryptographically secure authentication via [connect tokens](https://github.com/networkprotocol/netcode/blob/master/STANDARD.md) From 53e12a618894c740c929ccf5f97c519d723bd6fb Mon Sep 17 00:00:00 2001 From: kbirk Date: Sat, 20 Jan 2024 12:27:28 -0500 Subject: [PATCH 12/39] Add the matcher sample again --- matcher/Dockerfile | 28 +++++ matcher/README.md | 31 ++++++ matcher/go.mod | 11 ++ matcher/go.sum | 8 ++ matcher/main.go | 254 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 332 insertions(+) create mode 100644 matcher/Dockerfile create mode 100644 matcher/README.md create mode 100644 matcher/go.mod create mode 100644 matcher/go.sum create mode 100644 matcher/main.go diff --git a/matcher/Dockerfile b/matcher/Dockerfile new file mode 100644 index 00000000..2c7bc60f --- /dev/null +++ b/matcher/Dockerfile @@ -0,0 +1,28 @@ +FROM golang:1.20.13 AS matcher_build + +# Matcher +WORKDIR /matcher + +# Copy go.mod and go.sum files to the workspace separately and download dependecies. +# Doing this separately will cache these as its own separate layer +COPY ./go.mod . +COPY ./go.sum . +RUN go mod download + +# Copy the source code as the last step +COPY . . + +# Build the binary +RUN CGO_ENABLED=0 go build -o matcher.bin main.go + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +# Then we copy and run it from a slim image +FROM alpine:3.5 +WORKDIR /matcher + +COPY --from=matcher_build /matcher/matcher.bin . + +EXPOSE 8081 + +ENTRYPOINT ["/matcher/matcher.bin"] diff --git a/matcher/README.md b/matcher/README.md new file mode 100644 index 00000000..f63e28ef --- /dev/null +++ b/matcher/README.md @@ -0,0 +1,31 @@ +# Yojimbo Matcher Sample + +This is a sample matcher server written in go that will provide a connection token via the following endpoint: + +``` +GET /match/{protocolID}/{clientID} +``` + +# Building the Docker image: + +To build the image run the following command from the `matcher` directory: + +```sh +docker build --tag=matcher . +``` + +# Running the Docker container: + +Run the container image mapping the port to your host machine: + +```sh +docker run -d -p 8081:8081 --name matcher matcher +``` + +# Using the matcher: + +To hit the container with a test request: + +```sh +PROTOCOL_ID=123 && CLIENT_ID=42 && curl http://localhost:8081/match/${PROTOCOL_ID}/${CLIENT_ID} +``` diff --git a/matcher/go.mod b/matcher/go.mod new file mode 100644 index 00000000..474472ba --- /dev/null +++ b/matcher/go.mod @@ -0,0 +1,11 @@ +module github.com/mas-bandwidth/yojimbo-matcher + +go 1.20 + +require ( + github.com/go-chi/chi/v5 v5.0.8 + github.com/pkg/errors v0.9.1 + golang.org/x/crypto v0.18.0 +) + +require golang.org/x/sys v0.16.0 // indirect diff --git a/matcher/go.sum b/matcher/go.sum new file mode 100644 index 00000000..c4f4c78c --- /dev/null +++ b/matcher/go.sum @@ -0,0 +1,8 @@ +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/matcher/main.go b/matcher/main.go new file mode 100644 index 00000000..84d24195 --- /dev/null +++ b/matcher/main.go @@ -0,0 +1,254 @@ +package main + +import ( + "crypto/rand" + "encoding/base64" + "encoding/binary" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + "strconv" + "time" + + "github.com/go-chi/chi/v5" + "github.com/pkg/errors" + "golang.org/x/crypto/chacha20poly1305" +) + +const ( + port = 8081 + serverAddress = "127.0.0.1" + serverPort = 40000 + keyBytes = 32 + authBytes = 16 + connectTokenExpiry = 45 + connectTokenBytes = 2048 + connectTokenPrivateBytes = 1024 + userDataBytes = 256 + timeoutSeconds = 5 + versionInfo = "NETCODE 1.02\x00" + verboseError = true + addressIPV4 = 1 + addressIPV6 = 2 +) + +var ( + stdoutLogger = log.New(os.Stdout, "yojimbo-matcher: ", log.Llongfile) + stderrLogger = log.New(os.Stderr, "yojimbo-matcher: ", log.Llongfile) + privateKey = []byte{ + 0x60, 0x6a, 0xbe, 0x6e, 0xc9, 0x19, 0x10, 0xea, + 0x9a, 0x65, 0x62, 0xf6, 0x6f, 0x2b, 0x30, 0xe4, + 0x43, 0x71, 0xd6, 0x2c, 0xd1, 0x99, 0x27, 0x26, + 0x6b, 0x3c, 0x60, 0xf4, 0xb7, 0x15, 0xab, 0xa1, + } +) + +func WriteAddresses(buffer []byte, addresses []net.UDPAddr) int { + binary.LittleEndian.PutUint32(buffer[0:], (uint32)(len(addresses))) + offset := 4 + for _, addr := range addresses { + ipv4 := addr.IP.To4() + port := addr.Port + if ipv4 != nil { + buffer[offset] = addressIPV4 + buffer[offset+1] = ipv4[0] + buffer[offset+2] = ipv4[1] + buffer[offset+3] = ipv4[2] + buffer[offset+4] = ipv4[3] + buffer[offset+5] = (byte)(port & 0xFF) + buffer[offset+6] = (byte)(port >> 8) + } else { + buffer[offset] = addressIPV6 + copy(buffer[offset+1:], addr.IP) + buffer[offset+17] = (byte)(port & 0xFF) + buffer[offset+18] = (byte)(port >> 8) + } + offset += 19 + } + return offset +} + +type ConnectTokenPrivate struct { + clientID uint64 + TimeoutSeconds int32 + ServerAddresses []net.UDPAddr + ClientToServerKey [keyBytes]byte + ServerToClientKey [keyBytes]byte + UserData [userDataBytes]byte +} + +func NewConnectTokenPrivate(clientID uint64, serverAddresses []net.UDPAddr, timeoutSeconds int32, userData []byte, clientToServerKey []byte, serverToClientKey []byte) *ConnectTokenPrivate { + connectTokenPrivate := &ConnectTokenPrivate{} + connectTokenPrivate.clientID = clientID + connectTokenPrivate.TimeoutSeconds = timeoutSeconds + connectTokenPrivate.ServerAddresses = serverAddresses + copy(connectTokenPrivate.UserData[:], userData) + copy(connectTokenPrivate.ClientToServerKey[:], clientToServerKey) + copy(connectTokenPrivate.ServerToClientKey[:], serverToClientKey) + return connectTokenPrivate +} + +func (token *ConnectTokenPrivate) Write(buffer []byte) { + binary.LittleEndian.PutUint64(buffer[0:], token.clientID) + binary.LittleEndian.PutUint32(buffer[8:], (uint32)(token.TimeoutSeconds)) + addressBytes := WriteAddresses(buffer[12:], token.ServerAddresses) + copy(buffer[12+addressBytes:], token.ClientToServerKey[:]) + copy(buffer[12+addressBytes+keyBytes:], token.ServerToClientKey[:]) + copy(buffer[12+addressBytes+keyBytes*2:], token.UserData[:]) +} + +type ConnectToken struct { + protocolID uint64 + CreateTimestamp uint64 + ExpireTimestamp uint64 + Sequence uint64 + PrivateData *ConnectTokenPrivate + TimeoutSeconds int32 + ServerAddresses []net.UDPAddr + ClientToServerKey [keyBytes]byte + ServerToClientKey [keyBytes]byte + PrivateKey [keyBytes]byte +} + +func NewConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID uint64, expireSeconds uint64, timeoutSeconds int32, userData []byte, privateKey []byte) (*ConnectToken, error) { + connectToken := &ConnectToken{} + connectToken.protocolID = protocolID + connectToken.CreateTimestamp = uint64(time.Now().Unix()) + if expireSeconds >= 0 { + connectToken.ExpireTimestamp = connectToken.CreateTimestamp + expireSeconds + } else { + connectToken.ExpireTimestamp = 0xFFFFFFFFFFFFFFFF + } + connectToken.TimeoutSeconds = timeoutSeconds + connectToken.ServerAddresses = serverAddresses + err := fillWithRandomBytes(connectToken.ClientToServerKey[:]) + if err != nil { + return nil, errors.Wrap(err, "failed to fill client to server key with random bytes") + } + err = fillWithRandomBytes(connectToken.ServerToClientKey[:]) + if err != nil { + return nil, errors.Wrap(err, "failed to fill server to client key with random bytes") + } + copy(connectToken.PrivateKey[:], privateKey[:]) + connectToken.PrivateData = NewConnectTokenPrivate(clientID, serverAddresses, timeoutSeconds, userData, connectToken.ClientToServerKey[:], connectToken.ServerToClientKey[:]) + return connectToken, nil +} + +func fillWithRandomBytes(buf []byte) error { + _, err := rand.Read(buf) + if err != nil { + return err + } + return nil +} + +func EncryptAEAD(message []byte, additional []byte, nonce []byte, key []byte) error { + aead, err := chacha20poly1305.NewX(key) + if err != nil { + return errors.Wrap(err, "failed to create cipher") + } + + // Encrypt the message and append the authentication tag. + aead.Seal(message[:0], nonce, message, additional) + + return nil +} + +func (token *ConnectToken) Write(buffer []byte) error { + copy(buffer, versionInfo) + binary.LittleEndian.PutUint64(buffer[13:], token.protocolID) + binary.LittleEndian.PutUint64(buffer[21:], token.CreateTimestamp) + binary.LittleEndian.PutUint64(buffer[29:], token.ExpireTimestamp) + nonce := make([]byte, 24) + err := fillWithRandomBytes(nonce) + if err != nil { + return errors.Wrap(err, "failed to fill nonce with random bytes") + } + copy(buffer[37:], nonce[:]) + token.PrivateData.Write(buffer[61:]) + additional := make([]byte, 13+8+8) + copy(additional, versionInfo[0:13]) + binary.LittleEndian.PutUint64(additional[13:], token.protocolID) + binary.LittleEndian.PutUint64(additional[21:], token.ExpireTimestamp) + err = EncryptAEAD(buffer[61:61+connectTokenPrivateBytes-authBytes], additional[:], nonce[:], token.PrivateKey[:]) + if err != nil { + return errors.Wrap(err, "failed to encrypt message") + } + binary.LittleEndian.PutUint32(buffer[connectTokenPrivateBytes+61:], (uint32)(token.TimeoutSeconds)) + offset := WriteAddresses(buffer[1024+61+4:], token.ServerAddresses) + copy(buffer[1024+61+4+offset:], token.ClientToServerKey[:]) + copy(buffer[1024+61+4+offset+keyBytes:], token.ServerToClientKey[:]) + return nil +} + +func GenerateConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID uint64, expireSeconds uint64, timeoutSeconds int32, userData []byte, privateKey []byte) ([]byte, error) { + connectToken, err := NewConnectToken(clientID, serverAddresses, protocolID, expireSeconds, timeoutSeconds, userData, privateKey) + if err != nil { + return nil, errors.Wrap(err, "failed to create connect token") + } + buffer := make([]byte, connectTokenBytes) + err = connectToken.Write(buffer) + if err != nil { + return nil, errors.Wrap(err, "failed to serialize connect token") + } + return buffer, nil +} + +func writeError(w http.ResponseWriter, err error, statusCode int) { + stderrLogger.Printf("%+v\n", err) + errMessage := "An error occured on the server while processing the request" + if verboseError { + errMessage = err.Error() + } + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(statusCode) + fmt.Fprint(w, errMessage) +} + +func MatchHandler(w http.ResponseWriter, r *http.Request) { + + clientID, err := strconv.ParseUint(chi.URLParam(r, "clientID"), 10, 64) + if err != nil { + writeError(w, fmt.Errorf("Unable to parse clientID: %s", chi.URLParam(r, "clientID")), http.StatusBadRequest) + return + } + protocolID, err := strconv.ParseUint(chi.URLParam(r, "protocolID"), 10, 64) + if err != nil { + writeError(w, fmt.Errorf("Unable to parse protocolID: %s", chi.URLParam(r, "protocolID")), http.StatusBadRequest) + return + } + + serverAddresses := make([]net.UDPAddr, 1) + serverAddresses[0] = net.UDPAddr{IP: net.ParseIP(serverAddress), Port: serverPort} + + userData := make([]byte, userDataBytes) + connectToken, err := GenerateConnectToken(clientID, serverAddresses, protocolID, connectTokenExpiry, timeoutSeconds, userData, privateKey) + if err != nil { + writeError(w, errors.Wrap(err, "Failed to generate connect token"), http.StatusInternalServerError) + return + } + connectTokenBase64 := base64.StdEncoding.EncodeToString(connectToken) + w.Header().Set("Content-Type", "application/text") + _, err = io.WriteString(w, connectTokenBase64) + if err != nil { + writeError(w, errors.Wrap(err, "Failed to write response"), http.StatusInternalServerError) + return + } + stderrLogger.Printf("Matched client %.16x to %s:%d\n", clientID, serverAddress, serverPort) +} + +func main() { + stderrLogger.Printf("Started matchmaker on port %d\n", port) + + router := chi.NewRouter() + router.Get("/match/{protocolID:[0-9]+}/{clientID:[0-9]+}", MatchHandler) + + err := http.ListenAndServe(":"+strconv.Itoa(port), router) + if err != nil { + stderrLogger.Fatalf("%+v\n", err) + } +} From ed7c0ca2626fea1b5dc32f267643c3d9d00d3599 Mon Sep 17 00:00:00 2001 From: kbirk Date: Sat, 20 Jan 2024 12:48:21 -0500 Subject: [PATCH 13/39] Fix go vet issues --- matcher/main.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/matcher/main.go b/matcher/main.go index 84d24195..fff96e86 100644 --- a/matcher/main.go +++ b/matcher/main.go @@ -46,7 +46,7 @@ var ( } ) -func WriteAddresses(buffer []byte, addresses []net.UDPAddr) int { +func writeAddresses(buffer []byte, addresses []net.UDPAddr) int { binary.LittleEndian.PutUint32(buffer[0:], (uint32)(len(addresses))) offset := 4 for _, addr := range addresses { @@ -71,7 +71,7 @@ func WriteAddresses(buffer []byte, addresses []net.UDPAddr) int { return offset } -type ConnectTokenPrivate struct { +type connectTokenPrivate struct { clientID uint64 TimeoutSeconds int32 ServerAddresses []net.UDPAddr @@ -80,8 +80,8 @@ type ConnectTokenPrivate struct { UserData [userDataBytes]byte } -func NewConnectTokenPrivate(clientID uint64, serverAddresses []net.UDPAddr, timeoutSeconds int32, userData []byte, clientToServerKey []byte, serverToClientKey []byte) *ConnectTokenPrivate { - connectTokenPrivate := &ConnectTokenPrivate{} +func newConnectTokenPrivate(clientID uint64, serverAddresses []net.UDPAddr, timeoutSeconds int32, userData []byte, clientToServerKey []byte, serverToClientKey []byte) *connectTokenPrivate { + connectTokenPrivate := &connectTokenPrivate{} connectTokenPrivate.clientID = clientID connectTokenPrivate.TimeoutSeconds = timeoutSeconds connectTokenPrivate.ServerAddresses = serverAddresses @@ -91,21 +91,21 @@ func NewConnectTokenPrivate(clientID uint64, serverAddresses []net.UDPAddr, time return connectTokenPrivate } -func (token *ConnectTokenPrivate) Write(buffer []byte) { +func (token *connectTokenPrivate) Write(buffer []byte) { binary.LittleEndian.PutUint64(buffer[0:], token.clientID) binary.LittleEndian.PutUint32(buffer[8:], (uint32)(token.TimeoutSeconds)) - addressBytes := WriteAddresses(buffer[12:], token.ServerAddresses) + addressBytes := writeAddresses(buffer[12:], token.ServerAddresses) copy(buffer[12+addressBytes:], token.ClientToServerKey[:]) copy(buffer[12+addressBytes+keyBytes:], token.ServerToClientKey[:]) copy(buffer[12+addressBytes+keyBytes*2:], token.UserData[:]) } -type ConnectToken struct { +type connectToken struct { protocolID uint64 CreateTimestamp uint64 ExpireTimestamp uint64 Sequence uint64 - PrivateData *ConnectTokenPrivate + PrivateData *connectTokenPrivate TimeoutSeconds int32 ServerAddresses []net.UDPAddr ClientToServerKey [keyBytes]byte @@ -113,8 +113,8 @@ type ConnectToken struct { PrivateKey [keyBytes]byte } -func NewConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID uint64, expireSeconds uint64, timeoutSeconds int32, userData []byte, privateKey []byte) (*ConnectToken, error) { - connectToken := &ConnectToken{} +func newConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID uint64, expireSeconds uint64, timeoutSeconds int32, userData []byte, privateKey []byte) (*connectToken, error) { + connectToken := &connectToken{} connectToken.protocolID = protocolID connectToken.CreateTimestamp = uint64(time.Now().Unix()) if expireSeconds >= 0 { @@ -133,7 +133,7 @@ func NewConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID return nil, errors.Wrap(err, "failed to fill server to client key with random bytes") } copy(connectToken.PrivateKey[:], privateKey[:]) - connectToken.PrivateData = NewConnectTokenPrivate(clientID, serverAddresses, timeoutSeconds, userData, connectToken.ClientToServerKey[:], connectToken.ServerToClientKey[:]) + connectToken.PrivateData = newConnectTokenPrivate(clientID, serverAddresses, timeoutSeconds, userData, connectToken.ClientToServerKey[:], connectToken.ServerToClientKey[:]) return connectToken, nil } @@ -145,7 +145,7 @@ func fillWithRandomBytes(buf []byte) error { return nil } -func EncryptAEAD(message []byte, additional []byte, nonce []byte, key []byte) error { +func encryptAEAD(message []byte, additional []byte, nonce []byte, key []byte) error { aead, err := chacha20poly1305.NewX(key) if err != nil { return errors.Wrap(err, "failed to create cipher") @@ -157,7 +157,7 @@ func EncryptAEAD(message []byte, additional []byte, nonce []byte, key []byte) er return nil } -func (token *ConnectToken) Write(buffer []byte) error { +func (token *connectToken) Write(buffer []byte) error { copy(buffer, versionInfo) binary.LittleEndian.PutUint64(buffer[13:], token.protocolID) binary.LittleEndian.PutUint64(buffer[21:], token.CreateTimestamp) @@ -173,19 +173,19 @@ func (token *ConnectToken) Write(buffer []byte) error { copy(additional, versionInfo[0:13]) binary.LittleEndian.PutUint64(additional[13:], token.protocolID) binary.LittleEndian.PutUint64(additional[21:], token.ExpireTimestamp) - err = EncryptAEAD(buffer[61:61+connectTokenPrivateBytes-authBytes], additional[:], nonce[:], token.PrivateKey[:]) + err = encryptAEAD(buffer[61:61+connectTokenPrivateBytes-authBytes], additional[:], nonce[:], token.PrivateKey[:]) if err != nil { return errors.Wrap(err, "failed to encrypt message") } binary.LittleEndian.PutUint32(buffer[connectTokenPrivateBytes+61:], (uint32)(token.TimeoutSeconds)) - offset := WriteAddresses(buffer[1024+61+4:], token.ServerAddresses) + offset := writeAddresses(buffer[1024+61+4:], token.ServerAddresses) copy(buffer[1024+61+4+offset:], token.ClientToServerKey[:]) copy(buffer[1024+61+4+offset+keyBytes:], token.ServerToClientKey[:]) return nil } -func GenerateConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID uint64, expireSeconds uint64, timeoutSeconds int32, userData []byte, privateKey []byte) ([]byte, error) { - connectToken, err := NewConnectToken(clientID, serverAddresses, protocolID, expireSeconds, timeoutSeconds, userData, privateKey) +func generateConnectToken(clientID uint64, serverAddresses []net.UDPAddr, protocolID uint64, expireSeconds uint64, timeoutSeconds int32, userData []byte, privateKey []byte) ([]byte, error) { + connectToken, err := newConnectToken(clientID, serverAddresses, protocolID, expireSeconds, timeoutSeconds, userData, privateKey) if err != nil { return nil, errors.Wrap(err, "failed to create connect token") } @@ -209,7 +209,7 @@ func writeError(w http.ResponseWriter, err error, statusCode int) { fmt.Fprint(w, errMessage) } -func MatchHandler(w http.ResponseWriter, r *http.Request) { +func matchHandler(w http.ResponseWriter, r *http.Request) { clientID, err := strconv.ParseUint(chi.URLParam(r, "clientID"), 10, 64) if err != nil { @@ -226,7 +226,7 @@ func MatchHandler(w http.ResponseWriter, r *http.Request) { serverAddresses[0] = net.UDPAddr{IP: net.ParseIP(serverAddress), Port: serverPort} userData := make([]byte, userDataBytes) - connectToken, err := GenerateConnectToken(clientID, serverAddresses, protocolID, connectTokenExpiry, timeoutSeconds, userData, privateKey) + connectToken, err := generateConnectToken(clientID, serverAddresses, protocolID, connectTokenExpiry, timeoutSeconds, userData, privateKey) if err != nil { writeError(w, errors.Wrap(err, "Failed to generate connect token"), http.StatusInternalServerError) return @@ -245,7 +245,7 @@ func main() { stderrLogger.Printf("Started matchmaker on port %d\n", port) router := chi.NewRouter() - router.Get("/match/{protocolID:[0-9]+}/{clientID:[0-9]+}", MatchHandler) + router.Get("/match/{protocolID:[0-9]+}/{clientID:[0-9]+}", matchHandler) err := http.ListenAndServe(":"+strconv.Itoa(port), router) if err != nil { From f733634bf3fc6834c5d02e522be2dc15325b62f0 Mon Sep 17 00:00:00 2001 From: kbirk Date: Sat, 20 Jan 2024 13:30:54 -0500 Subject: [PATCH 14/39] Update go.mod module name --- matcher/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/matcher/go.mod b/matcher/go.mod index 474472ba..c3f612d3 100644 --- a/matcher/go.mod +++ b/matcher/go.mod @@ -1,4 +1,4 @@ -module github.com/mas-bandwidth/yojimbo-matcher +module github.com/mas-bandwidth/matcher go 1.20 From 3b4a11cf40af4057c1dcd22efb7508145b1657f9 Mon Sep 17 00:00:00 2001 From: Jake Armstrong Date: Sat, 30 Mar 2024 23:42:09 -0400 Subject: [PATCH 15/39] update USAGE.md to reflect change in name of yojimbo_random_bytes --- USAGE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/USAGE.md b/USAGE.md index f9c826c3..c7f7bf2d 100644 --- a/USAGE.md +++ b/USAGE.md @@ -200,7 +200,7 @@ OnlineGameScreen::OnlineGameScreen(const yojimbo::Address& serverAddress) : m_client(yojimbo::GetDefaultAllocator(), yojimbo::Address("0.0.0.0"), m_connectionConfig, m_adapter, 0.0) { uint64_t clientId; - yojimbo::random_bytes((uint8_t*)&clientId, 8); + yojimbo_random_bytes((uint8_t*)&clientId, 8); m_client.InsecureConnect(DEFAULT_PRIVATE_KEY, clientId, m_serverAddress); } ``` From bfa864e7863322724ddaa9f8fd16b803e7028807 Mon Sep 17 00:00:00 2001 From: kbirk Date: Fri, 21 Jun 2024 20:29:35 -0400 Subject: [PATCH 16/39] Add GetClientUserData method. --- include/yojimbo_server.h | 2 ++ include/yojimbo_server_interface.h | 7 +++++++ source/yojimbo_server.cpp | 33 +++++++++++++++++------------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/include/yojimbo_server.h b/include/yojimbo_server.h index 33247b23..4db011e3 100644 --- a/include/yojimbo_server.h +++ b/include/yojimbo_server.h @@ -63,6 +63,8 @@ namespace yojimbo uint64_t GetClientId( int clientIndex ) const; + const uint8_t * GetClientUserData( int clientIndex ) const; + netcode_address_t * GetClientAddress( int clientIndex ) const; int GetNumConnectedClients() const; diff --git a/include/yojimbo_server_interface.h b/include/yojimbo_server_interface.h index a5b278b4..98b68af4 100644 --- a/include/yojimbo_server_interface.h +++ b/include/yojimbo_server_interface.h @@ -143,6 +143,13 @@ namespace yojimbo virtual uint64_t GetClientId( int clientIndex ) const = 0; + /** + Get the user data of the client. + @param clientIndex the index of the client slot in [0,maxClients-1], where maxClients corresponds to the value passed into the last call to Server::Start. + @returns The user data of the client. + */ + const uint8_t * GetClientUserData( int clientIndex ) const; + /** Get the address of the client @param clientIndex the index of the client slot in [0,maxClients-1], where maxClients corresponds to the value passed into the last call to Server::Start. diff --git a/source/yojimbo_server.cpp b/source/yojimbo_server.cpp index 634bbc22..470461b4 100644 --- a/source/yojimbo_server.cpp +++ b/source/yojimbo_server.cpp @@ -7,17 +7,17 @@ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ @@ -31,7 +31,7 @@ namespace yojimbo { - Server::Server( Allocator & allocator, const uint8_t privateKey[], const Address & address, const ClientServerConfig & config, Adapter & adapter, double time ) + Server::Server( Allocator & allocator, const uint8_t privateKey[], const Address & address, const ClientServerConfig & config, Adapter & adapter, double time ) : BaseServer( allocator, config, adapter, time ) { yojimbo_assert( KeyBytes == NETCODE_KEY_BYTES ); @@ -52,12 +52,12 @@ namespace yojimbo { if ( IsRunning() ) Stop(); - + BaseServer::Start( maxClients ); - + char addressString[MaxAddressLength]; m_address.ToString( addressString, MaxAddressLength ); - + struct netcode_server_config_t netcodeConfig; netcode_default_server_config(&netcodeConfig); netcodeConfig.protocol_id = m_config.protocolId; @@ -68,15 +68,15 @@ namespace yojimbo netcodeConfig.callback_context = this; netcodeConfig.connect_disconnect_callback = StaticConnectDisconnectCallbackFunction; netcodeConfig.send_loopback_packet_callback = StaticSendLoopbackPacketCallbackFunction; - + m_server = netcode_server_create(addressString, &netcodeConfig, GetTime()); - + if ( !m_server ) { Stop(); return; } - + netcode_server_start( m_server, maxClients ); m_boundAddress.SetPort( netcode_server_get_port( m_server ) ); @@ -186,6 +186,11 @@ namespace yojimbo return netcode_server_client_id( m_server, clientIndex ); } + const uint8_t * Server::GetClientUserData( int clientIndex ) const + { + return (const uint8_t*)netcode_server_client_user_data( m_server, clientIndex ); + } + netcode_address_t * Server::GetClientAddress( int clientIndex ) const { return netcode_server_client_address( m_server, clientIndex ); From 8401f6593d1e9757d9a5fc37e8933aea037473a9 Mon Sep 17 00:00:00 2001 From: kbirk Date: Mon, 1 Jul 2024 20:55:55 -0400 Subject: [PATCH 17/39] Fix address offset bug in matcher code. --- matcher/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matcher/main.go b/matcher/main.go index fff96e86..30f3d7c9 100644 --- a/matcher/main.go +++ b/matcher/main.go @@ -60,13 +60,14 @@ func writeAddresses(buffer []byte, addresses []net.UDPAddr) int { buffer[offset+4] = ipv4[3] buffer[offset+5] = (byte)(port & 0xFF) buffer[offset+6] = (byte)(port >> 8) + offset += 7 } else { buffer[offset] = addressIPV6 copy(buffer[offset+1:], addr.IP) buffer[offset+17] = (byte)(port & 0xFF) buffer[offset+18] = (byte)(port >> 8) + offset += 19 } - offset += 19 } return offset } From 3ee226b9cf848fff95752b7325c017488da2aaf6 Mon Sep 17 00:00:00 2001 From: kbirk Date: Mon, 1 Jul 2024 20:57:43 -0400 Subject: [PATCH 18/39] Small fix. --- matcher/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/matcher/main.go b/matcher/main.go index 30f3d7c9..256c5f63 100644 --- a/matcher/main.go +++ b/matcher/main.go @@ -179,9 +179,9 @@ func (token *connectToken) Write(buffer []byte) error { return errors.Wrap(err, "failed to encrypt message") } binary.LittleEndian.PutUint32(buffer[connectTokenPrivateBytes+61:], (uint32)(token.TimeoutSeconds)) - offset := writeAddresses(buffer[1024+61+4:], token.ServerAddresses) - copy(buffer[1024+61+4+offset:], token.ClientToServerKey[:]) - copy(buffer[1024+61+4+offset+keyBytes:], token.ServerToClientKey[:]) + offset := writeAddresses(buffer[connectTokenPrivateBytes+61+4:], token.ServerAddresses) + copy(buffer[connectTokenPrivateBytes+61+4+offset:], token.ClientToServerKey[:]) + copy(buffer[connectTokenPrivateBytes+61+4+offset+keyBytes:], token.ServerToClientKey[:]) return nil } From bb24022ed8d67c1cb094c6950bb9772a0c1c7166 Mon Sep 17 00:00:00 2001 From: kbirk Date: Tue, 2 Jul 2024 20:46:28 -0400 Subject: [PATCH 19/39] Shot in the dark --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd5cf5c5..9b86a464 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,17 @@ jobs: submodules: recursive - name: Setup premake uses: abel0b/setup-premake@v1 - + # Install libsodium from apt - name: Setup (Linux) if: runner.os == 'Linux' run: | sudo apt-get install libsodium-dev - + # Install libsodium from brew - name: Setup (MacOS) if: runner.os == 'MacOS' - run: brew install libsodium + run: brew reinstall libsodium # Build with premake + make - name: Build (gmake) @@ -64,4 +64,3 @@ jobs: - name: Test (vs2019) if: runner.os == 'Windows' run: "& ./bin/${{ matrix.configuration }}/test.exe" - From f94d8d308dd1473dd17b27fcaeb9fa41aa2ce9dd Mon Sep 17 00:00:00 2001 From: kbirk Date: Tue, 2 Jul 2024 20:53:22 -0400 Subject: [PATCH 20/39] Revert "Shot in the dark" This reverts commit bb24022ed8d67c1cb094c6950bb9772a0c1c7166. --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b86a464..cd5cf5c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,17 +19,17 @@ jobs: submodules: recursive - name: Setup premake uses: abel0b/setup-premake@v1 - + # Install libsodium from apt - name: Setup (Linux) if: runner.os == 'Linux' run: | sudo apt-get install libsodium-dev - + # Install libsodium from brew - name: Setup (MacOS) if: runner.os == 'MacOS' - run: brew reinstall libsodium + run: brew install libsodium # Build with premake + make - name: Build (gmake) @@ -64,3 +64,4 @@ jobs: - name: Test (vs2019) if: runner.os == 'Windows' run: "& ./bin/${{ matrix.configuration }}/test.exe" + From 43eff6f6c89369782a82c36e10455b340db628f1 Mon Sep 17 00:00:00 2001 From: kbirk Date: Tue, 2 Jul 2024 21:03:30 -0400 Subject: [PATCH 21/39] Is the runner apple silicon? --- premake5.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/premake5.lua b/premake5.lua index f2f1f715..e9d2640b 100644 --- a/premake5.lua +++ b/premake5.lua @@ -7,7 +7,7 @@ solution "Yojimbo" configurations { "Debug", "Release" } includedirs { ".", "include", "sodium", "tlsf", "netcode", "reliable", "serialize" } if not os.istarget "windows" then - targetdir "bin/" + targetdir "bin/" end rtti "Off" warnings "Extra" @@ -64,6 +64,7 @@ project "client" links { "yojimbo", "sodium-builtin", "tlsf", "netcode", "reliable" } filter "system:not windows" links { "yojimbo", "sodium", "tlsf", "netcode", "reliable" } + libdirs { "/opt/homebrew/lib" } project "server" files { "server.cpp", "shared.h" } @@ -71,6 +72,7 @@ project "server" links { "yojimbo", "sodium-builtin", "tlsf", "netcode", "reliable" } filter "system:not windows" links { "yojimbo", "sodium", "tlsf", "netcode", "reliable" } + libdirs { "/opt/homebrew/lib" } project "loopback" files { "loopback.cpp", "shared.h" } @@ -78,6 +80,7 @@ project "loopback" links { "yojimbo", "sodium-builtin", "tlsf", "netcode", "reliable" } filter "system:not windows" links { "yojimbo", "sodium", "tlsf", "netcode", "reliable" } + libdirs { "/opt/homebrew/lib" } project "soak" files { "soak.cpp", "shared.h" } @@ -85,6 +88,7 @@ project "soak" links { "yojimbo", "sodium-builtin", "tlsf", "netcode", "reliable" } filter "system:not windows" links { "yojimbo", "sodium", "tlsf", "netcode", "reliable" } + libdirs { "/opt/homebrew/lib" } project "test" files { "test.cpp" } @@ -93,6 +97,7 @@ project "test" links { "yojimbo", "sodium-builtin", "tlsf", "netcode", "reliable" } filter "system:not windows" links { "yojimbo", "sodium", "tlsf", "netcode", "reliable" } + libdirs { "/opt/homebrew/lib" } newaction { @@ -102,7 +107,7 @@ newaction execute = function () - files_to_delete = + files_to_delete = { "Makefile", "*.make", @@ -120,7 +125,7 @@ newaction "*.xcworkspace" } - directories_to_delete = + directories_to_delete = { "obj", "ipch", From c006f7121047e5f1f660f2f8411c726411c35728 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 30 Aug 2024 19:43:40 -0400 Subject: [PATCH 22/39] add some safety asserts to help catch when MaxClients is set too low for tests, channelIndex --- include/yojimbo_config.h | 3 +- include/yojimbo_constants.h | 2 +- include/yojimbo_network_info.h | 6 +- reliable/reliable.c | 120 +++++++++++++++++++++++++++++++-- reliable/reliable.h | 11 ++- source/yojimbo_base_client.cpp | 12 ++++ source/yojimbo_base_server.cpp | 27 ++++++++ source/yojimbo_server.cpp | 3 +- test.cpp | 34 +++++----- 9 files changed, 190 insertions(+), 28 deletions(-) diff --git a/include/yojimbo_config.h b/include/yojimbo_config.h index 541d1a30..d4a2f2e9 100644 --- a/include/yojimbo_config.h +++ b/include/yojimbo_config.h @@ -170,8 +170,9 @@ namespace yojimbo ConnectionConfig() { - numChannels = 1; + numChannels = 2; maxPacketSize = 8 * 1024; + channel[0].type = CHANNEL_TYPE_RELIABLE_ORDERED; } }; diff --git a/include/yojimbo_constants.h b/include/yojimbo_constants.h index 3833c631..147c910f 100644 --- a/include/yojimbo_constants.h +++ b/include/yojimbo_constants.h @@ -31,7 +31,7 @@ namespace yojimbo { const int MaxClients = 64; ///< The maximum number of clients supported by this library. You can increase this if you want, but this library is designed around patterns that work best for [2,64] player games. If your game has less than 64 clients, reducing this will save memory. - const int MaxChannels = 64; ///< The maximum number of message channels supported by this library. If you need less than 64 channels per-packet, reducing this will save memory. + const int MaxChannels = 2; ///< The maximum number of message channels supported by this library. If you need less than 64 channels per-packet, reducing this will save memory. const int KeyBytes = 32; ///< Size of encryption key for dedicated client/server in bytes. Must be equal to key size for libsodium encryption primitive. Do not change. diff --git a/include/yojimbo_network_info.h b/include/yojimbo_network_info.h index 7195a192..756dcb53 100644 --- a/include/yojimbo_network_info.h +++ b/include/yojimbo_network_info.h @@ -36,7 +36,11 @@ namespace yojimbo struct NetworkInfo { - float RTT; ///< Round trip time estimate (milliseconds). + float RTT; ///< Round trip time estimate (milliseconds). Exponentially smoothed average tracking most recent RTT value. + float minRTT; ///< Minimum RTT seen over the last n samples (see rtt_history_size in reliable config). This is a more stable and accurate RTT value under typical Wi-Fi jitter. + float maxRTT; ///< Maximum RTT seen over the last n samples. + float averageRTT; ///< Average RTT seen over the last n samples. + float jitter; ///< Jitter relative to min RTT over the last n samples. float packetLoss; ///< Packet loss percent. float sentBandwidth; ///< Sent bandwidth (kbps). float receivedBandwidth; ///< Received bandwidth (kbps). diff --git a/reliable/reliable.c b/reliable/reliable.c index c8638020..4733a1c0 100644 --- a/reliable/reliable.c +++ b/reliable/reliable.c @@ -508,6 +508,10 @@ struct reliable_endpoint_t struct reliable_config_t config; double time; float rtt; + float rtt_min; + float rtt_max; + float rtt_avg; + float jitter; float packet_loss; float sent_bandwidth_kbps; float received_bandwidth_kbps; @@ -515,6 +519,7 @@ struct reliable_endpoint_t int num_acks; uint16_t * acks; uint16_t sequence; + float * rtt_history_buffer; struct reliable_sequence_buffer_t * sent_packets; struct reliable_sequence_buffer_t * received_packets; struct reliable_sequence_buffer_t * fragment_reassembly; @@ -556,9 +561,10 @@ void reliable_default_config( struct reliable_config_t * config ) config->received_packets_buffer_size = 256; config->fragment_reassembly_buffer_size = 64; config->rtt_smoothing_factor = 0.0025f; + config->rtt_history_size = 512; config->packet_loss_smoothing_factor = 0.1f; config->bandwidth_smoothing_factor = 0.1f; - config->packet_header_size = 28; // note: UDP over IPv4 = 20 + 8 bytes, UDP over IPv6 = 40 + 8 bytes + config->packet_header_size = 28; // note: UDP over IPv4 = 20 + 8 bytes, UDP over IPv6 = 40 + 8 bytes } struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t * config, double time ) @@ -574,6 +580,7 @@ struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t reliable_assert( config->received_packets_buffer_size > 0 ); reliable_assert( config->transmit_packet_function != NULL ); reliable_assert( config->process_packet_function != NULL ); + reliable_assert( config->rtt_history_size > 0 ); void * allocator_context = config->allocator_context; void * (*allocate_function)(void*,size_t) = config->allocate_function; @@ -601,7 +608,7 @@ struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t endpoint->config = *config; endpoint->time = time; - endpoint->acks = (uint16_t*) allocate_function( allocator_context, config->ack_buffer_size * sizeof( uint16_t ) ); + endpoint->acks = (uint16_t*) allocate_function( allocator_context, config->ack_buffer_size * sizeof(uint16_t) ); endpoint->sent_packets = reliable_sequence_buffer_create( config->sent_packets_buffer_size, sizeof( struct reliable_sent_packet_data_t ), @@ -621,7 +628,14 @@ struct reliable_endpoint_t * reliable_endpoint_create( struct reliable_config_t allocate_function, free_function ); - memset( endpoint->acks, 0, config->ack_buffer_size * sizeof( uint16_t ) ); + endpoint->rtt_history_buffer = (float*) allocate_function( allocator_context, config->rtt_history_size * sizeof(float) ); + + for ( int i = 0; i < config->rtt_history_size; i++ ) + { + endpoint->rtt_history_buffer[i] = -1.0f; + } + + memset( endpoint->acks, 0, config->ack_buffer_size * sizeof(uint16_t) ); return endpoint; } @@ -632,6 +646,8 @@ void reliable_endpoint_destroy( struct reliable_endpoint_t * endpoint ) reliable_assert( endpoint->acks ); reliable_assert( endpoint->sent_packets ); reliable_assert( endpoint->received_packets ); + reliable_assert( endpoint->fragment_reassembly ); + reliable_assert( endpoint->rtt_history_buffer ); int i; for ( i = 0; i < endpoint->config.fragment_reassembly_buffer_size; ++i ) @@ -652,6 +668,8 @@ void reliable_endpoint_destroy( struct reliable_endpoint_t * endpoint ) reliable_sequence_buffer_destroy( endpoint->received_packets ); reliable_sequence_buffer_destroy( endpoint->fragment_reassembly ); + endpoint->free_function( endpoint->allocator_context, endpoint->rtt_history_buffer ); + endpoint->free_function( endpoint->allocator_context, endpoint ); } @@ -1141,8 +1159,14 @@ void reliable_endpoint_receive_packet( struct reliable_endpoint_t * endpoint, ui endpoint->counters[RELIABLE_ENDPOINT_COUNTER_NUM_PACKETS_ACKED]++; sent_packet_data->acked = 1; - float rtt = (float) ( endpoint->time - sent_packet_data->time ) * 1000.0f; + const float rtt = (float) ( endpoint->time - sent_packet_data->time ) * 1000.0f; + reliable_assert( rtt >= 0.0 ); + + int index = ack_sequence % endpoint->config.rtt_history_size; + + endpoint->rtt_history_buffer[index] = rtt; + if ( ( endpoint->rtt == 0.0f && rtt > 0.0f ) || fabs( endpoint->rtt - rtt ) < 0.00001 ) { endpoint->rtt = rtt; @@ -1320,7 +1344,67 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim reliable_assert( endpoint ); endpoint->time = time; - + + // calculate min and max rtt + { + float min_rtt = 10000.0f; + float max_rtt = 0.0f; + float sum_rtt = 0.0f; + int count = 0; + for ( int i = 0; i < endpoint->config.rtt_history_size; i++ ) + { + const float rtt = endpoint->rtt_history_buffer[i]; + if ( rtt >= 0.0f ) + { + if ( rtt < min_rtt ) + { + min_rtt = rtt; + } + if ( rtt > max_rtt ) + { + max_rtt = rtt; + } + sum_rtt += rtt; + count++; + } + } + if ( min_rtt == 10000.0f ) + { + min_rtt = 0.0f; + } + endpoint->rtt_min = min_rtt; + endpoint->rtt_max = max_rtt; + if ( count > 0 ) + { + endpoint->rtt_avg = sum_rtt / (float)count; + } + else + { + endpoint->rtt_avg = 0.0f; + } + } + + // calculate jitter + { + float sum = 0.0f; + int count = 0; + for ( int i = 0; i < endpoint->config.rtt_history_size; i++ ) + { + if ( endpoint->rtt_history_buffer[i] >= 0.0f ) + { + sum += endpoint->rtt_history_buffer[i]; + } + } + if ( count > 0 ) + { + endpoint->jitter = sum / (float)count; + } + else + { + endpoint->jitter = 0.0f; + } + } + // calculate packet loss { uint32_t base_sequence = ( endpoint->sent_packets->sequence - endpoint->config.sent_packets_buffer_size + 1 ) + 0xFFFF; @@ -1478,6 +1562,30 @@ float reliable_endpoint_rtt( struct reliable_endpoint_t * endpoint ) return endpoint->rtt; } +float reliable_endpoint_rtt_min( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->rtt_min; +} + +float reliable_endpoint_rtt_max( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->rtt_max; +} + +float reliable_endpoint_rtt_avg( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->rtt_avg; +} + +float reliable_endpoint_jitter( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->jitter; +} + float reliable_endpoint_packet_loss( struct reliable_endpoint_t * endpoint ) { reliable_assert( endpoint ); @@ -2382,7 +2490,7 @@ void test_fragment_cleanup() void reliable_test() { - //while ( 1 ) + // while ( 1 ) { RUN_TEST( test_endian ); RUN_TEST( test_sequence_buffer ); diff --git a/reliable/reliable.h b/reliable/reliable.h index 63b392a4..2aafb07f 100644 --- a/reliable/reliable.h +++ b/reliable/reliable.h @@ -68,7 +68,7 @@ #define RELIABLE_ENDPOINT_NUM_COUNTERS 10 #define RELIABLE_MAX_PACKET_HEADER_BYTES 9 -#define RELIABLE_FRAGMENT_HEADER_BYTES 5 +#define RELIABLE_FRAGMENT_HEADER_BYTES 5 #define RELIABLE_LOG_LEVEL_NONE 0 #define RELIABLE_LOG_LEVEL_ERROR 1 @@ -107,6 +107,7 @@ struct reliable_config_t int received_packets_buffer_size; int fragment_reassembly_buffer_size; float rtt_smoothing_factor; + int rtt_history_size; float packet_loss_smoothing_factor; float bandwidth_smoothing_factor; int packet_header_size; @@ -139,6 +140,14 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim float reliable_endpoint_rtt( struct reliable_endpoint_t * endpoint ); +float reliable_endpoint_rtt_min( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_rtt_max( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_rtt_avg( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_jitter( struct reliable_endpoint_t * endpoint ); + float reliable_endpoint_packet_loss( struct reliable_endpoint_t * endpoint ); void reliable_endpoint_bandwidth( struct reliable_endpoint_t * endpoint, float * sent_bandwidth_kbps, float * received_bandwidth_kbps, float * acked_bandwidth_kpbs ); diff --git a/source/yojimbo_base_client.cpp b/source/yojimbo_base_client.cpp index 01c8efeb..adb611d6 100644 --- a/source/yojimbo_base_client.cpp +++ b/source/yojimbo_base_client.cpp @@ -235,24 +235,32 @@ namespace yojimbo bool BaseClient::CanSendMessage( int channelIndex ) const { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_connection->CanSendMessage( channelIndex ); } bool BaseClient::HasMessagesToSend( int channelIndex ) const { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_connection->HasMessagesToSend( channelIndex ); } void BaseClient::SendMessage( int channelIndex, Message * message ) { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); m_connection->SendMessage( channelIndex, message, GetContext() ); } Message * BaseClient::ReceiveMessage( int channelIndex ) { yojimbo_assert( m_connection ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_connection->ReceiveMessage( channelIndex ); } @@ -273,6 +281,10 @@ namespace yojimbo info.numPacketsReceived = counters[RELIABLE_ENDPOINT_COUNTER_NUM_PACKETS_RECEIVED]; info.numPacketsAcked = counters[RELIABLE_ENDPOINT_COUNTER_NUM_PACKETS_ACKED]; info.RTT = reliable_endpoint_rtt( m_endpoint ); + info.minRTT = reliable_endpoint_rtt_min( m_endpoint ); + info.maxRTT = reliable_endpoint_rtt_min( m_endpoint ); + info.averageRTT = reliable_endpoint_rtt_avg( m_endpoint ); + info.jitter = reliable_endpoint_jitter( m_endpoint ); info.packetLoss = reliable_endpoint_packet_loss( m_endpoint ); reliable_endpoint_bandwidth( m_endpoint, &info.sentBandwidth, &info.receivedBandwidth, &info.ackedBandwidth ); } diff --git a/source/yojimbo_base_server.cpp b/source/yojimbo_base_server.cpp index d60c4403..7f31d1c2 100644 --- a/source/yojimbo_base_server.cpp +++ b/source/yojimbo_base_server.cpp @@ -45,24 +45,33 @@ namespace yojimbo void BaseServer::Start( int maxClients ) { + yojimbo_assert( maxClients <= MaxClients ); + Stop(); + m_running = true; m_maxClients = maxClients; + yojimbo_assert( !m_globalMemory ); yojimbo_assert( !m_globalAllocator ); m_globalMemory = (uint8_t*) YOJIMBO_ALLOCATE( *m_allocator, m_config.serverGlobalMemory ); + m_globalAllocator = m_adapter->CreateAllocator( *m_allocator, m_globalMemory, m_config.serverGlobalMemory ); yojimbo_assert( m_globalAllocator ); + if ( m_config.networkSimulator ) { m_networkSimulator = YOJIMBO_NEW( *m_globalAllocator, NetworkSimulator, *m_globalAllocator, m_config.maxSimulatorPackets, m_time ); } + for ( int i = 0; i < m_maxClients; ++i ) { yojimbo_assert( !m_clientMemory[i] ); yojimbo_assert( !m_clientAllocator[i] ); m_clientMemory[i] = (uint8_t*) YOJIMBO_ALLOCATE( *m_allocator, m_config.serverPerClientMemory ); + yojimbo_assert( m_clientMemory[i] ); + m_clientAllocator[i] = m_adapter->CreateAllocator( *m_allocator, m_clientMemory[i], m_config.serverPerClientMemory ); yojimbo_assert( m_clientAllocator[i] ); @@ -104,6 +113,8 @@ namespace yojimbo yojimbo_assert( m_globalMemory ); yojimbo_assert( m_globalAllocator ); YOJIMBO_DELETE( *m_globalAllocator, NetworkSimulator, m_networkSimulator ); + yojimbo_assert( m_maxClients > 0 ); + yojimbo_assert( m_maxClients <= MaxClients ); for ( int i = 0; i < m_maxClients; ++i ) { yojimbo_assert( m_clientMemory[i] ); @@ -119,6 +130,14 @@ namespace yojimbo YOJIMBO_DELETE( *m_allocator, Allocator, m_globalAllocator ); YOJIMBO_FREE( *m_allocator, m_globalMemory ); } + for ( int i = 0; i < MaxClients; ++i ) + { + m_clientMemory[i] = NULL; + m_clientAllocator[i] = NULL; + m_clientMessageFactory[i] = NULL; + m_clientConnection[i] = NULL; + m_clientEndpoint[i] = NULL; + } m_running = false; m_maxClients = 0; m_packetBuffer = NULL; @@ -223,6 +242,8 @@ namespace yojimbo { yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); yojimbo_assert( m_clientConnection[clientIndex] ); return m_clientConnection[clientIndex]->CanSendMessage( channelIndex ); } @@ -232,6 +253,8 @@ namespace yojimbo yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); yojimbo_assert( m_clientConnection[clientIndex] ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_clientConnection[clientIndex]->HasMessagesToSend( channelIndex ); } @@ -240,6 +263,8 @@ namespace yojimbo yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); yojimbo_assert( m_clientConnection[clientIndex] ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_clientConnection[clientIndex]->SendMessage( channelIndex, message, GetContext() ); } @@ -248,6 +273,8 @@ namespace yojimbo yojimbo_assert( clientIndex >= 0 ); yojimbo_assert( clientIndex < m_maxClients ); yojimbo_assert( m_clientConnection[clientIndex] ); + yojimbo_assert( channelIndex >= 0 ); + yojimbo_assert( channelIndex < m_config.numChannels ); return m_clientConnection[clientIndex]->ReceiveMessage( channelIndex ); } diff --git a/source/yojimbo_server.cpp b/source/yojimbo_server.cpp index 470461b4..dd16ffeb 100644 --- a/source/yojimbo_server.cpp +++ b/source/yojimbo_server.cpp @@ -50,8 +50,7 @@ namespace yojimbo void Server::Start( int maxClients ) { - if ( IsRunning() ) - Stop(); + yojimbo_assert( maxClients <= MaxClients ); BaseServer::Start( maxClients ); diff --git a/test.cpp b/test.cpp index 480b27c8..ae3a1301 100644 --- a/test.cpp +++ b/test.cpp @@ -618,6 +618,8 @@ void PumpConnectionUpdate( ConnectionConfig & connectionConfig, double & time, C receiverSequence++; } +const int ReliableChannel = 0; + void test_connection_reliable_ordered_messages() { TestMessageFactory messageFactory( GetDefaultAllocator() ); @@ -636,7 +638,7 @@ void test_connection_reliable_ordered_messages() TestMessage * message = (TestMessage*) messageFactory.CreateMessage( TEST_MESSAGE ); check( message ); message->sequence = i; - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } const int SenderPort = 10000; @@ -658,7 +660,7 @@ void test_connection_reliable_ordered_messages() while ( true ) { - Message * message = receiver.ReceiveMessage( 0 ); + Message * message = receiver.ReceiveMessage( ReliableChannel ); if ( !message ) break; @@ -704,7 +706,7 @@ void test_connection_reliable_ordered_blocks() for ( int j = 0; j < blockSize; ++j ) blockData[j] = i + j; message->AttachBlock( messageFactory.GetAllocator(), blockData, blockSize ); - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } const int SenderPort = 10000; @@ -726,7 +728,7 @@ void test_connection_reliable_ordered_blocks() while ( true ) { - Message * message = receiver.ReceiveMessage( 0 ); + Message * message = receiver.ReceiveMessage( ReliableChannel ); if ( !message ) break; @@ -784,7 +786,7 @@ void test_connection_reliable_ordered_messages_and_blocks() TestMessage * message = (TestMessage*) messageFactory.CreateMessage( TEST_MESSAGE ); check( message ); message->sequence = i; - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } else { @@ -796,7 +798,7 @@ void test_connection_reliable_ordered_messages_and_blocks() for ( int j = 0; j < blockSize; ++j ) blockData[j] = i + j; message->AttachBlock( messageFactory.GetAllocator(), blockData, blockSize ); - sender.SendMessage( 0, message ); + sender.SendMessage( ReliableChannel, message ); } } @@ -819,7 +821,7 @@ void test_connection_reliable_ordered_messages_and_blocks() while ( true ) { - Message * message = receiver.ReceiveMessage( 0 ); + Message * message = receiver.ReceiveMessage( ReliableChannel ); if ( !message ) break; @@ -1179,7 +1181,7 @@ void PumpClientServerUpdate( double & time, Client ** client, int numClients, Se yojimbo_sleep( 0.0f ); } -void SendClientToServerMessages( Client & client, int numMessagesToSend, int channelIndex = 0 ) +void SendClientToServerMessages( Client & client, int numMessagesToSend, int channelIndex = ReliableChannel ) { for ( int i = 0; i < numMessagesToSend; ++i ) { @@ -1209,7 +1211,7 @@ void SendClientToServerMessages( Client & client, int numMessagesToSend, int cha } } -void SendServerToClientMessages( Server & server, int clientIndex, int numMessagesToSend, int channelIndex = 0 ) +void SendServerToClientMessages( Server & server, int clientIndex, int numMessagesToSend, int channelIndex = ReliableChannel ) { for ( int i = 0; i < numMessagesToSend; ++i ) { @@ -1239,11 +1241,11 @@ void SendServerToClientMessages( Server & server, int clientIndex, int numMessag } } -void ProcessServerToClientMessages( Client & client, int & numMessagesReceivedFromServer ) +void ProcessServerToClientMessages( Client & client, int & numMessagesReceivedFromServer, int channelIndex = ReliableChannel ) { while ( true ) { - Message * message = client.ReceiveMessage( 0 ); + Message * message = client.ReceiveMessage( channelIndex ); if ( !message ) break; @@ -1283,11 +1285,11 @@ void ProcessServerToClientMessages( Client & client, int & numMessagesReceivedFr } } -void ProcessClientToServerMessages( Server & server, int clientIndex, int & numMessagesReceivedFromClient ) +void ProcessClientToServerMessages( Server & server, int clientIndex, int & numMessagesReceivedFromClient, int channelIndex = ReliableChannel ) { while ( true ) { - Message * message = server.ReceiveMessage( clientIndex, 0 ); + Message * message = server.ReceiveMessage( clientIndex, channelIndex ); if ( !message ) break; @@ -1511,19 +1513,19 @@ void test_client_server_start_stop_restart() Server server( GetDefaultAllocator(), privateKey, serverAddress, config, adapter, time ); - server.Start( MaxClients ); - server.SetLatency( 250 ); server.SetJitter( 100 ); server.SetPacketLoss( 25 ); server.SetDuplicates( 25 ); - int numClients[] = { 3, 5, 1 }; + int numClients[] = { 3, 5, 1, 32, 5 }; const int NumIterations = sizeof( numClients ) / sizeof( int ); for ( int iteration = 0; iteration < NumIterations; ++iteration ) { + yojimbo_assert( numClients[iteration] < MaxClients ); + server.Start( numClients[iteration] ); Client * clients[MaxClients]; From 5e7282a839dfeb4cb3faa64ebae429015fb3a790 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 30 Aug 2024 19:46:45 -0400 Subject: [PATCH 23/39] make unit tests resiliient even if you reduce NumChannels to 1, and MaxClients to 1 --- test.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test.cpp b/test.cpp index ae3a1301..0ff39b92 100644 --- a/test.cpp +++ b/test.cpp @@ -1524,7 +1524,11 @@ void test_client_server_start_stop_restart() for ( int iteration = 0; iteration < NumIterations; ++iteration ) { - yojimbo_assert( numClients[iteration] < MaxClients ); + numClients[iteration] = numClients[iteration] % MaxClients; + if ( numClients[iteration] == 0 ) + { + numClients[iteration] = 1; + } server.Start( numClients[iteration] ); From b128d94aade45bc5642368365e2eb80f36c9ef7b Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 30 Aug 2024 19:48:00 -0400 Subject: [PATCH 24/39] comment update. min max channels is 2 realistically --- include/yojimbo_constants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/yojimbo_constants.h b/include/yojimbo_constants.h index 147c910f..1ff45a1e 100644 --- a/include/yojimbo_constants.h +++ b/include/yojimbo_constants.h @@ -31,7 +31,7 @@ namespace yojimbo { const int MaxClients = 64; ///< The maximum number of clients supported by this library. You can increase this if you want, but this library is designed around patterns that work best for [2,64] player games. If your game has less than 64 clients, reducing this will save memory. - const int MaxChannels = 2; ///< The maximum number of message channels supported by this library. If you need less than 64 channels per-packet, reducing this will save memory. + const int MaxChannels = 2; ///< The maximum number of message channels supported by this library. If you need less than 64 channels per-packet, reducing this will save memory. Minimum is 2. const int KeyBytes = 32; ///< Size of encryption key for dedicated client/server in bytes. Must be equal to key size for libsodium encryption primitive. Do not change. From 83985c54510bdf5bdb87673f8129e13ec556e900 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 30 Aug 2024 19:55:15 -0400 Subject: [PATCH 25/39] bump patch version --- include/yojimbo_config.h | 2 +- test.cpp | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/yojimbo_config.h b/include/yojimbo_config.h index d4a2f2e9..034cb817 100644 --- a/include/yojimbo_config.h +++ b/include/yojimbo_config.h @@ -33,7 +33,7 @@ #define YOJIMBO_MAJOR_VERSION 1 #define YOJIMBO_MINOR_VERSION 0 -#define YOJIMBO_PATCH_VERSION 0 +#define YOJIMBO_PATCH_VERSION 1 #if !defined(YOJIMBO_DEBUG) && !defined(YOJIMBO_RELEASE) #if defined(NDEBUG) diff --git a/test.cpp b/test.cpp index 0ff39b92..fbb90ffc 100644 --- a/test.cpp +++ b/test.cpp @@ -877,6 +877,9 @@ void test_connection_reliable_ordered_messages_and_blocks_multiple_channels() { const int NumChannels = 2; + yojimbo_assert( NumChannels >= 0 ); + yojimbo_assert( NumChannels <= MaxChannels ); + double time = 100.0; TestMessageFactory messageFactory( GetDefaultAllocator() ); @@ -1914,6 +1917,8 @@ void test_reliable_outbound_sequence_outdated() config.numChannels = 2; config.timeout = -1; + yojimbo_assert( config.numChannels <= MaxChannels ); + const int BlockSize = config.channel[0].blockFragmentSize * 2; Client client( GetDefaultAllocator(), clientAddress, config, adapter, time ); From 3bb944a8ab02a92e21266166741e6c3eaac2f2fc Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Fri, 30 Aug 2024 19:58:04 -0400 Subject: [PATCH 26/39] bump to 1.2.4 --- include/yojimbo_config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/yojimbo_config.h b/include/yojimbo_config.h index 034cb817..12954043 100644 --- a/include/yojimbo_config.h +++ b/include/yojimbo_config.h @@ -32,8 +32,8 @@ #endif #define YOJIMBO_MAJOR_VERSION 1 -#define YOJIMBO_MINOR_VERSION 0 -#define YOJIMBO_PATCH_VERSION 1 +#define YOJIMBO_MINOR_VERSION 2 +#define YOJIMBO_PATCH_VERSION 4 #if !defined(YOJIMBO_DEBUG) && !defined(YOJIMBO_RELEASE) #if defined(NDEBUG) From 63a0b47ca8d0e2b52d3b1315244b1416edec5e7a Mon Sep 17 00:00:00 2001 From: Dan Bechard Date: Sat, 31 Aug 2024 16:56:10 -0400 Subject: [PATCH 27/39] typo fix Signed-off-by: Dan Bechard --- include/yojimbo_base_client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/yojimbo_base_client.h b/include/yojimbo_base_client.h index 6910c33a..db7d3c80 100644 --- a/include/yojimbo_base_client.h +++ b/include/yojimbo_base_client.h @@ -46,7 +46,7 @@ namespace yojimbo @param allocator The allocator for all memory used by the client. @param config The base client/server configuration. @param time The current time in seconds. See ClientInterface::AdvanceTime - @param allocator The adapter to the game program. Specifies allocators, message factory to use etc. + @param adapter The adapter to the game program. Specifies allocators, message factory to use etc. */ explicit BaseClient( class Allocator & allocator, const ClientServerConfig & config, class Adapter & adapter, double time ); From 2022d5bd59ab5bed8a6040a69311fa771c88d095 Mon Sep 17 00:00:00 2001 From: Dan Bechard Date: Sat, 31 Aug 2024 20:33:03 -0400 Subject: [PATCH 28/39] Update yojimbo_client_interface.h --- include/yojimbo_client_interface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/yojimbo_client_interface.h b/include/yojimbo_client_interface.h index 88b0c927..35fdd432 100644 --- a/include/yojimbo_client_interface.h +++ b/include/yojimbo_client_interface.h @@ -102,7 +102,7 @@ namespace yojimbo /** Is the client connected to a server? - This is true once a client successfully finishes connection negotiatio, and connects to a server. It is false while connecting to a server. + This is true once a client successfully finishes connection negotiation, and connects to a server. It is false while connecting to a server. @returns true if the client is connected to a server. */ From 10e97546bef5b525c93d0ffaa837883827246540 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Sat, 31 Aug 2024 21:32:26 -0400 Subject: [PATCH 29/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 746804fd..188d4910 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ It has the following features: * Packet fragmentation and reassembly * Bitpacker and serialization system * Reliable-ordered messages and data blocks -* Estimates of packet loss, latency and bandwidth usage +* Estimates of latency, jitter and packet loss yojimbo is stable and production ready. From 30c8366784080bfb85660d696d85a1cd17d971a4 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 3 Sep 2024 16:19:32 -0400 Subject: [PATCH 30/39] bring in updated version of reliable with fixed jitter calculation --- reliable/reliable.c | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/reliable/reliable.c b/reliable/reliable.c index 4733a1c0..825d99fe 100644 --- a/reliable/reliable.c +++ b/reliable/reliable.c @@ -1393,6 +1393,7 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim if ( endpoint->rtt_history_buffer[i] >= 0.0f ) { sum += endpoint->rtt_history_buffer[i]; + count++; } } if ( count > 0 ) @@ -1409,26 +1410,37 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim { uint32_t base_sequence = ( endpoint->sent_packets->sequence - endpoint->config.sent_packets_buffer_size + 1 ) + 0xFFFF; int i; + int num_sent = 0; int num_dropped = 0; int num_samples = endpoint->config.sent_packets_buffer_size / 2; for ( i = 0; i < num_samples; ++i ) { uint16_t sequence = (uint16_t) ( base_sequence + i ); - struct reliable_sent_packet_data_t * sent_packet_data = (struct reliable_sent_packet_data_t*) - reliable_sequence_buffer_find( endpoint->sent_packets, sequence ); - if ( sent_packet_data && !sent_packet_data->acked ) + struct reliable_sent_packet_data_t * sent_packet_data = (struct reliable_sent_packet_data_t*) reliable_sequence_buffer_find( endpoint->sent_packets, sequence ); + if ( sent_packet_data ) { - num_dropped++; + num_sent++; + if ( !sent_packet_data->acked ) + { + num_dropped++; + } } } - float packet_loss = ( (float) num_dropped ) / ( (float) num_samples ) * 100.0f; - if ( fabs( endpoint->packet_loss - packet_loss ) > 0.00001 ) + if ( num_sent > 0 ) { - endpoint->packet_loss += ( packet_loss - endpoint->packet_loss ) * endpoint->config.packet_loss_smoothing_factor; + float packet_loss = ( (float) num_dropped ) / ( (float) num_sent ) * 100.0f; + if ( fabs( endpoint->packet_loss - packet_loss ) > 0.00001 ) + { + endpoint->packet_loss += ( packet_loss - endpoint->packet_loss ) * endpoint->config.packet_loss_smoothing_factor; + } + else + { + endpoint->packet_loss = packet_loss; + } } else { - endpoint->packet_loss = packet_loss; + endpoint->packet_loss = 0.0f; } } From ddd3d33cde7df477fad52b8500a4f0dceecb0bf4 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 08:30:56 -0400 Subject: [PATCH 31/39] update reliable with jitter fix --- reliable/reliable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reliable/reliable.c b/reliable/reliable.c index 825d99fe..14d4c53d 100644 --- a/reliable/reliable.c +++ b/reliable/reliable.c @@ -1392,7 +1392,7 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim { if ( endpoint->rtt_history_buffer[i] >= 0.0f ) { - sum += endpoint->rtt_history_buffer[i]; + sum += ( endpoint->rtt_history_buffer[i] - endpoint->rtt_min ); count++; } } From 26bc006afac7de68813aa2c91ee30d4b683b4971 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 09:42:51 -0400 Subject: [PATCH 32/39] incorporate different jitter calculations from latest reliable --- include/yojimbo_network_info.h | 4 +- reliable/reliable.c | 67 +++++++++++++++++++++++++++++++--- reliable/reliable.h | 8 +++- source/yojimbo_base_client.cpp | 4 +- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/include/yojimbo_network_info.h b/include/yojimbo_network_info.h index 756dcb53..0dc40af2 100644 --- a/include/yojimbo_network_info.h +++ b/include/yojimbo_network_info.h @@ -40,7 +40,9 @@ namespace yojimbo float minRTT; ///< Minimum RTT seen over the last n samples (see rtt_history_size in reliable config). This is a more stable and accurate RTT value under typical Wi-Fi jitter. float maxRTT; ///< Maximum RTT seen over the last n samples. float averageRTT; ///< Average RTT seen over the last n samples. - float jitter; ///< Jitter relative to min RTT over the last n samples. + float averageJitter; ///< Average jitter relative to min RTT over the last n samples. + float maxJitter; ///< Max jitter relative to min RTT seen over the last n samples. + float stddevJitter; ///< One standard deviation of jitter relative to average RTT over the last n samples. float packetLoss; ///< Packet loss percent. float sentBandwidth; ///< Sent bandwidth (kbps). float receivedBandwidth; ///< Received bandwidth (kbps). diff --git a/reliable/reliable.c b/reliable/reliable.c index 14d4c53d..a0bcb976 100644 --- a/reliable/reliable.c +++ b/reliable/reliable.c @@ -511,7 +511,9 @@ struct reliable_endpoint_t float rtt_min; float rtt_max; float rtt_avg; - float jitter; + float jitter_avg_vs_min_rtt; + float jitter_max_vs_min_rtt; + float jitter_stddev_vs_avg_rtt; float packet_loss; float sent_bandwidth_kbps; float received_bandwidth_kbps; @@ -1384,7 +1386,7 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim } } - // calculate jitter + // calculate average jitter vs. min rtt { float sum = 0.0f; int count = 0; @@ -1398,11 +1400,51 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim } if ( count > 0 ) { - endpoint->jitter = sum / (float)count; + endpoint->jitter_avg_vs_min_rtt = sum / (float)count; } else { - endpoint->jitter = 0.0f; + endpoint->jitter_avg_vs_min_rtt = 0.0f; + } + } + + // calculate max jitter vs. min rtt + { + float max = 0.0f; + for ( int i = 0; i < endpoint->config.rtt_history_size; i++ ) + { + if ( endpoint->rtt_history_buffer[i] >= 0.0f ) + { + float difference = ( endpoint->rtt_history_buffer[i] - endpoint->rtt_min ); + if ( difference > max ) + { + max = difference; + } + } + } + endpoint->jitter_max_vs_min_rtt = max; + } + + // calculate stddev jitter vs. avg rtt + { + float sum = 0.0f; + int count = 0; + for ( int i = 0; i < endpoint->config.rtt_history_size; i++ ) + { + if ( endpoint->rtt_history_buffer[i] >= 0.0f ) + { + float deviation = ( endpoint->rtt_history_buffer[i] - endpoint->rtt_min ); + sum += deviation * deviation; + count++; + } + } + if ( count > 0 ) + { + endpoint->jitter_stddev_vs_avg_rtt = pow( sum / (float)count, 0.5f ); + } + else + { + endpoint->jitter_stddev_vs_avg_rtt = 0.0f; } } @@ -1592,10 +1634,23 @@ float reliable_endpoint_rtt_avg( struct reliable_endpoint_t * endpoint ) return endpoint->rtt_avg; } -float reliable_endpoint_jitter( struct reliable_endpoint_t * endpoint ) +float reliable_endpoint_jitter_avg_vs_min_rtt( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->jitter_avg_vs_min_rtt; +} + +float reliable_endpoint_jitter_max_vs_min_rtt( struct reliable_endpoint_t * endpoint ) +{ + reliable_assert( endpoint ); + return endpoint->jitter_max_vs_min_rtt; +} + + +float reliable_endpoint_jitter_stddev_vs_avg_rtt( struct reliable_endpoint_t * endpoint ) { reliable_assert( endpoint ); - return endpoint->jitter; + return endpoint->jitter_stddev_vs_avg_rtt; } float reliable_endpoint_packet_loss( struct reliable_endpoint_t * endpoint ) diff --git a/reliable/reliable.h b/reliable/reliable.h index 2aafb07f..e3c8e954 100644 --- a/reliable/reliable.h +++ b/reliable/reliable.h @@ -138,7 +138,7 @@ void reliable_endpoint_reset( struct reliable_endpoint_t * endpoint ); void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double time ); -float reliable_endpoint_rtt( struct reliable_endpoint_t * endpoint ); +float reliable_endpoint_rtt( struct reliable_endpoint_t * endpoint ); // exponentially smoothed moving average float reliable_endpoint_rtt_min( struct reliable_endpoint_t * endpoint ); @@ -146,7 +146,11 @@ float reliable_endpoint_rtt_max( struct reliable_endpoint_t * endpoint ); float reliable_endpoint_rtt_avg( struct reliable_endpoint_t * endpoint ); -float reliable_endpoint_jitter( struct reliable_endpoint_t * endpoint ); +float reliable_endpoint_jitter_avg_vs_min_rtt( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_jitter_max_vs_min_rtt( struct reliable_endpoint_t * endpoint ); + +float reliable_endpoint_jitter_stddev_vs_avg_rtt( struct reliable_endpoint_t * endpoint ); float reliable_endpoint_packet_loss( struct reliable_endpoint_t * endpoint ); diff --git a/source/yojimbo_base_client.cpp b/source/yojimbo_base_client.cpp index adb611d6..eab88c26 100644 --- a/source/yojimbo_base_client.cpp +++ b/source/yojimbo_base_client.cpp @@ -284,7 +284,9 @@ namespace yojimbo info.minRTT = reliable_endpoint_rtt_min( m_endpoint ); info.maxRTT = reliable_endpoint_rtt_min( m_endpoint ); info.averageRTT = reliable_endpoint_rtt_avg( m_endpoint ); - info.jitter = reliable_endpoint_jitter( m_endpoint ); + info.averageJitter = reliable_endpoint_jitter_avg_vs_min_rtt( m_endpoint ); + info.maxJitter = reliable_endpoint_jitter_max_vs_min_rtt( m_endpoint ); + info.stddevJitter = reliable_endpoint_jitter_stddev_vs_avg_rtt( m_endpoint ); info.packetLoss = reliable_endpoint_packet_loss( m_endpoint ); reliable_endpoint_bandwidth( m_endpoint, &info.sentBandwidth, &info.receivedBandwidth, &info.ackedBandwidth ); } From 36137d69786d05468c0a3849a6739ce4f62deb97 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 09:58:10 -0400 Subject: [PATCH 33/39] fix windows --- reliable/reliable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reliable/reliable.c b/reliable/reliable.c index a0bcb976..1c164db8 100644 --- a/reliable/reliable.c +++ b/reliable/reliable.c @@ -1440,7 +1440,7 @@ void reliable_endpoint_update( struct reliable_endpoint_t * endpoint, double tim } if ( count > 0 ) { - endpoint->jitter_stddev_vs_avg_rtt = pow( sum / (float)count, 0.5f ); + endpoint->jitter_stddev_vs_avg_rtt = (float) pow( sum / (float)count, 0.5f ); } else { From 176f30d60ee98805e33bb59fb43eb1a19bc42fee Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 10:23:40 -0400 Subject: [PATCH 34/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 188d4910..02c8e10e 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Alternatively, you can download the latest [release](https://github.com/mas-band ## Author -The author of this library is Glenn Fiedler. +The author of this library is [Glenn Fiedler](https://www.linkedin.com/in/glenn-fiedler-11b735302/). Other open source libraries by the same author include: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) From e9d7956b4fd181dd881a145a31711cec55ecb7ba Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 12:00:43 -0400 Subject: [PATCH 35/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02c8e10e..abc00bb5 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,10 @@ It has the following features: * Encrypted and signed packets sent over UDP * Packet fragmentation and reassembly * Bitpacker and serialization system -* Reliable-ordered messages and data blocks -* Estimates of latency, jitter and packet loss +* Unreliable-unordered messages for time sensitive data +* Reliable-ordered messages with aggressive resend until ack +* Data blocks larger that maximum packet size can be attached to reliable-ordered messages +* Estimates of latency, jitter, packet loss, bandwidth sent, received and acked per-connection yojimbo is stable and production ready. From 6e78f1e8d958469af5874d53494d94a6437d0b2a Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 12:01:41 -0400 Subject: [PATCH 36/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index abc00bb5..2613f5a9 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ It's designed around the networking requirements of competitive multiplayer games like first person shooters. +Generally speaking, if you have a custom game engine written in C++ and you want to network your game, Yojimbo is a good choice. + ![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) It has the following features: From b54d578526318c877d2949ca80a5c31edb3fca9a Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 12:01:53 -0400 Subject: [PATCH 37/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2613f5a9..85201171 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ It's designed around the networking requirements of competitive multiplayer games like first person shooters. -Generally speaking, if you have a custom game engine written in C++ and you want to network your game, Yojimbo is a good choice. +Generally speaking, if you have a custom game engine written in C++ and you want to network your game, yojimbo is a good choice. ![image](https://github.com/mas-bandwidth/yojimbo/assets/696656/098935f2-ba2b-4540-8d7f-474acc7f2cd8) From f62c577dc5b4c477369a45c564317d00ea8a6534 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Tue, 17 Sep 2024 12:02:19 -0400 Subject: [PATCH 38/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85201171..02e561df 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ It has the following features: * Bitpacker and serialization system * Unreliable-unordered messages for time sensitive data * Reliable-ordered messages with aggressive resend until ack -* Data blocks larger that maximum packet size can be attached to reliable-ordered messages +* Data blocks larger than maximum packet size can be attached to reliable-ordered messages * Estimates of latency, jitter, packet loss, bandwidth sent, received and acked per-connection yojimbo is stable and production ready. From e342ac4f648edcacec78e91e2626abc572758da3 Mon Sep 17 00:00:00 2001 From: Glenn Fiedler Date: Wed, 18 Sep 2024 13:02:06 -0400 Subject: [PATCH 39/39] Update README.md Signed-off-by: Glenn Fiedler --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02e561df..13c86f0d 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Alternatively, you can download the latest [release](https://github.com/mas-band The author of this library is [Glenn Fiedler](https://www.linkedin.com/in/glenn-fiedler-11b735302/). -Other open source libraries by the same author include: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) +Yojimbo is built on top of other open source libraries by the same author: [netcode](https://github.com/mas-bandwidth/netcode), [reliable](https://github.com/mas-bandwidth/reliable), and [serialize](https://github.com/mas-bandwidth/serialize) If you find this software useful, please consider [sponsoring it](https://github.com/sponsors/mas-bandwidth). Thanks!