From 0e566fe0682c33628aa24ce8a906fe2dbf5d05b6 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Thu, 2 Feb 2023 21:27:46 -0300 Subject: [PATCH 01/27] Adding RETRANSMIT mode and a custom message header --- README.md | 2 +- lib/LinkWireless.h | 61 +++++++++++++++++++++++++++++++++++----------- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8dfd980..853e11b 100644 --- a/README.md +++ b/README.md @@ -174,4 +174,4 @@ Name | Return type | Description ⚠️ packet loss can occur, so always send the full game state or implement retransmission on top of this! -⚠️ the adapter can transfer a maximum of twenty 32-bit words at a time, and messages are often concatenated together, so keep things way below this limit (specially when `forwarding` is on)! \ No newline at end of file +⚠️ the adapter can transfer a maximum of twenty 32-bit words at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 2b3c2f9..65b2fea 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -39,7 +39,7 @@ // retransmission on top of this! // - the adapter can transfer a maximum of twenty 32-bit words at a // time, and messages are often concatenated together, so keep things way below -// this limit (specially when forwarding is on)! +// this limit (specially when the protocol is FORWARD or RETRANSMIT)! // -------------------------------------------------------------------------- #include @@ -87,16 +87,20 @@ const u16 LINK_WIRELESS_LOGIN_PARTS[] = {0x494e, 0x494e, 0x544e, 0x544e, 0x4e45, class LinkWireless { public: enum State { NEEDS_RESET, AUTHENTICATED, SERVING, CONNECTING, CONNECTED }; + enum Protocol { BASIC, FORWARD, RETRANSMIT }; struct Message { u8 playerId = 0; std::vector data = std::vector{}; + + u32 _packetId = 0; }; explicit LinkWireless(u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, - bool forwarding = true) { + Protocol protocol = RETRANSMIT) { + // TODO: UPDATE README, TALK ABOUT `protocol` this->msgTimeout = msgTimeout; - this->forwarding = forwarding; + this->protocol = protocol; } bool isActive() { return isEnabled; } @@ -249,9 +253,12 @@ class LinkWireless { Message message; message.playerId = _author < 0 ? playerId : _author; message.data = data; + message._packetId = lastPacketId; outgoingMessages.push_back(message); + lastPacketId++; + return true; } @@ -273,12 +280,14 @@ class LinkWireless { messages = std::vector{}; for (u32 i = 0; i < words.size(); i++) { - u32 header = words[i]; + MessageHeaderSerializer messageHeaderSerializer; + + messageHeaderSerializer.headerInt = words[i]; + auto header = messageHeaderSerializer.headerStruct; - u16 subHeader = msB32(header); - u8 playerCount = msB16(subHeader); - u8 playerId = lsB16(subHeader); - u8 size = (u8)lsB32(header); + u8 playerCount = header.clientCount + 1; + u8 playerId = header.playerId; + u8 size = header.size; if (i + size >= words.size()) { reset(); @@ -312,7 +321,7 @@ class LinkWireless { return disconnect(); } - if (state == SERVING && forwarding) { + if (state == SERVING && (protocol == FORWARD || protocol == RETRANSMIT)) { for (auto& message : messages) send(message.data, message.playerId); } @@ -357,14 +366,28 @@ class LinkWireless { std::vector responses = std::vector{}; }; + struct MessageHeader { + unsigned int packetId : 21; + unsigned int size : 5; + unsigned int playerId : 4; + unsigned int clientCount : 2; + }; + + union MessageHeaderSerializer { + MessageHeader headerStruct; + u32 headerInt; + }; + u32 msgTimeout; - bool forwarding; + Protocol protocol; LinkSPI* linkSPI = new LinkSPI(); LinkGPIO* linkGPIO = new LinkGPIO(); State state = NEEDS_RESET; u8 playerId = 0; u8 playerCount = 1; std::vector outgoingMessages; + u32 lastPacketId = 0; + u32 lastConfirmedPacketId[LINK_WIRELESS_MAX_PLAYERS]; u32 timeouts[LINK_WIRELESS_MAX_PLAYERS]; bool isEnabled = false; @@ -377,9 +400,16 @@ class LinkWireless { std::vector words; for (auto& message : outgoingMessages) { - u32 header = buildU32(buildU16(playerCount, message.playerId), - message.data.size()); - words.push_back(header); + MessageHeader header; + MessageHeaderSerializer messageHeaderSerializer; + + header.clientCount = playerCount - 1; + header.playerId = message.playerId; + header.size = message.data.size(); + header.packetId = message._packetId; + messageHeaderSerializer.headerStruct = header; + + words.push_back(messageHeaderSerializer.headerInt); words.insert(words.end(), message.data.begin(), message.data.end()); } @@ -435,8 +465,11 @@ class LinkWireless { this->playerId = 0; this->playerCount = 1; this->outgoingMessages = std::vector{}; - for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) + this->lastPacketId = 0; + for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) { + lastConfirmedPacketId[i] = 0; timeouts[i] = 0; + } stop(); return start(); From 0b42ab78dbad0ae0ba9684356214cdad9cbb922c Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Thu, 2 Feb 2023 23:44:43 -0300 Subject: [PATCH 02/27] Adding retransmission protocol --- README.md | 12 +-- docs/wireless_adapter.md | 2 + examples/LinkWireless_demo/src/main.cpp | 8 +- lib/LinkWireless.h | 136 ++++++++++++++++++------ 4 files changed, 121 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 853e11b..c281061 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ The library uses message queues to send/receive data and transmits when it's pos Name | Type | Default | Description --- | --- | --- | --- -`baudRate` | **BaudRate** | `BaudRate::BAUD_RATE_1` | Sets a specific baud rate. +`baudRate` | **BaudRate** | `LinkCable::BaudRate::BAUD_RATE_1` | Sets a specific baud rate. `timeout` | **u32** | `3` | Number of *frames* without an `II_SERIAL` IRQ to reset the connection. `remoteTimeout` | **u32** | `5` | Number of *messages* with `0xFFFF` to mark a player as disconnected. `bufferSize` | **u32** | `30` | Number of *messages* that the queues will be able to store. @@ -75,7 +75,7 @@ This tool allows sending Multiboot ROMs (small 256KiB programs that fit in EWRAM Name | Return type | Description --- | --- | --- -`sendRom(rom, romSize, cancel)` | **LinkCableMultiboot::Result** | Sends the `rom`. During the handshake process, the library will continuously invoke `cancel`, and abort the transfer if it returns `true`. The `romSize` must be a number between `448` and `262144`, and a multiple of `16`. +`sendRom(rom, romSize, cancel)` | **LinkCableMultiboot::Result** | Sends the `rom`. During the handshake process, the library will continuously invoke `cancel`, and abort the transfer if it returns `true`. The `romSize` must be a number between `448` and `262144`, and a multiple of `16`. Once completed, the return value should be `LinkCableMultiboot::Result::SUCCESS`. ⚠️ for better results, turn on the GBAs **after** calling the `sendRom` method! @@ -140,6 +140,8 @@ Name | Return type | Description This is a driver for an accessory that enables wireless games up to 5 players. The inner workings of the adapter are highly unknown, but [this article](docs/wireless_adapter.md) is very helpful. I've updated the blog post to add more details about the things I learnt by the means of ~~reverse engineering~~ brute force and trial&error. +The library, by default, implements a lightweight protocol on top of the adapter's message system. This allows detecting disconnections, forwarding messages to all nodes, and retransmitting to prevent packet loss. + ![photo](https://user-images.githubusercontent.com/1631752/216233248-1f8ee26e-c8c1-418a-ad02-ad7c283dc49f.png) ## Constructor @@ -149,7 +151,7 @@ This is a driver for an accessory that enables wireless games up to 5 players. T Name | Type | Default | Description --- | --- | --- | --- `msgTimeout` | **u32** | `5` | Number of *`receive(...)` calls* without a message from other connected player to disconnect. -`forwarding` | **bool** | `true` | If `true`, the server forwards all messages to the clients. Otherwise, clients only see messages sent from the server (ignoring other peers). +`protocol` | **LinkWireless::Protocol** | `LinkWireless::Protocol::RETRANSMIT` | `LinkWireless::Protocol::BASIC`:
Clients only see messages sent from the server, ignoring other peers. Packet loss can occur, so games need to always send the full game state or implement retransmission on top of this.

`LinkWireless::Protocol::FORWARD`:
The server forwards all messages to the clients.

`LinkWireless::Protocol::RETRANSMIT`:
Same as `FORWARD`, but the library also handles retransmission for you, so there should be no packet loss. ## Methods @@ -172,6 +174,4 @@ Name | Return type | Description `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. -⚠️ packet loss can occur, so always send the full game state or implement retransmission on top of this! - -⚠️ the adapter can transfer a maximum of twenty 32-bit words at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file +⚠️ the library can transfer a maximum of fifteen 32-bit words at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file diff --git a/docs/wireless_adapter.md b/docs/wireless_adapter.md index 665b323..303ab8d 100644 --- a/docs/wireless_adapter.md +++ b/docs/wireless_adapter.md @@ -350,6 +350,8 @@ Whenever either side expects something to be sent from the other (as SPI is alwa * Responds with all the data from all adapters. No IDs are included, this is just what was sent concatenated together. * Once data has been pulled out, it clears the data buffer, so calling this again can only get new data. +⚠️ When the data is concatenated, only one **header** (see [SendData](#senddata---0x24)) is included at the first value of the response. + #### Wait - `0x27` [![Image without alt text or caption](img/0x27.png)](img/0x27.png) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index a7de1f2..4b80a11 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -180,7 +180,10 @@ void messageLoop() { // (5) Send data if ((keys & KEY_B) || (!sending && (keys & KEY_A))) { + bool doubleSend = false; sending = true; + + again: counters[linkWireless->getPlayerId()]++; if (!linkWireless->send( std::vector{counters[linkWireless->getPlayerId()]})) { @@ -188,6 +191,9 @@ void messageLoop() { hang(); return; } + + if (!doubleSend && (keys & KEY_LEFT)) + goto again; } if (sending && (!(keys & KEY_A))) sending = false; @@ -247,7 +253,7 @@ void messageLoop() { std::string output = "Players: " + std::to_string(linkWireless->getPlayerCount()) + "\n\n(press A to increment counter)\n(hold B to do it " - "continuously)\n\nPacket loss check: " + + "continuously)\n(hold LEFT for double send)\n\nPacket loss check: " + (packetLossCheck ? "ON" : "OFF") + "\n(switch with UP)\n\n"; for (u32 i = 0; i < linkWireless->getPlayerCount(); i++) { output += diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 65b2fea..c594220 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -35,14 +35,13 @@ // linkWireless->disconnect(); // -------------------------------------------------------------------------- // considerations: -// - packet loss can occur, so always send the full game state or implement -// retransmission on top of this! -// - the adapter can transfer a maximum of twenty 32-bit words at a +// - the library can transfer a maximum of fifteen 32-bit words at a // time, and messages are often concatenated together, so keep things way below // this limit (specially when the protocol is FORWARD or RETRANSMIT)! // -------------------------------------------------------------------------- #include +#include #include #include "LinkGPIO.h" #include "LinkSPI.h" @@ -53,7 +52,8 @@ #define LINK_WIRELESS_BROADCAST_SEARCH_WAIT ((160 + 68) * 60) #define LINK_WIRELESS_CMD_TIMEOUT 100 #define LINK_WIRELESS_MAX_PLAYERS 5 -#define LINK_WIRELESS_MAX_TRANSFER_LENGTH 20 +#define LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH 15 +#define LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH 20 #define LINK_WIRELESS_LOGIN_STEPS 9 #define LINK_WIRELESS_COMMAND_HEADER 0x9966 #define LINK_WIRELESS_RESPONSE_ACK 0x80 @@ -98,7 +98,6 @@ class LinkWireless { explicit LinkWireless(u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, Protocol protocol = RETRANSMIT) { - // TODO: UPDATE README, TALK ABOUT `protocol` this->msgTimeout = msgTimeout; this->protocol = protocol; } @@ -247,18 +246,16 @@ class LinkWireless { bool send(std::vector data, int _author = -1) { LINK_WIRELESS_RESET_IF_NEEDED if ((state != SERVING && state != CONNECTED) || data.size() == 0 || - data.size() > LINK_WIRELESS_MAX_TRANSFER_LENGTH) + data.size() > LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH) return false; Message message; message.playerId = _author < 0 ? playerId : _author; message.data = data; - message._packetId = lastPacketId; + message._packetId = ++lastPacketId; outgoingMessages.push_back(message); - lastPacketId++; - return true; } @@ -274,46 +271,87 @@ class LinkWireless { if (!receiveData(words)) return false; + u32 startIndex = 0; + if (protocol == RETRANSMIT && words.size() > 0) { + u32 confirmation = words[0]; + bool isServerConfirmation = confirmation & LINK_WIRELESS_DATA_REQUEST; + confirmation &= ~LINK_WIRELESS_DATA_REQUEST; + + if (isServerConfirmation) { + if (words.size() < LINK_WIRELESS_MAX_PLAYERS - 1) { + reset(); + return false; + } + + u32 min = 0xffffffff; + for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { + u32 clientConfirmation = words[1 + i]; + lastConfirmationFromClients[1 + i] = clientConfirmation; + + if (clientConfirmation < min) + min = clientConfirmation; + } + removeConfirmedMessages(min); + } else { + lastConfirmationFromServer = confirmation; + removeConfirmedMessages(confirmation); + } + + startIndex = isServerConfirmation ? 1 : LINK_WIRELESS_MAX_PLAYERS - 1; + } + for (u32 i = 0; i < playerCount; i++) if (i != playerId) timeouts[i]++; messages = std::vector{}; - for (u32 i = 0; i < words.size(); i++) { + for (u32 i = startIndex; i < words.size(); i++) { MessageHeaderSerializer messageHeaderSerializer; - messageHeaderSerializer.headerInt = words[i]; - auto header = messageHeaderSerializer.headerStruct; - u8 playerCount = header.clientCount + 1; - u8 playerId = header.playerId; + MessageHeader header = messageHeaderSerializer.headerStruct; + u8 remotePlayerCount = header.clientCount + 1; + u8 remotePlayerId = header.playerId; u8 size = header.size; + u32 packetId = header.packetId; if (i + size >= words.size()) { reset(); return false; } - if (state == CONNECTED && playerId == 0) - this->playerCount = playerCount; - timeouts[0] = 0; - timeouts[playerId] = 0; + timeouts[remotePlayerId] = 0; + + if (state == SERVING) { + if (protocol == RETRANSMIT && + packetId <= lastPacketIdFromClients[remotePlayerId]) + goto skip; + + lastPacketIdFromClients[remotePlayerId] = packetId; + } else { + if (protocol == RETRANSMIT && packetId <= lastPacketIdFromServer) + goto skip; - if (playerId == this->playerId) { + playerCount = remotePlayerCount; + lastPacketIdFromServer = packetId; + } + + if (remotePlayerId == playerId) { + skip: i += size; continue; } if (size > 0) { Message message; - message.playerId = playerId; + message.playerId = remotePlayerId; for (u32 j = 0; j < size; j++) message.data.push_back(words[i + 1 + j]); + message._packetId = packetId; messages.push_back(message); + i += size; } - - i += size; } for (u32 i = 0; i < playerCount; i++) { @@ -367,9 +405,9 @@ class LinkWireless { }; struct MessageHeader { - unsigned int packetId : 21; + unsigned int packetId : 22; unsigned int size : 5; - unsigned int playerId : 4; + unsigned int playerId : 3; unsigned int clientCount : 2; }; @@ -387,7 +425,10 @@ class LinkWireless { u8 playerCount = 1; std::vector outgoingMessages; u32 lastPacketId = 0; - u32 lastConfirmedPacketId[LINK_WIRELESS_MAX_PLAYERS]; + u32 lastPacketIdFromServer = 0; + u32 lastConfirmationFromServer = 0; + u32 lastPacketIdFromClients[LINK_WIRELESS_MAX_PLAYERS]; + u32 lastConfirmationFromClients[LINK_WIRELESS_MAX_PLAYERS]; u32 timeouts[LINK_WIRELESS_MAX_PLAYERS]; bool isEnabled = false; @@ -395,20 +436,42 @@ class LinkWireless { if (outgoingMessages.empty()) { Message emptyMessage; emptyMessage.playerId = playerId; + emptyMessage._packetId = ++lastPacketId; outgoingMessages.push_back(emptyMessage); } std::vector words; + + // TODO: EXTRACT METHODS + // TODO: ADD ERROR REASON + // TODO: ADD MAX PLAYERS + + if (protocol == RETRANSMIT) { + if (state == SERVING) { + for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { + u32 serverBit = i == 0 ? LINK_WIRELESS_DATA_REQUEST : 0; + u32 confirmation = lastPacketIdFromClients[1 + i] | serverBit; + words.push_back(confirmation); + } + } else { + words.push_back(lastPacketIdFromServer); + } + } + for (auto& message : outgoingMessages) { MessageHeader header; - MessageHeaderSerializer messageHeaderSerializer; - header.clientCount = playerCount - 1; header.playerId = message.playerId; header.size = message.data.size(); header.packetId = message._packetId; + + MessageHeaderSerializer messageHeaderSerializer; messageHeaderSerializer.headerStruct = header; + if (words.size() + 1 + header.size > + LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) + break; + words.push_back(messageHeaderSerializer.headerInt); words.insert(words.end(), message.data.begin(), message.data.end()); } @@ -416,15 +479,25 @@ class LinkWireless { if (!sendData(words)) return false; - outgoingMessages.clear(); + if (protocol != RETRANSMIT) + outgoingMessages.clear(); return true; } + void removeConfirmedMessages(u32 confirmation) { + outgoingMessages.erase( + std::remove_if(outgoingMessages.begin(), outgoingMessages.end(), + [confirmation](Message it) { + return it._packetId <= confirmation; + }), + outgoingMessages.end()); + } + bool sendData(std::vector data) { LINK_WIRELESS_RESET_IF_NEEDED if ((state != SERVING && state != CONNECTED) || data.size() == 0 || - data.size() > LINK_WIRELESS_MAX_TRANSFER_LENGTH) + data.size() > LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) return false; u32 bytes = data.size() * 4; @@ -466,8 +539,11 @@ class LinkWireless { this->playerCount = 1; this->outgoingMessages = std::vector{}; this->lastPacketId = 0; + this->lastPacketIdFromServer = 0; + this->lastConfirmationFromServer = 0; for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) { - lastConfirmedPacketId[i] = 0; + lastPacketIdFromClients[i] = 0; + lastConfirmationFromClients[i] = 0; timeouts[i] = 0; } From 561d8417f05facb17ac3b4edf0655699a155bc16 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 00:18:06 -0300 Subject: [PATCH 03/27] Adding error causes --- README.md | 1 + examples/LinkWireless_demo/src/main.cpp | 67 +++++++---------- lib/LinkWireless.h | 97 +++++++++++++++++++++---- 3 files changed, 108 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index c281061..e4f9992 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Name | Return type | Description `receive(messages)` | **bool** | Sends the pending data and fills the `messages` vector with incoming messages, checking for timeouts and forwarding if needed. This call doesn't block the hardware waiting for messages, it returns if there are no incoming messages. `disconnect()` | **bool** | Disconnects and resets the adapter. `getState()` | **LinkWireless::State** | Returns the current state (one of `LinkWireless::State::NEEDS_RESET`, `LinkWireless::State::AUTHENTICATED`, `LinkWireless::State::SERVING`, `LinkWireless::State::CONNECTING`, or `LinkWireless::State::CONNECTED`). +`getLastError()` | **LinkWireless::Error** | If one of the other methods return `false`, you can inspect this to know the cause. After this call, the last error is cleared. `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 4b80a11..c081a91 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -4,6 +4,13 @@ // (0) Include the header #include "../../_lib/LinkWireless.h" +#define CHECK_ERRORS(MESSAGE) \ + if ((lastError = linkWireless->getLastError())) { \ + log(std::string(MESSAGE) + " (" + std::to_string(lastError) + ")"); \ + hang(); \ + return; \ + } + void activate(); void serve(); void connect(); @@ -12,6 +19,8 @@ void log(std::string text); void waitFor(u16 key); void hang(); +LinkWireless::Error lastError; + // (1) Create a LinkWireless instance LinkWireless* linkWireless = new LinkWireless(); @@ -84,11 +93,8 @@ void serve() { log("Serving..."); // (3) Start a server - if (!linkWireless->serve()) { - log("Serve failed :("); - hang(); - return; - } + linkWireless->serve(); + CHECK_ERRORS("Serve failed :(") log("Listening..."); @@ -101,11 +107,8 @@ void serve() { return; } - if (!linkWireless->acceptConnections()) { - log("Accept failed :("); - hang(); - return; - } + linkWireless->acceptConnections(); + CHECK_ERRORS("Accept failed :(") } while (linkWireless->getPlayerCount() <= 1); log("Connection accepted!"); @@ -118,11 +121,8 @@ void connect() { // (4) Connect to a server std::vector serverIds; - if (!linkWireless->getServerIds(serverIds)) { - log("Search failed :("); - hang(); - return; - } + linkWireless->getServerIds(serverIds); + CHECK_ERRORS("Search failed :(") if (serverIds.size() == 0) { log("Nothing found :("); @@ -137,11 +137,8 @@ void connect() { waitFor(KEY_START); - if (!linkWireless->connect(serverIds[0])) { - log("Connect failed :("); - hang(); - return; - } + linkWireless->connect(serverIds[0]); + CHECK_ERRORS("Connect failed :(") while (linkWireless->getState() == LinkWireless::State::CONNECTING) { u16 keys = ~REG_KEYS & KEY_ANY; @@ -152,11 +149,8 @@ void connect() { return; } - if (!linkWireless->keepConnecting()) { - log("Finish connection failed :("); - hang(); - return; - } + linkWireless->keepConnecting(); + CHECK_ERRORS("Finish conn failed :(") } log("Connected! " + std::to_string(linkWireless->getPlayerId())); @@ -185,12 +179,9 @@ void messageLoop() { again: counters[linkWireless->getPlayerId()]++; - if (!linkWireless->send( - std::vector{counters[linkWireless->getPlayerId()]})) { - log("Send failed :("); - hang(); - return; - } + linkWireless->send( + std::vector{counters[linkWireless->getPlayerId()]}); + CHECK_ERRORS("Send failed :(") if (!doubleSend && (keys & KEY_LEFT)) goto again; @@ -200,11 +191,8 @@ void messageLoop() { // (6) Receive data std::vector messages; - if (!linkWireless->receive(messages)) { - log("Receive failed :("); - hang(); - return; - } + linkWireless->receive(messages); + CHECK_ERRORS("Receive failed :(") if (messages.size() > 0) { for (auto& message : messages) { u32 expected = counters[message.playerId] + 1; @@ -225,11 +213,8 @@ void messageLoop() { // Accept new connections if (linkWireless->getState() == LinkWireless::State::SERVING) { - if (!linkWireless->acceptConnections()) { - log("Accept failed :("); - hang(); - return; - } + linkWireless->acceptConnections(); + CHECK_ERRORS("Accept failed :(") } // (7) Disconnect diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index c594220..c9b6b23 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -88,6 +88,18 @@ class LinkWireless { public: enum State { NEEDS_RESET, AUTHENTICATED, SERVING, CONNECTING, CONNECTED }; enum Protocol { BASIC, FORWARD, RETRANSMIT }; + enum Error { + NONE, + WRONG_STATE, + COMMAND_FAILED, + WEIRD_PLAYER_ID, + INVALID_SEND_SIZE, + SEND_DATA_FAILED, + RECEIVE_DATA_FAILED, + BAD_CONFIRMATION, + BAD_MESSAGE, + TIMEOUT + }; struct Message { u8 playerId = 0; @@ -105,6 +117,7 @@ class LinkWireless { bool isActive() { return isEnabled; } bool activate() { + lastError = NONE; bool success = reset(); isEnabled = true; @@ -112,14 +125,17 @@ class LinkWireless { } void deactivate() { + lastError = NONE; isEnabled = false; stop(); } bool serve() { LINK_WIRELESS_RESET_IF_NEEDED - if (state != AUTHENTICATED) + if (state != AUTHENTICATED) { + lastError = WRONG_STATE; return false; + } auto broadcast = std::vector{1, 2, 3, 4, 5, 6}; bool success = @@ -128,6 +144,7 @@ class LinkWireless { if (!success) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -138,13 +155,16 @@ class LinkWireless { bool acceptConnections() { LINK_WIRELESS_RESET_IF_NEEDED - if (state != SERVING) + if (state != SERVING) { + lastError = WRONG_STATE; return false; + } auto result = sendCommand(LINK_WIRELESS_COMMAND_ACCEPT_CONNECTIONS); if (!result.success) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -155,14 +175,17 @@ class LinkWireless { bool getServerIds(std::vector& serverIds) { LINK_WIRELESS_RESET_IF_NEEDED - if (state != AUTHENTICATED) + if (state != AUTHENTICATED) { + lastError = WRONG_STATE; return false; + } bool success1 = sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_START).success; if (!success1) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -175,6 +198,7 @@ class LinkWireless { if (!success2) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -191,8 +215,10 @@ class LinkWireless { bool connect(u16 serverId) { LINK_WIRELESS_RESET_IF_NEEDED - if (state != AUTHENTICATED) + if (state != AUTHENTICATED) { + lastError = WRONG_STATE; return false; + } bool success = sendCommand(LINK_WIRELESS_COMMAND_CONNECT, std::vector{serverId}) @@ -200,6 +226,7 @@ class LinkWireless { if (!success) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -210,12 +237,15 @@ class LinkWireless { bool keepConnecting() { LINK_WIRELESS_RESET_IF_NEEDED - if (state != CONNECTING) + if (state != CONNECTING) { + lastError = WRONG_STATE; return false; + } auto result1 = sendCommand(LINK_WIRELESS_COMMAND_IS_FINISHED_CONNECT); if (!result1.success || result1.responses.size() == 0) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -227,6 +257,7 @@ class LinkWireless { if (assignedPlayerId >= LINK_WIRELESS_MAX_PLAYERS) { reset(); + lastError = WEIRD_PLAYER_ID; return false; } @@ -234,6 +265,7 @@ class LinkWireless { if (!result2.success || result2.responses.size() == 0 || (u16)result2.responses[0] != assignedClientId) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -245,9 +277,15 @@ class LinkWireless { bool send(std::vector data, int _author = -1) { LINK_WIRELESS_RESET_IF_NEEDED - if ((state != SERVING && state != CONNECTED) || data.size() == 0 || - data.size() > LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH) + if (state != SERVING && state != CONNECTED) { + lastError = WRONG_STATE; + return false; + } + if (data.size() == 0 || + data.size() > LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH) { + lastError = INVALID_SEND_SIZE; return false; + } Message message; message.playerId = _author < 0 ? playerId : _author; @@ -261,15 +299,21 @@ class LinkWireless { bool receive(std::vector& messages) { LINK_WIRELESS_RESET_IF_NEEDED - if (state != SERVING && state != CONNECTED) + if (state != SERVING && state != CONNECTED) { + lastError = WRONG_STATE; return false; + } - if (!sendPendingMessages()) + if (!sendPendingMessages()) { + lastError = SEND_DATA_FAILED; return false; + } std::vector words; - if (!receiveData(words)) + if (!receiveData(words)) { + lastError = RECEIVE_DATA_FAILED; return false; + } u32 startIndex = 0; if (protocol == RETRANSMIT && words.size() > 0) { @@ -280,6 +324,7 @@ class LinkWireless { if (isServerConfirmation) { if (words.size() < LINK_WIRELESS_MAX_PLAYERS - 1) { reset(); + lastError = BAD_CONFIRMATION; return false; } @@ -317,6 +362,7 @@ class LinkWireless { if (i + size >= words.size()) { reset(); + lastError = BAD_MESSAGE; return false; } @@ -355,8 +401,10 @@ class LinkWireless { } for (u32 i = 0; i < playerCount; i++) { - if ((i == 0 || state == SERVING) && timeouts[i] > msgTimeout) + if ((i == 0 || state == SERVING) && timeouts[i] > msgTimeout) { + lastError = TIMEOUT; return disconnect(); + } } if (state == SERVING && (protocol == FORWARD || protocol == RETRANSMIT)) { @@ -369,8 +417,10 @@ class LinkWireless { bool disconnect() { LINK_WIRELESS_RESET_IF_NEEDED - if (state != SERVING && state != CONNECTED) + if (state != SERVING && state != CONNECTED) { + lastError = WRONG_STATE; return false; + } bool success = sendCommand(LINK_WIRELESS_COMMAND_DISCONNECT).success; @@ -385,6 +435,11 @@ class LinkWireless { } State getState() { return state; } + Error getLastError() { + Error error = lastError; + lastError = NONE; + return error; + } u8 getPlayerId() { return playerId; } u8 getPlayerCount() { return playerCount; } @@ -430,6 +485,7 @@ class LinkWireless { u32 lastPacketIdFromClients[LINK_WIRELESS_MAX_PLAYERS]; u32 lastConfirmationFromClients[LINK_WIRELESS_MAX_PLAYERS]; u32 timeouts[LINK_WIRELESS_MAX_PLAYERS]; + Error lastError = NONE; bool isEnabled = false; bool sendPendingMessages() { @@ -443,7 +499,6 @@ class LinkWireless { std::vector words; // TODO: EXTRACT METHODS - // TODO: ADD ERROR REASON // TODO: ADD MAX PLAYERS if (protocol == RETRANSMIT) { @@ -496,9 +551,15 @@ class LinkWireless { bool sendData(std::vector data) { LINK_WIRELESS_RESET_IF_NEEDED - if ((state != SERVING && state != CONNECTED) || data.size() == 0 || - data.size() > LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) + if (state != SERVING && state != CONNECTED) { + lastError = WRONG_STATE; return false; + } + if (data.size() == 0 || + data.size() > LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) { + lastError = INVALID_SEND_SIZE; + return false; + } u32 bytes = data.size() * 4; u32 header = playerId == 0 ? bytes : (1 << (3 + playerId * 5)) * bytes; @@ -508,6 +569,7 @@ class LinkWireless { if (!success) { reset(); + lastError = COMMAND_FAILED; return false; } @@ -516,14 +578,17 @@ class LinkWireless { bool receiveData(std::vector& data) { LINK_WIRELESS_RESET_IF_NEEDED - if (state != SERVING && state != CONNECTED) + if (state != SERVING && state != CONNECTED) { + lastError = WRONG_STATE; return false; + } auto result = sendCommand(LINK_WIRELESS_COMMAND_RECEIVE_DATA); data = result.responses; if (!result.success) { reset(); + lastError = COMMAND_FAILED; return false; } From d235b447a279c7f700ba9fb0db0f6ce201eae60b Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 00:43:27 -0300 Subject: [PATCH 04/27] FIX: Client confirmation detection --- lib/LinkWireless.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index c9b6b23..0c7332c 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -317,9 +317,7 @@ class LinkWireless { u32 startIndex = 0; if (protocol == RETRANSMIT && words.size() > 0) { - u32 confirmation = words[0]; - bool isServerConfirmation = confirmation & LINK_WIRELESS_DATA_REQUEST; - confirmation &= ~LINK_WIRELESS_DATA_REQUEST; + bool isServerConfirmation = words[0] & LINK_WIRELESS_DATA_REQUEST; if (isServerConfirmation) { if (words.size() < LINK_WIRELESS_MAX_PLAYERS - 1) { @@ -330,7 +328,8 @@ class LinkWireless { u32 min = 0xffffffff; for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { - u32 clientConfirmation = words[1 + i]; + u32 mask = i == 0 ? ~LINK_WIRELESS_DATA_REQUEST : ~0; + u32 clientConfirmation = words[i] & mask; lastConfirmationFromClients[1 + i] = clientConfirmation; if (clientConfirmation < min) @@ -338,8 +337,8 @@ class LinkWireless { } removeConfirmedMessages(min); } else { - lastConfirmationFromServer = confirmation; - removeConfirmedMessages(confirmation); + lastConfirmationFromServer = words[0]; + removeConfirmedMessages(lastConfirmationFromServer); } startIndex = isServerConfirmation ? 1 : LINK_WIRELESS_MAX_PLAYERS - 1; @@ -500,6 +499,7 @@ class LinkWireless { // TODO: EXTRACT METHODS // TODO: ADD MAX PLAYERS + // TODO: `outgoingMessages` limit if (protocol == RETRANSMIT) { if (state == SERVING) { From 372d6dd2db967f5915e319657c40769efa24d6a5 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 01:28:12 -0300 Subject: [PATCH 05/27] Rewriting confirmations as special messages --- README.md | 2 +- lib/LinkWireless.h | 138 +++++++++++++++++++++++++++------------------ 2 files changed, 84 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index e4f9992..d13a16d 100644 --- a/README.md +++ b/README.md @@ -175,4 +175,4 @@ Name | Return type | Description `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. -⚠️ the library can transfer a maximum of fifteen 32-bit words at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file +⚠️ the library can transfer a maximum of 14 words of 32 bits at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 0c7332c..a0f4099 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -35,7 +35,7 @@ // linkWireless->disconnect(); // -------------------------------------------------------------------------- // considerations: -// - the library can transfer a maximum of fifteen 32-bit words at a +// - the library can transfer a maximum of 14 words of 32 bits at a // time, and messages are often concatenated together, so keep things way below // this limit (specially when the protocol is FORWARD or RETRANSMIT)! // -------------------------------------------------------------------------- @@ -49,10 +49,11 @@ #define LINK_WIRELESS_DEFAULT_MSG_TIMEOUT 5 #define LINK_WIRELESS_PING_WAIT 50 #define LINK_WIRELESS_TRANSFER_WAIT 15 +#define LINK_WIRELESS_MSG_CONFIRMATION 0 #define LINK_WIRELESS_BROADCAST_SEARCH_WAIT ((160 + 68) * 60) #define LINK_WIRELESS_CMD_TIMEOUT 100 #define LINK_WIRELESS_MAX_PLAYERS 5 -#define LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH 15 +#define LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH 14 #define LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH 20 #define LINK_WIRELESS_LOGIN_STEPS 9 #define LINK_WIRELESS_COMMAND_HEADER 0x9966 @@ -315,45 +316,16 @@ class LinkWireless { return false; } - u32 startIndex = 0; - if (protocol == RETRANSMIT && words.size() > 0) { - bool isServerConfirmation = words[0] & LINK_WIRELESS_DATA_REQUEST; - - if (isServerConfirmation) { - if (words.size() < LINK_WIRELESS_MAX_PLAYERS - 1) { - reset(); - lastError = BAD_CONFIRMATION; - return false; - } - - u32 min = 0xffffffff; - for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { - u32 mask = i == 0 ? ~LINK_WIRELESS_DATA_REQUEST : ~0; - u32 clientConfirmation = words[i] & mask; - lastConfirmationFromClients[1 + i] = clientConfirmation; - - if (clientConfirmation < min) - min = clientConfirmation; - } - removeConfirmedMessages(min); - } else { - lastConfirmationFromServer = words[0]; - removeConfirmedMessages(lastConfirmationFromServer); - } - - startIndex = isServerConfirmation ? 1 : LINK_WIRELESS_MAX_PLAYERS - 1; - } - for (u32 i = 0; i < playerCount; i++) if (i != playerId) timeouts[i]++; messages = std::vector{}; - for (u32 i = startIndex; i < words.size(); i++) { - MessageHeaderSerializer messageHeaderSerializer; - messageHeaderSerializer.headerInt = words[i]; + for (u32 i = 0; i < words.size(); i++) { + MessageHeaderSerializer serializer; + serializer.asInt = words[i]; - MessageHeader header = messageHeaderSerializer.headerStruct; + MessageHeader header = serializer.asStruct; u8 remotePlayerCount = header.clientCount + 1; u8 remotePlayerId = header.playerId; u8 size = header.size; @@ -370,12 +342,15 @@ class LinkWireless { if (state == SERVING) { if (protocol == RETRANSMIT && + packetId != LINK_WIRELESS_MSG_CONFIRMATION && packetId <= lastPacketIdFromClients[remotePlayerId]) goto skip; lastPacketIdFromClients[remotePlayerId] = packetId; } else { - if (protocol == RETRANSMIT && packetId <= lastPacketIdFromServer) + if (protocol == RETRANSMIT && + packetId != LINK_WIRELESS_MSG_CONFIRMATION && + packetId <= lastPacketIdFromServer) goto skip; playerCount = remotePlayerCount; @@ -394,7 +369,18 @@ class LinkWireless { for (u32 j = 0; j < size; j++) message.data.push_back(words[i + 1 + j]); message._packetId = packetId; - messages.push_back(message); + + if (protocol == RETRANSMIT && + packetId == LINK_WIRELESS_MSG_CONFIRMATION) { + if (!handleConfirmation(message)) { + reset(); + lastError = BAD_CONFIRMATION; + return false; + } + } else { + messages.push_back(message); + } + i += size; } } @@ -466,8 +452,8 @@ class LinkWireless { }; union MessageHeaderSerializer { - MessageHeader headerStruct; - u32 headerInt; + MessageHeader asStruct; + u32 asInt; }; u32 msgTimeout; @@ -503,31 +489,24 @@ class LinkWireless { if (protocol == RETRANSMIT) { if (state == SERVING) { - for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { - u32 serverBit = i == 0 ? LINK_WIRELESS_DATA_REQUEST : 0; - u32 confirmation = lastPacketIdFromClients[1 + i] | serverBit; - words.push_back(confirmation); - } + words.push_back(buildConfirmationHeader(0)); + for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) + words.push_back(lastPacketIdFromClients[1 + i]); } else { + words.push_back(buildConfirmationHeader(playerId)); words.push_back(lastPacketIdFromServer); } } for (auto& message : outgoingMessages) { - MessageHeader header; - header.clientCount = playerCount - 1; - header.playerId = message.playerId; - header.size = message.data.size(); - header.packetId = message._packetId; + u8 size = message.data.size(); + u32 header = + buildMessageHeader(message.playerId, size, message._packetId); - MessageHeaderSerializer messageHeaderSerializer; - messageHeaderSerializer.headerStruct = header; - - if (words.size() + 1 + header.size > - LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) + if (words.size() + 1 + size > LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) break; - words.push_back(messageHeaderSerializer.headerInt); + words.push_back(header); words.insert(words.end(), message.data.begin(), message.data.end()); } @@ -540,6 +519,55 @@ class LinkWireless { return true; } + bool handleConfirmation(Message confirmation) { + if (confirmation.data.size() == 0) + return false; + + bool isServerConfirmation = confirmation.playerId == 0; + + if (isServerConfirmation) { + if (state != CONNECTED || + confirmation.data.size() != LINK_WIRELESS_MAX_PLAYERS - 1) + return false; + + lastConfirmationFromServer = confirmation.data[playerId - 1]; + removeConfirmedMessages(lastConfirmationFromServer); + } else { + if (state != SERVING || confirmation.data.size() != 1) + return false; + + u32 confirmationData = confirmation.data[0]; + lastConfirmationFromClients[confirmation.playerId] = confirmationData; + + u32 min = confirmationData; + for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { + u32 confirmationData = lastConfirmationFromClients[1 + i]; + if (confirmationData < min) + min = confirmationData; + } + removeConfirmedMessages(min); + } + + return true; + } + + u32 buildConfirmationHeader(u8 playerId) { + return buildMessageHeader( + playerId, playerId == 0 ? LINK_WIRELESS_MAX_PLAYERS - 1 : 1, 0); + } + + u32 buildMessageHeader(u8 playerId, u8 size, u32 packetId) { + MessageHeader header; + header.clientCount = playerCount - 1; + header.playerId = playerId; + header.size = size; + header.packetId = packetId; + + MessageHeaderSerializer serializer; + serializer.asStruct = header; + return serializer.asInt; + } + void removeConfirmedMessages(u32 confirmation) { outgoingMessages.erase( std::remove_if(outgoingMessages.begin(), outgoingMessages.end(), From a1bae2cada3f0d8831dd83c21847f86f87de18e3 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 01:36:20 -0300 Subject: [PATCH 06/27] Extracting methods --- lib/LinkWireless.h | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index a0f4099..2301000 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -483,20 +483,11 @@ class LinkWireless { std::vector words; - // TODO: EXTRACT METHODS // TODO: ADD MAX PLAYERS // TODO: `outgoingMessages` limit - if (protocol == RETRANSMIT) { - if (state == SERVING) { - words.push_back(buildConfirmationHeader(0)); - for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) - words.push_back(lastPacketIdFromClients[1 + i]); - } else { - words.push_back(buildConfirmationHeader(playerId)); - words.push_back(lastPacketIdFromServer); - } - } + if (protocol == RETRANSMIT) + addConfirmations(words); for (auto& message : outgoingMessages) { u8 size = message.data.size(); @@ -519,6 +510,17 @@ class LinkWireless { return true; } + void addConfirmations(std::vector& words) { + if (state == SERVING) { + words.push_back(buildConfirmationHeader(0)); + for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) + words.push_back(lastPacketIdFromClients[1 + i]); + } else { + words.push_back(buildConfirmationHeader(playerId)); + words.push_back(lastPacketIdFromServer); + } + } + bool handleConfirmation(Message confirmation) { if (confirmation.data.size() == 0) return false; @@ -551,6 +553,15 @@ class LinkWireless { return true; } + void removeConfirmedMessages(u32 confirmation) { + outgoingMessages.erase( + std::remove_if(outgoingMessages.begin(), outgoingMessages.end(), + [confirmation](Message it) { + return it._packetId <= confirmation; + }), + outgoingMessages.end()); + } + u32 buildConfirmationHeader(u8 playerId) { return buildMessageHeader( playerId, playerId == 0 ? LINK_WIRELESS_MAX_PLAYERS - 1 : 1, 0); @@ -568,15 +579,6 @@ class LinkWireless { return serializer.asInt; } - void removeConfirmedMessages(u32 confirmation) { - outgoingMessages.erase( - std::remove_if(outgoingMessages.begin(), outgoingMessages.end(), - [confirmation](Message it) { - return it._packetId <= confirmation; - }), - outgoingMessages.end()); - } - bool sendData(std::vector data) { LINK_WIRELESS_RESET_IF_NEEDED if (state != SERVING && state != CONNECTED) { From 0214bf9e73837bbe748da937db3f46f0b065e5b3 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 01:41:36 -0300 Subject: [PATCH 07/27] Adding maxPlayers option --- README.md | 3 ++- lib/LinkWireless.h | 22 ++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d13a16d..5b43715 100644 --- a/README.md +++ b/README.md @@ -150,8 +150,9 @@ The library, by default, implements a lightweight protocol on top of the adapter Name | Type | Default | Description --- | --- | --- | --- -`msgTimeout` | **u32** | `5` | Number of *`receive(...)` calls* without a message from other connected player to disconnect. `protocol` | **LinkWireless::Protocol** | `LinkWireless::Protocol::RETRANSMIT` | `LinkWireless::Protocol::BASIC`:
Clients only see messages sent from the server, ignoring other peers. Packet loss can occur, so games need to always send the full game state or implement retransmission on top of this.

`LinkWireless::Protocol::FORWARD`:
The server forwards all messages to the clients.

`LinkWireless::Protocol::RETRANSMIT`:
Same as `FORWARD`, but the library also handles retransmission for you, so there should be no packet loss. +`maxPlayers` | **u8** | `5` | Maximum number of allowed players. +`msgTimeout` | **u32** | `5` | Number of *`receive(...)` calls* without a message from other connected player to disconnect. ## Methods diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 2301000..285fc03 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -94,6 +94,7 @@ class LinkWireless { WRONG_STATE, COMMAND_FAILED, WEIRD_PLAYER_ID, + MAX_PLAYERS_LIMIT_REACHED, INVALID_SEND_SIZE, SEND_DATA_FAILED, RECEIVE_DATA_FAILED, @@ -109,10 +110,12 @@ class LinkWireless { u32 _packetId = 0; }; - explicit LinkWireless(u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, - Protocol protocol = RETRANSMIT) { - this->msgTimeout = msgTimeout; + explicit LinkWireless(Protocol protocol = RETRANSMIT, + u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS, + u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT) { this->protocol = protocol; + this->maxPlayers = maxPlayers; + this->msgTimeout = msgTimeout; } bool isActive() { return isEnabled; } @@ -171,6 +174,12 @@ class LinkWireless { playerCount = 1 + result.responses.size(); + if (playerCount > maxPlayers) { + disconnect(); + lastError = MAX_PLAYERS_LIMIT_REACHED; + return false; + } + return true; } @@ -388,7 +397,8 @@ class LinkWireless { for (u32 i = 0; i < playerCount; i++) { if ((i == 0 || state == SERVING) && timeouts[i] > msgTimeout) { lastError = TIMEOUT; - return disconnect(); + disconnect(); + return false; } } @@ -456,8 +466,9 @@ class LinkWireless { u32 asInt; }; - u32 msgTimeout; Protocol protocol; + u8 maxPlayers; + u32 msgTimeout; LinkSPI* linkSPI = new LinkSPI(); LinkGPIO* linkGPIO = new LinkGPIO(); State state = NEEDS_RESET; @@ -483,7 +494,6 @@ class LinkWireless { std::vector words; - // TODO: ADD MAX PLAYERS // TODO: `outgoingMessages` limit if (protocol == RETRANSMIT) From f967e10d78667a1c2df11ff88f450b6c9f0dd792 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 01:49:11 -0300 Subject: [PATCH 08/27] Adding bufferSize --- README.md | 3 ++- lib/LinkWireless.h | 12 +++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b43715..e18f62c 100644 --- a/README.md +++ b/README.md @@ -153,10 +153,11 @@ Name | Type | Default | Description `protocol` | **LinkWireless::Protocol** | `LinkWireless::Protocol::RETRANSMIT` | `LinkWireless::Protocol::BASIC`:
Clients only see messages sent from the server, ignoring other peers. Packet loss can occur, so games need to always send the full game state or implement retransmission on top of this.

`LinkWireless::Protocol::FORWARD`:
The server forwards all messages to the clients.

`LinkWireless::Protocol::RETRANSMIT`:
Same as `FORWARD`, but the library also handles retransmission for you, so there should be no packet loss. `maxPlayers` | **u8** | `5` | Maximum number of allowed players. `msgTimeout` | **u32** | `5` | Number of *`receive(...)` calls* without a message from other connected player to disconnect. +`bufferSize` | **u32** | `30` | Number of *messages* that the queues will be able to store. ## Methods -✔️ Most of the methods return a boolean, indicating if the action was successful. If not, the connection with the adapter is reset and the game needs to start again. All actions are synchronic. +✔️ Most of the methods return a boolean, indicating if the action was successful. If not, you can call `getLastError()` to know the reason. Usually, unless it's a trivial error (like buffers being full), the connection with the adapter is reset and the game needs to start again. You can check the connection state with `getState()`. All actions are synchronic. Name | Return type | Description --- | --- | --- diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 285fc03..ad163c5 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -47,6 +47,7 @@ #include "LinkSPI.h" #define LINK_WIRELESS_DEFAULT_MSG_TIMEOUT 5 +#define LINK_WIRELESS_DEFAULT_BUFFER_SIZE 30 #define LINK_WIRELESS_PING_WAIT 50 #define LINK_WIRELESS_TRANSFER_WAIT 15 #define LINK_WIRELESS_MSG_CONFIRMATION 0 @@ -96,6 +97,7 @@ class LinkWireless { WEIRD_PLAYER_ID, MAX_PLAYERS_LIMIT_REACHED, INVALID_SEND_SIZE, + BUFFER_IS_FULL, SEND_DATA_FAILED, RECEIVE_DATA_FAILED, BAD_CONFIRMATION, @@ -112,10 +114,12 @@ class LinkWireless { explicit LinkWireless(Protocol protocol = RETRANSMIT, u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS, - u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT) { + u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, + u32 bufferSize = LINK_WIRELESS_DEFAULT_BUFFER_SIZE) { this->protocol = protocol; this->maxPlayers = maxPlayers; this->msgTimeout = msgTimeout; + this->bufferSize = bufferSize; } bool isActive() { return isEnabled; } @@ -297,6 +301,11 @@ class LinkWireless { return false; } + if (outgoingMessages.size() >= bufferSize) { + lastError = BUFFER_IS_FULL; + return false; + } + Message message; message.playerId = _author < 0 ? playerId : _author; message.data = data; @@ -469,6 +478,7 @@ class LinkWireless { Protocol protocol; u8 maxPlayers; u32 msgTimeout; + u32 bufferSize; LinkSPI* linkSPI = new LinkSPI(); LinkGPIO* linkGPIO = new LinkGPIO(); State state = NEEDS_RESET; From 10fe6020d977d248c78c2da8072c72577c593aa8 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 01:50:31 -0300 Subject: [PATCH 09/27] Fix server confirmations including 0. Stop adding dummy messages in retransmit mode --- lib/LinkWireless.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index ad163c5..c1c7c29 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -364,7 +364,8 @@ class LinkWireless { packetId <= lastPacketIdFromClients[remotePlayerId]) goto skip; - lastPacketIdFromClients[remotePlayerId] = packetId; + if (packetId != LINK_WIRELESS_MSG_CONFIRMATION) + lastPacketIdFromClients[remotePlayerId] = packetId; } else { if (protocol == RETRANSMIT && packetId != LINK_WIRELESS_MSG_CONFIRMATION && @@ -372,7 +373,9 @@ class LinkWireless { goto skip; playerCount = remotePlayerCount; - lastPacketIdFromServer = packetId; + + if (packetId != LINK_WIRELESS_MSG_CONFIRMATION) + lastPacketIdFromServer = packetId; } if (remotePlayerId == playerId) { @@ -495,7 +498,7 @@ class LinkWireless { bool isEnabled = false; bool sendPendingMessages() { - if (outgoingMessages.empty()) { + if (outgoingMessages.empty() && protocol != RETRANSMIT) { Message emptyMessage; emptyMessage.playerId = playerId; emptyMessage._packetId = ++lastPacketId; @@ -504,8 +507,6 @@ class LinkWireless { std::vector words; - // TODO: `outgoingMessages` limit - if (protocol == RETRANSMIT) addConfirmations(words); @@ -561,10 +562,10 @@ class LinkWireless { u32 confirmationData = confirmation.data[0]; lastConfirmationFromClients[confirmation.playerId] = confirmationData; - u32 min = confirmationData; + u32 min = 0xffffffff; for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) { u32 confirmationData = lastConfirmationFromClients[1 + i]; - if (confirmationData < min) + if (confirmationData > 0 && confirmationData < min) min = confirmationData; } removeConfirmedMessages(min); From d7ffa9792e652b0cfb68dd88fcbd9cc67108c98a Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 05:04:21 -0300 Subject: [PATCH 10/27] Adding state to errors, and reset --- examples/LinkWireless_demo/src/main.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index c081a91..c7ee247 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -4,11 +4,13 @@ // (0) Include the header #include "../../_lib/LinkWireless.h" -#define CHECK_ERRORS(MESSAGE) \ - if ((lastError = linkWireless->getLastError())) { \ - log(std::string(MESSAGE) + " (" + std::to_string(lastError) + ")"); \ - hang(); \ - return; \ +#define CHECK_ERRORS(MESSAGE) \ + if ((lastError = linkWireless->getLastError())) { \ + log(std::string(MESSAGE) + " (" + std::to_string(lastError) + ") [" + \ + std::to_string(linkWireless->getState()) + "]"); \ + hang(); \ + linkWireless->activate(); \ + return; \ } void activate(); From 92384f9e6ab5b6412ab9dc3ab7b0a67e01032803 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 05:20:42 -0300 Subject: [PATCH 11/27] Improving logs --- README.md | 1 + examples/LinkWireless_demo/src/main.cpp | 8 +++++--- lib/LinkWireless.h | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e18f62c..12078c0 100644 --- a/README.md +++ b/README.md @@ -176,5 +176,6 @@ Name | Return type | Description `getLastError()` | **LinkWireless::Error** | If one of the other methods return `false`, you can inspect this to know the cause. After this call, the last error is cleared. `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. +`getPendingCount()` | **u32** | Returns the number of outgoing messages, ready to be sent. ⚠️ the library can transfer a maximum of 14 words of 32 bits at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index c7ee247..f594265 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -152,7 +152,7 @@ void connect() { } linkWireless->keepConnecting(); - CHECK_ERRORS("Finish conn failed :(") + CHECK_ERRORS("Finish failed :(") } log("Connected! " + std::to_string(linkWireless->getPlayerId())); @@ -222,7 +222,7 @@ void messageLoop() { // (7) Disconnect if ((keys & KEY_SELECT)) { if (!linkWireless->disconnect()) { - log("Disconnect failed :("); + log("Disconn failed :("); hang(); return; } @@ -238,7 +238,8 @@ void messageLoop() { switching = false; std::string output = - "Players: " + std::to_string(linkWireless->getPlayerCount()) + + "Player #" + std::to_string(linkWireless->getPlayerId()) + " (" + + std::to_string(linkWireless->getPlayerCount()) + " total)" + "\n\n(press A to increment counter)\n(hold B to do it " "continuously)\n(hold LEFT for double send)\n\nPacket loss check: " + (packetLossCheck ? "ON" : "OFF") + "\n(switch with UP)\n\n"; @@ -246,6 +247,7 @@ void messageLoop() { output += "p" + std::to_string(i) + ": " + std::to_string(counters[i]) + "\n"; } + output += "\n_buffer: " + std::to_string(linkWireless->getPendingCount()); // Print VBlankIntrWait(); diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index c1c7c29..8ce3f82 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -449,6 +449,7 @@ class LinkWireless { } u8 getPlayerId() { return playerId; } u8 getPlayerCount() { return playerCount; } + u32 getPendingCount() { return outgoingMessages.size(); } ~LinkWireless() { delete linkSPI; From e4eaf0c0e191173281ec12f21333b45d500d5460 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 05:49:58 -0300 Subject: [PATCH 12/27] Adding canSend() method --- README.md | 3 ++- lib/LinkWireless.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 12078c0..b93a551 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ Name | Return type | Description `getLastError()` | **LinkWireless::Error** | If one of the other methods return `false`, you can inspect this to know the cause. After this call, the last error is cleared. `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. -`getPendingCount()` | **u32** | Returns the number of outgoing messages, ready to be sent. +`canSend()` | **bool** | Returns `false` only if the next `send(...)` call would fail due to full buffers. +`getPendingCount()` | **u32** | Returns the number of outgoing messages ready to be sent. ⚠️ the library can transfer a maximum of 14 words of 32 bits at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 8ce3f82..64b90b9 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -449,6 +449,7 @@ class LinkWireless { } u8 getPlayerId() { return playerId; } u8 getPlayerCount() { return playerCount; } + bool canSend() { return outgoingMessages.size() < bufferSize; } u32 getPendingCount() { return outgoingMessages.size(); } ~LinkWireless() { From 6eb235353af55e58107c9d086ea9578d829f9838 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 06:02:09 -0300 Subject: [PATCH 13/27] Fixing double send demo --- examples/LinkWireless_demo/src/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index f594265..b18ec0b 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -185,8 +185,10 @@ void messageLoop() { std::vector{counters[linkWireless->getPlayerId()]}); CHECK_ERRORS("Send failed :(") - if (!doubleSend && (keys & KEY_LEFT)) + if (!doubleSend && (keys & KEY_LEFT)) { + doubleSend = true; goto again; + } } if (sending && (!(keys & KEY_A))) sending = false; From caeb851eb35ead98f1f3020bde42d0d7e7802967 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 07:05:38 -0300 Subject: [PATCH 14/27] Using canSend() to prevent errors --- examples/LinkWireless_demo/src/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index b18ec0b..179fdad 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -175,7 +175,8 @@ void messageLoop() { u16 keys = ~REG_KEYS & KEY_ANY; // (5) Send data - if ((keys & KEY_B) || (!sending && (keys & KEY_A))) { + if (linkWireless->canSend() && + ((keys & KEY_B) || (!sending && (keys & KEY_A)))) { bool doubleSend = false; sending = true; From 367f5043387919d1a185a2cbc978f014f50f2853 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 08:16:25 -0300 Subject: [PATCH 15/27] Adding slave limit --- README.md | 8 ++-- docs/wireless_adapter.md | 2 + examples/LinkWireless_demo/src/main.cpp | 28 ++++++++---- lib/LinkWireless.h | 61 +++++++++++++++---------- 4 files changed, 63 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index b93a551..9e4e11e 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,8 @@ The library, by default, implements a lightweight protocol on top of the adapter Name | Type | Default | Description --- | --- | --- | --- -`protocol` | **LinkWireless::Protocol** | `LinkWireless::Protocol::RETRANSMIT` | `LinkWireless::Protocol::BASIC`:
Clients only see messages sent from the server, ignoring other peers. Packet loss can occur, so games need to always send the full game state or implement retransmission on top of this.

`LinkWireless::Protocol::FORWARD`:
The server forwards all messages to the clients.

`LinkWireless::Protocol::RETRANSMIT`:
Same as `FORWARD`, but the library also handles retransmission for you, so there should be no packet loss. +`forwarding` | **bool** | `true` | If `true`, the server forwards all messages to the clients. Otherwise, clients only see messages sent from the server (ignoring other peers). +`retransmission` | **bool** | `true` | If `true`, the library handles retransmission for you, so there should be no packet loss. `maxPlayers` | **u8** | `5` | Maximum number of allowed players. `msgTimeout` | **u32** | `5` | Number of *`receive(...)` calls* without a message from other connected player to disconnect. `bufferSize` | **u32** | `30` | Number of *messages* that the queues will be able to store. @@ -173,10 +174,11 @@ Name | Return type | Description `receive(messages)` | **bool** | Sends the pending data and fills the `messages` vector with incoming messages, checking for timeouts and forwarding if needed. This call doesn't block the hardware waiting for messages, it returns if there are no incoming messages. `disconnect()` | **bool** | Disconnects and resets the adapter. `getState()` | **LinkWireless::State** | Returns the current state (one of `LinkWireless::State::NEEDS_RESET`, `LinkWireless::State::AUTHENTICATED`, `LinkWireless::State::SERVING`, `LinkWireless::State::CONNECTING`, or `LinkWireless::State::CONNECTED`). -`getLastError()` | **LinkWireless::Error** | If one of the other methods return `false`, you can inspect this to know the cause. After this call, the last error is cleared. +`getLastError()` | **LinkWireless::Error** | If one of the other methods returns `false`, you can inspect this to know the cause. After this call, the last error is cleared. `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. `canSend()` | **bool** | Returns `false` only if the next `send(...)` call would fail due to full buffers. `getPendingCount()` | **u32** | Returns the number of outgoing messages ready to be sent. -⚠️ the library can transfer a maximum of 14 words of 32 bits at a time, and messages are often concatenated together, so keep things way below this limit (specially when the protocol is `FORWARD` or `RETRANSMIT`)! \ No newline at end of file +⚠️ masters can send up to `14` words of 32 bits at a time! +⚠️ slaves can only send `4` (or `2` if `retransmission` is on)! diff --git a/docs/wireless_adapter.md b/docs/wireless_adapter.md index 303ab8d..772f645 100644 --- a/docs/wireless_adapter.md +++ b/docs/wireless_adapter.md @@ -328,6 +328,8 @@ Whenever either side expects something to be sent from the other (as SPI is alwa * The third client should send: `0x100000`, `0xaabbccdd` * The fourth client should send: `0x2000000`, `0xaabbccdd` +⚠️ The guest formula only seems to be true when `bytes <= 16`. I'm sure that guests can send more than 16 bytes (I've seen it!), but I have no idea how to reliably build a header that works. Hosts, on the other hand, can send any number of bytes below 90 and it will work just fine. + ⚠️ Note that when having more than 2 connected adapters, data is not transferred between different guests. If a guest wants to tell something to another guest, it has to talk first with the host with `SendData`, and then the host needs to relay that information to the other guest. ⚠️ The command "overrides" previous data, so if one node is using `ReceiveData`, but before the receive call the other node uses two consecutive `SendData`s, the receiving end will only get the last stream. diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 179fdad..377eeda 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -22,9 +22,7 @@ void waitFor(u16 key); void hang(); LinkWireless::Error lastError; - -// (1) Create a LinkWireless instance -LinkWireless* linkWireless = new LinkWireless(); +LinkWireless* linkWireless = NULL; void init() { REG_DISPCNT = DCNT_MODE0 | DCNT_BG0; @@ -32,14 +30,25 @@ void init() { irq_init(NULL); irq_add(II_VBLANK, NULL); - - // (2) Initialize the library - linkWireless->activate(); } int main() { init(); + // Options + log("Press A to start\n\n(hold LEFT = forwarding)\n(hold UP = " + "retransmission)"); + waitFor(KEY_A); + u16 initialKeys = ~REG_KEYS & KEY_ANY; + bool forwarding = initialKeys & KEY_LEFT; + bool retransmission = initialKeys & KEY_UP; + + // (1) Create a LinkWireless instance + linkWireless = new LinkWireless(forwarding, retransmission); + + // (2) Initialize the library + linkWireless->activate(); + bool activating = false; bool serving = false; bool connecting = false; @@ -47,8 +56,11 @@ int main() { while (true) { u16 keys = ~REG_KEYS & KEY_ANY; - log("START = Activate\nL = Serve\nR = Connect\n\n (DOWN = ok)\n " - "(SELECT = cancel)"); + log(std::string("") + + "L = Serve\nR = Connect\n\n (DOWN = ok)\n " + "(SELECT = cancel)\n (START = activate)\n\n-> forwarding: " + + (forwarding ? "ON" : "OFF") + "\n" + + "-> retransmission: " + (retransmission ? "ON" : "OFF")); // START = Activate if ((keys & KEY_START) && !activating) { diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 64b90b9..7307cee 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -34,10 +34,9 @@ // - 7) Disconnect: // linkWireless->disconnect(); // -------------------------------------------------------------------------- -// considerations: -// - the library can transfer a maximum of 14 words of 32 bits at a -// time, and messages are often concatenated together, so keep things way below -// this limit (specially when the protocol is FORWARD or RETRANSMIT)! +// `data` restrictions: +// - masters can send up to 14 words of 32 bits at a time! +// - slaves can only send 4 (or 2 if retransmission is on)! // -------------------------------------------------------------------------- #include @@ -54,8 +53,11 @@ #define LINK_WIRELESS_BROADCAST_SEARCH_WAIT ((160 + 68) * 60) #define LINK_WIRELESS_CMD_TIMEOUT 100 #define LINK_WIRELESS_MAX_PLAYERS 5 -#define LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH 14 -#define LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH 20 +#define LINK_WIRELESS_MAX_USER_SERVER_TRANSFER_LENGTH 14 +#define LINK_WIRELESS_NORMAL_MAX_USER_CLIENT_TRANSFER_LENGTH 4 +#define LINK_WIRELESS_RETRANSMISSION_MAX_USER_CLIENT_TRANSFER_LENGTH 2 +#define LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH 20 +#define LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH 4 #define LINK_WIRELESS_LOGIN_STEPS 9 #define LINK_WIRELESS_COMMAND_HEADER 0x9966 #define LINK_WIRELESS_RESPONSE_ACK 0x80 @@ -89,7 +91,6 @@ const u16 LINK_WIRELESS_LOGIN_PARTS[] = {0x494e, 0x494e, 0x544e, 0x544e, 0x4e45, class LinkWireless { public: enum State { NEEDS_RESET, AUTHENTICATED, SERVING, CONNECTING, CONNECTED }; - enum Protocol { BASIC, FORWARD, RETRANSMIT }; enum Error { NONE, WRONG_STATE, @@ -112,11 +113,13 @@ class LinkWireless { u32 _packetId = 0; }; - explicit LinkWireless(Protocol protocol = RETRANSMIT, + explicit LinkWireless(bool forwarding = true, + bool retransmission = true, u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS, u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, u32 bufferSize = LINK_WIRELESS_DEFAULT_BUFFER_SIZE) { - this->protocol = protocol; + this->forwarding = forwarding; + this->retransmission = retransmission; this->maxPlayers = maxPlayers; this->msgTimeout = msgTimeout; this->bufferSize = bufferSize; @@ -295,8 +298,13 @@ class LinkWireless { lastError = WRONG_STATE; return false; } - if (data.size() == 0 || - data.size() > LINK_WIRELESS_MAX_USER_TRANSFER_LENGTH) { + u32 maxTransferLength = + state == SERVING + ? LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH + : (retransmission + ? LINK_WIRELESS_RETRANSMISSION_MAX_USER_CLIENT_TRANSFER_LENGTH + : LINK_WIRELESS_NORMAL_MAX_USER_CLIENT_TRANSFER_LENGTH); + if (data.size() == 0 || data.size() > maxTransferLength) { lastError = INVALID_SEND_SIZE; return false; } @@ -359,16 +367,14 @@ class LinkWireless { timeouts[remotePlayerId] = 0; if (state == SERVING) { - if (protocol == RETRANSMIT && - packetId != LINK_WIRELESS_MSG_CONFIRMATION && + if (retransmission && packetId != LINK_WIRELESS_MSG_CONFIRMATION && packetId <= lastPacketIdFromClients[remotePlayerId]) goto skip; if (packetId != LINK_WIRELESS_MSG_CONFIRMATION) lastPacketIdFromClients[remotePlayerId] = packetId; } else { - if (protocol == RETRANSMIT && - packetId != LINK_WIRELESS_MSG_CONFIRMATION && + if (retransmission && packetId != LINK_WIRELESS_MSG_CONFIRMATION && packetId <= lastPacketIdFromServer) goto skip; @@ -391,8 +397,7 @@ class LinkWireless { message.data.push_back(words[i + 1 + j]); message._packetId = packetId; - if (protocol == RETRANSMIT && - packetId == LINK_WIRELESS_MSG_CONFIRMATION) { + if (retransmission && packetId == LINK_WIRELESS_MSG_CONFIRMATION) { if (!handleConfirmation(message)) { reset(); lastError = BAD_CONFIRMATION; @@ -414,7 +419,7 @@ class LinkWireless { } } - if (state == SERVING && (protocol == FORWARD || protocol == RETRANSMIT)) { + if (state == SERVING && forwarding && playerCount > 2) { for (auto& message : messages) send(message.data, message.playerId); } @@ -480,7 +485,8 @@ class LinkWireless { u32 asInt; }; - Protocol protocol; + bool forwarding; + bool retransmission; u8 maxPlayers; u32 msgTimeout; u32 bufferSize; @@ -500,16 +506,17 @@ class LinkWireless { bool isEnabled = false; bool sendPendingMessages() { - if (outgoingMessages.empty() && protocol != RETRANSMIT) { + if (outgoingMessages.empty() && !retransmission) { Message emptyMessage; emptyMessage.playerId = playerId; emptyMessage._packetId = ++lastPacketId; outgoingMessages.push_back(emptyMessage); } + u32 maxTransferLength = getDeviceTransferLength(); std::vector words; - if (protocol == RETRANSMIT) + if (retransmission) addConfirmations(words); for (auto& message : outgoingMessages) { @@ -517,7 +524,7 @@ class LinkWireless { u32 header = buildMessageHeader(message.playerId, size, message._packetId); - if (words.size() + 1 + size > LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) + if (words.size() + 1 + size > maxTransferLength) break; words.push_back(header); @@ -527,7 +534,7 @@ class LinkWireless { if (!sendData(words)) return false; - if (protocol != RETRANSMIT) + if (!retransmission) outgoingMessages.clear(); return true; @@ -608,8 +615,7 @@ class LinkWireless { lastError = WRONG_STATE; return false; } - if (data.size() == 0 || - data.size() > LINK_WIRELESS_MAX_DEVICE_TRANSFER_LENGTH) { + if (data.size() == 0 || data.size() > getDeviceTransferLength()) { lastError = INVALID_SEND_SIZE; return false; } @@ -651,6 +657,11 @@ class LinkWireless { return true; } + u32 getDeviceTransferLength() { + return state == SERVING ? LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH + : LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH; + } + bool reset() { this->state = NEEDS_RESET; this->playerId = 0; From d52a709f81bb767317a0830b03bcadff8c75bef7 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 08:47:04 -0300 Subject: [PATCH 16/27] Fixing double send --- examples/LinkWireless_demo/src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 377eeda..820472e 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -198,7 +198,7 @@ void messageLoop() { std::vector{counters[linkWireless->getPlayerId()]}); CHECK_ERRORS("Send failed :(") - if (!doubleSend && (keys & KEY_LEFT)) { + if (!doubleSend && (keys & KEY_LEFT) && linkWireless->canSend()) { doubleSend = true; goto again; } From 00272e35eed6b3d15987253e60e0814d93e9bd59 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 16:00:34 -0300 Subject: [PATCH 17/27] Updating usage limits --- README.md | 5 +++-- docs/wireless_adapter.md | 7 ++++--- lib/LinkWireless.h | 16 +++++++--------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9e4e11e..e2fea8e 100644 --- a/README.md +++ b/README.md @@ -180,5 +180,6 @@ Name | Return type | Description `canSend()` | **bool** | Returns `false` only if the next `send(...)` call would fail due to full buffers. `getPendingCount()` | **u32** | Returns the number of outgoing messages ready to be sent. -⚠️ masters can send up to `14` words of 32 bits at a time! -⚠️ slaves can only send `4` (or `2` if `retransmission` is on)! +⚠️ servers can send up to `19` words of 32 bits at a time! +⚠️ clients can send up to `3` words of 32 bits at a time! +⚠️ if `retransmission` is on, these limits drop to `14` and `1`! diff --git a/docs/wireless_adapter.md b/docs/wireless_adapter.md index 772f645..fb16507 100644 --- a/docs/wireless_adapter.md +++ b/docs/wireless_adapter.md @@ -317,8 +317,6 @@ Whenever either side expects something to be sent from the other (as SPI is alwa * Send N 32 bit values to connected adapter. -⚠️ Each `SendData` can send up to 90 bytes (or 22 values). - ⚠️ The first byte **is a header**, and it has to be correct. Otherwise, the adapter will ignore the command and won't send any data. The header is as follows: - For hosts: the number of `bytes` that comes next. For example, if we want to send `0xaabbccdd` and `0x12345678` in the same command, we need to send: * `0x00000008`, `0xaabbccdd`, `0x12345678`. @@ -328,7 +326,10 @@ Whenever either side expects something to be sent from the other (as SPI is alwa * The third client should send: `0x100000`, `0xaabbccdd` * The fourth client should send: `0x2000000`, `0xaabbccdd` -⚠️ The guest formula only seems to be true when `bytes <= 16`. I'm sure that guests can send more than 16 bytes (I've seen it!), but I have no idea how to reliably build a header that works. Hosts, on the other hand, can send any number of bytes below 90 and it will work just fine. +⚠️ Each `SendData` can send up to: +- **Host:** 90 bytes (or 22 values) +- **Guests:** 16 bytes (or 4 values) +- *(the header doesn't count)* ⚠️ Note that when having more than 2 connected adapters, data is not transferred between different guests. If a guest wants to tell something to another guest, it has to talk first with the host with `SendData`, and then the host needs to relay that information to the other guest. diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 7307cee..3a56b3f 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -35,8 +35,9 @@ // linkWireless->disconnect(); // -------------------------------------------------------------------------- // `data` restrictions: -// - masters can send up to 14 words of 32 bits at a time! -// - slaves can only send 4 (or 2 if retransmission is on)! +// - servers can send up to 19 words of 32 bits at a time! +// - clients can send up to 3 words of 32 bits at a time! +// - if retransmission is on, these limits drop to 14 and 1! // -------------------------------------------------------------------------- #include @@ -53,9 +54,6 @@ #define LINK_WIRELESS_BROADCAST_SEARCH_WAIT ((160 + 68) * 60) #define LINK_WIRELESS_CMD_TIMEOUT 100 #define LINK_WIRELESS_MAX_PLAYERS 5 -#define LINK_WIRELESS_MAX_USER_SERVER_TRANSFER_LENGTH 14 -#define LINK_WIRELESS_NORMAL_MAX_USER_CLIENT_TRANSFER_LENGTH 4 -#define LINK_WIRELESS_RETRANSMISSION_MAX_USER_CLIENT_TRANSFER_LENGTH 2 #define LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH 20 #define LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH 4 #define LINK_WIRELESS_LOGIN_STEPS 9 @@ -87,6 +85,8 @@ const u16 LINK_WIRELESS_LOGIN_PARTS[] = {0x494e, 0x494e, 0x544e, 0x544e, 0x4e45, 0x4e45, 0x4f44, 0x4f44, 0x8001}; +const u16 LINK_WIRELESS_USER_MAX_SERVER_TRANSFER_LENGTHS[] = {19, 14}; +const u32 LINK_WIRELESS_USER_MAX_CLIENT_TRANSFER_LENGTHS[] = {3, 1}; class LinkWireless { public: @@ -300,10 +300,8 @@ class LinkWireless { } u32 maxTransferLength = state == SERVING - ? LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH - : (retransmission - ? LINK_WIRELESS_RETRANSMISSION_MAX_USER_CLIENT_TRANSFER_LENGTH - : LINK_WIRELESS_NORMAL_MAX_USER_CLIENT_TRANSFER_LENGTH); + ? LINK_WIRELESS_USER_MAX_SERVER_TRANSFER_LENGTHS[retransmission] + : LINK_WIRELESS_USER_MAX_CLIENT_TRANSFER_LENGTHS[retransmission]; if (data.size() == 0 || data.size() > maxTransferLength) { lastError = INVALID_SEND_SIZE; return false; From 5898f22c858050b9d96594814f0a304bc9ac6802 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 16:13:16 -0300 Subject: [PATCH 18/27] Adding back option --- examples/LinkWireless_demo/src/main.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 820472e..93f244c 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -35,6 +35,7 @@ void init() { int main() { init(); +start: // Options log("Press A to start\n\n(hold LEFT = forwarding)\n(hold UP = " "retransmission)"); @@ -56,12 +57,20 @@ int main() { while (true) { u16 keys = ~REG_KEYS & KEY_ANY; + // Menu log(std::string("") + "L = Serve\nR = Connect\n\n (DOWN = ok)\n " "(SELECT = cancel)\n (START = activate)\n\n-> forwarding: " + (forwarding ? "ON" : "OFF") + "\n" + "-> retransmission: " + (retransmission ? "ON" : "OFF")); + // SELECT = back + if (keys & KEY_SELECT) { + linkWireless->deactivate(); + linkWireless = NULL; + goto start; + } + // START = Activate if ((keys & KEY_START) && !activating) { activating = true; @@ -149,7 +158,11 @@ void connect() { log(str); } - waitFor(KEY_START); + waitFor(KEY_START | KEY_SELECT); + if ((~REG_KEYS & KEY_ANY) & KEY_SELECT) { + linkWireless->disconnect(); + return; + } linkWireless->connect(serverIds[0]); CHECK_ERRORS("Connect failed :(") From a7d42a4b1873688f14ecdc3da71410cc082c04bd Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 16:51:08 -0300 Subject: [PATCH 19/27] Adding multireceive --- README.md | 11 ++- examples/LinkWireless_demo/src/main.cpp | 25 ++++-- lib/LinkWireless.h | 104 ++++++++++++++++++------ 3 files changed, 108 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index e2fea8e..c5f90c1 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,8 @@ Name | Type | Default | Description `forwarding` | **bool** | `true` | If `true`, the server forwards all messages to the clients. Otherwise, clients only see messages sent from the server (ignoring other peers). `retransmission` | **bool** | `true` | If `true`, the library handles retransmission for you, so there should be no packet loss. `maxPlayers` | **u8** | `5` | Maximum number of allowed players. -`msgTimeout` | **u32** | `5` | Number of *`receive(...)` calls* without a message from other connected player to disconnect. +`msgTimeout` | **u32** | `5` | Timeout used by `receive(...)`. It's the maximum number of *receive calls* without a message from other connected player to disconnect. +`multiReceiveTimeout` | **u32** | `1140` | Timeout used by `receive(messages, times)`. It's the maximum number of *vertical lines* without a message from other connected player to disconnect *(228 vertical lines = 1 frame)*. `bufferSize` | **u32** | `30` | Number of *messages* that the queues will be able to store. ## Methods @@ -172,14 +173,18 @@ Name | Return type | Description `getServerIds(serverIds)` | **bool** | Fills the `serverIds` vector with all the currently broadcasting servers. `send(data)` | **bool** | Enqueues `data` to be sent to other nodes. Note that this data will be sent in the next `receive(...)` call. `receive(messages)` | **bool** | Sends the pending data and fills the `messages` vector with incoming messages, checking for timeouts and forwarding if needed. This call doesn't block the hardware waiting for messages, it returns if there are no incoming messages. +`receive(messages, times)` | **bool** | Perform multiple `receive(...)` calls until successfully exchanging data a number of `times`. This can only be called if `retransmission` is on. +`receive(messages, times, cancel)` | **bool** | Like `receive(messages, times)` but accepts a `cancel` function. The library will continuously invoke it, and abort the transfer if it returns `true`. `disconnect()` | **bool** | Disconnects and resets the adapter. `getState()` | **LinkWireless::State** | Returns the current state (one of `LinkWireless::State::NEEDS_RESET`, `LinkWireless::State::AUTHENTICATED`, `LinkWireless::State::SERVING`, `LinkWireless::State::CONNECTING`, or `LinkWireless::State::CONNECTED`). -`getLastError()` | **LinkWireless::Error** | If one of the other methods returns `false`, you can inspect this to know the cause. After this call, the last error is cleared. `getPlayerId()` | **u8** *(0~4)* | Returns the current player id. `getPlayerCount()` | **u8** *(1~5)* | Returns the connected players. `canSend()` | **bool** | Returns `false` only if the next `send(...)` call would fail due to full buffers. -`getPendingCount()` | **u32** | Returns the number of outgoing messages ready to be sent. +`getPendingCount()` | **u32** | Returns the number of outgoing messages ready to be sent. It will always be lower than `bufferSize`. +`didReceiveBytes()` | **bool** | Returns whether the last `receive(...)` call gathered any bytes or not. +`getLastError()` | **LinkWireless::Error** | If one of the other methods returns `false`, you can inspect this to know the cause. After this call, the last error is cleared. ⚠️ servers can send up to `19` words of 32 bits at a time! ⚠️ clients can send up to `3` words of 32 bits at a time! ⚠️ if `retransmission` is on, these limits drop to `14` and `1`! +⚠️ you can workaround these limits by doing multiple exchanges with `receive(messages, times)`! \ No newline at end of file diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 93f244c..67a3d51 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -4,6 +4,8 @@ // (0) Include the header #include "../../_lib/LinkWireless.h" +#define TRANSFERS_PER_FRAME 4 + #define CHECK_ERRORS(MESSAGE) \ if ((lastError = linkWireless->getLastError())) { \ log(std::string(MESSAGE) + " (" + std::to_string(lastError) + ") [" + \ @@ -23,6 +25,8 @@ void hang(); LinkWireless::Error lastError; LinkWireless* linkWireless = NULL; +bool forwarding = true; +bool retransmission = true; void init() { REG_DISPCNT = DCNT_MODE0 | DCNT_BG0; @@ -37,12 +41,12 @@ int main() { start: // Options - log("Press A to start\n\n(hold LEFT = forwarding)\n(hold UP = " - "retransmission)"); + log("Press A to start\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nhold LEFT on start:\n -> " + "disable forwarding\n\nhold UP on start:\n -> disable retransmission"); waitFor(KEY_A); u16 initialKeys = ~REG_KEYS & KEY_ANY; - bool forwarding = initialKeys & KEY_LEFT; - bool retransmission = initialKeys & KEY_UP; + forwarding = !(initialKeys & KEY_LEFT); + retransmission = !(initialKeys & KEY_UP); // (1) Create a LinkWireless instance linkWireless = new LinkWireless(forwarding, retransmission); @@ -191,8 +195,8 @@ void messageLoop() { std::vector counters; for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) counters.push_back(1 + i * 10); - bool sending = false; + bool sending = false; bool packetLossCheck = false; bool switching = false; @@ -221,7 +225,16 @@ void messageLoop() { // (6) Receive data std::vector messages; - linkWireless->receive(messages); + if (retransmission) { + // (exchanging data 4 times, just for speed purposes) + linkWireless->receive(messages, TRANSFERS_PER_FRAME, []() { + u16 keys = ~REG_KEYS & KEY_ANY; + return keys & KEY_SELECT; + }); + } else { + // (exchanging data one time) + linkWireless->receive(messages); + } CHECK_ERRORS("Receive failed :(") if (messages.size() > 0) { for (auto& message : messages) { diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 3a56b3f..4f66fc6 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -38,6 +38,8 @@ // - servers can send up to 19 words of 32 bits at a time! // - clients can send up to 3 words of 32 bits at a time! // - if retransmission is on, these limits drop to 14 and 1! +// - you can workaround these limits by doing multiple exchanges with +// receive(messages, times)! // -------------------------------------------------------------------------- #include @@ -47,6 +49,7 @@ #include "LinkSPI.h" #define LINK_WIRELESS_DEFAULT_MSG_TIMEOUT 5 +#define LINK_WIRELESS_DEFAULT_MULTIRECEIVE_TIMEOUT (228 * 5) #define LINK_WIRELESS_DEFAULT_BUFFER_SIZE 30 #define LINK_WIRELESS_PING_WAIT 50 #define LINK_WIRELESS_TRANSFER_WAIT 15 @@ -103,7 +106,8 @@ class LinkWireless { RECEIVE_DATA_FAILED, BAD_CONFIRMATION, BAD_MESSAGE, - TIMEOUT + TIMEOUT, + RETRANSMISSION_IS_OFF }; struct Message { @@ -113,15 +117,18 @@ class LinkWireless { u32 _packetId = 0; }; - explicit LinkWireless(bool forwarding = true, - bool retransmission = true, - u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS, - u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, - u32 bufferSize = LINK_WIRELESS_DEFAULT_BUFFER_SIZE) { + explicit LinkWireless( + bool forwarding = true, + bool retransmission = true, + u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS, + u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT, + u32 multiReceiveTimeout = LINK_WIRELESS_DEFAULT_MULTIRECEIVE_TIMEOUT, + u32 bufferSize = LINK_WIRELESS_DEFAULT_BUFFER_SIZE) { this->forwarding = forwarding; this->retransmission = retransmission; this->maxPlayers = maxPlayers; this->msgTimeout = msgTimeout; + this->multiReceiveTimeout = multiReceiveTimeout; this->bufferSize = bufferSize; } @@ -322,7 +329,7 @@ class LinkWireless { return true; } - bool receive(std::vector& messages) { + bool receive(std::vector& messages, bool _timeout = true) { LINK_WIRELESS_RESET_IF_NEEDED if (state != SERVING && state != CONNECTED) { lastError = WRONG_STATE; @@ -344,7 +351,8 @@ class LinkWireless { if (i != playerId) timeouts[i]++; - messages = std::vector{}; + u32 startIndex = messages.size(); + for (u32 i = 0; i < words.size(); i++) { MessageHeaderSerializer serializer; serializer.asInt = words[i]; @@ -410,7 +418,8 @@ class LinkWireless { } for (u32 i = 0; i < playerCount; i++) { - if ((i == 0 || state == SERVING) && timeouts[i] > msgTimeout) { + if ((i == 0 || state == SERVING) && _timeout && + timeouts[i] > msgTimeout) { lastError = TIMEOUT; disconnect(); return false; @@ -418,8 +427,45 @@ class LinkWireless { } if (state == SERVING && forwarding && playerCount > 2) { - for (auto& message : messages) + for (u32 i = startIndex; i < messages.size(); i++) { + auto message = messages[i]; send(message.data, message.playerId); + } + } + + return true; + } + + bool receive(std::vector& messages, u32 times) { + return receive(messages, times, []() { return false; }); + } + + template + bool receive(std::vector& messages, u32 times, F cancel) { + if (!retransmission) { + lastError = RETRANSMISSION_IS_OFF; + return false; + } + + u32 successfullExchanges = 0; + + u32 lines = 0; + u32 vCount = REG_VCOUNT; + while (successfullExchanges < times) { + if (cancel()) + return true; + + if (timeout(multiReceiveTimeout, lines, vCount)) { + lastError = TIMEOUT; + disconnect(); + return false; + } + + if (!receive(messages, false)) + return false; + + if (didReceiveAnyBytes) + successfullExchanges++; } return true; @@ -445,15 +491,16 @@ class LinkWireless { } State getState() { return state; } + u8 getPlayerId() { return playerId; } + u8 getPlayerCount() { return playerCount; } + bool canSend() { return outgoingMessages.size() < bufferSize; } + u32 getPendingCount() { return outgoingMessages.size(); } + bool didReceiveBytes() { return didReceiveAnyBytes; } Error getLastError() { Error error = lastError; lastError = NONE; return error; } - u8 getPlayerId() { return playerId; } - u8 getPlayerCount() { return playerCount; } - bool canSend() { return outgoingMessages.size() < bufferSize; } - u32 getPendingCount() { return outgoingMessages.size(); } ~LinkWireless() { delete linkSPI; @@ -487,6 +534,7 @@ class LinkWireless { bool retransmission; u8 maxPlayers; u32 msgTimeout; + u32 multiReceiveTimeout; u32 bufferSize; LinkSPI* linkSPI = new LinkSPI(); LinkGPIO* linkGPIO = new LinkGPIO(); @@ -500,6 +548,7 @@ class LinkWireless { u32 lastPacketIdFromClients[LINK_WIRELESS_MAX_PLAYERS]; u32 lastConfirmationFromClients[LINK_WIRELESS_MAX_PLAYERS]; u32 timeouts[LINK_WIRELESS_MAX_PLAYERS]; + bool didReceiveAnyBytes = false; Error lastError = NONE; bool isEnabled = false; @@ -640,6 +689,8 @@ class LinkWireless { return false; } + this->didReceiveAnyBytes = false; + auto result = sendCommand(LINK_WIRELESS_COMMAND_RECEIVE_DATA); data = result.responses; @@ -649,8 +700,10 @@ class LinkWireless { return false; } - if (data.size() > 0) + if (data.size() > 0) { data.erase(data.begin()); + this->didReceiveAnyBytes = true; + } return true; } @@ -669,10 +722,11 @@ class LinkWireless { this->lastPacketIdFromServer = 0; this->lastConfirmationFromServer = 0; for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) { - lastPacketIdFromClients[i] = 0; - lastConfirmationFromClients[i] = 0; - timeouts[i] = 0; + this->lastPacketIdFromClients[i] = 0; + this->lastConfirmationFromClients[i] = 0; + this->timeouts[i] = 0; } + this->didReceiveAnyBytes = false; stop(); return start(); @@ -785,7 +839,7 @@ class LinkWireless { u32 lines = 0; u32 vCount = REG_VCOUNT; u32 receivedData = linkSPI->transfer( - data, [this, &lines, &vCount]() { return timeout(lines, vCount); }, + data, [this, &lines, &vCount]() { return cmdTimeout(lines, vCount); }, false, customAck); lines = 0; @@ -793,11 +847,11 @@ class LinkWireless { if (customAck) { linkSPI->_setSOLow(); while (!linkSPI->_isSIHigh()) - if (timeout(lines, vCount)) + if (cmdTimeout(lines, vCount)) return LINK_SPI_NO_DATA; linkSPI->_setSOHigh(); while (linkSPI->_isSIHigh()) - if (timeout(lines, vCount)) + if (cmdTimeout(lines, vCount)) return LINK_SPI_NO_DATA; linkSPI->_setSOLow(); } @@ -805,13 +859,17 @@ class LinkWireless { return receivedData; } - bool timeout(u32& lines, u32& vCount) { + bool cmdTimeout(u32& lines, u32& vCount) { + return timeout(LINK_WIRELESS_CMD_TIMEOUT, lines, vCount); + } + + bool timeout(u32 limit, u32& lines, u32& vCount) { if (REG_VCOUNT != vCount) { lines++; vCount = REG_VCOUNT; } - return lines > LINK_WIRELESS_CMD_TIMEOUT; + return lines > limit; } void wait(u32 verticalLines) { From 1362fd596e05334c3879b8ca80e8c0a317547bfa Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 18:01:39 -0300 Subject: [PATCH 20/27] Minimizing packet loss when retransmission is disabled --- lib/LinkWireless.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 4f66fc6..d73ecba 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -49,7 +49,7 @@ #include "LinkSPI.h" #define LINK_WIRELESS_DEFAULT_MSG_TIMEOUT 5 -#define LINK_WIRELESS_DEFAULT_MULTIRECEIVE_TIMEOUT (228 * 5) +#define LINK_WIRELESS_DEFAULT_MULTIRECEIVE_TIMEOUT ((160 + 68) * 5) #define LINK_WIRELESS_DEFAULT_BUFFER_SIZE 30 #define LINK_WIRELESS_PING_WAIT 50 #define LINK_WIRELESS_TRANSFER_WAIT 15 @@ -336,17 +336,19 @@ class LinkWireless { return false; } - if (!sendPendingMessages()) { - lastError = SEND_DATA_FAILED; - return false; - } - std::vector words; if (!receiveData(words)) { lastError = RECEIVE_DATA_FAILED; return false; } + if (state == SERVING || didReceiveAnyBytes) { + if (!sendPendingMessages()) { + lastError = SEND_DATA_FAILED; + return false; + } + } + for (u32 i = 0; i < playerCount; i++) if (i != playerId) timeouts[i]++; From 8d13b9deaccbbaac7fbb40a5826e23c6194df9dd Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 18:16:11 -0300 Subject: [PATCH 21/27] Fixing multireceive timeouts --- README.md | 2 +- lib/LinkWireless.h | 44 +++++++++++++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c5f90c1..4cd2c67 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Name | Type | Default | Description `retransmission` | **bool** | `true` | If `true`, the library handles retransmission for you, so there should be no packet loss. `maxPlayers` | **u8** | `5` | Maximum number of allowed players. `msgTimeout` | **u32** | `5` | Timeout used by `receive(...)`. It's the maximum number of *receive calls* without a message from other connected player to disconnect. -`multiReceiveTimeout` | **u32** | `1140` | Timeout used by `receive(messages, times)`. It's the maximum number of *vertical lines* without a message from other connected player to disconnect *(228 vertical lines = 1 frame)*. +`multiReceiveTimeout` | **u32** | `1140` | An extra timeout used by `receive(messages, times)`. It's the maximum number of *vertical lines* without a message from anybody to disconnect *(228 vertical lines = 1 frame)*. `bufferSize` | **u32** | `30` | Number of *messages* that the queues will be able to store. ## Methods diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index d73ecba..8da49da 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -329,7 +329,7 @@ class LinkWireless { return true; } - bool receive(std::vector& messages, bool _timeout = true) { + bool receive(std::vector& messages, bool _enableTimeouts = true) { LINK_WIRELESS_RESET_IF_NEEDED if (state != SERVING && state != CONNECTED) { lastError = WRONG_STATE; @@ -349,9 +349,8 @@ class LinkWireless { } } - for (u32 i = 0; i < playerCount; i++) - if (i != playerId) - timeouts[i]++; + if (_enableTimeouts) + trackTimeouts(); u32 startIndex = messages.size(); @@ -419,14 +418,8 @@ class LinkWireless { } } - for (u32 i = 0; i < playerCount; i++) { - if ((i == 0 || state == SERVING) && _timeout && - timeouts[i] > msgTimeout) { - lastError = TIMEOUT; - disconnect(); - return false; - } - } + if (_enableTimeouts && !checkTimeouts()) + return false; if (state == SERVING && forwarding && playerCount > 2) { for (u32 i = startIndex; i < messages.size(); i++) { @@ -450,6 +443,7 @@ class LinkWireless { } u32 successfullExchanges = 0; + trackTimeouts(); u32 lines = 0; u32 vCount = REG_VCOUNT; @@ -470,6 +464,12 @@ class LinkWireless { successfullExchanges++; } + if (!checkTimeouts()) { + lastError = TIMEOUT; + disconnect(); + return false; + } + return true; } @@ -589,6 +589,24 @@ class LinkWireless { return true; } + void trackTimeouts() { + for (u32 i = 0; i < playerCount; i++) + if (i != playerId) + timeouts[i]++; + } + + bool checkTimeouts() { + for (u32 i = 0; i < playerCount; i++) { + if ((i == 0 || state == SERVING) && timeouts[i] > msgTimeout) { + lastError = TIMEOUT; + disconnect(); + return false; + } + } + + return true; + } + void addConfirmations(std::vector& words) { if (state == SERVING) { words.push_back(buildConfirmationHeader(0)); @@ -867,7 +885,7 @@ class LinkWireless { bool timeout(u32 limit, u32& lines, u32& vCount) { if (REG_VCOUNT != vCount) { - lines++; + lines += std::max((s32)REG_VCOUNT - (s32)vCount, 0); vCount = REG_VCOUNT; } From abef3c15759249c82510cacc5d67cd3b98e285d3 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 18:38:49 -0300 Subject: [PATCH 22/27] Removing state limitations to disconnect --- lib/LinkWireless.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 8da49da..e7b4a52 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -475,10 +475,6 @@ class LinkWireless { bool disconnect() { LINK_WIRELESS_RESET_IF_NEEDED - if (state != SERVING && state != CONNECTED) { - lastError = WRONG_STATE; - return false; - } bool success = sendCommand(LINK_WIRELESS_COMMAND_DISCONNECT).success; From 69e3eedf57ea22dc76f8217814609d0944030958 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 18:47:36 -0300 Subject: [PATCH 23/27] Stop hanging when packets are lost! --- examples/LinkWireless_demo/src/main.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index 67a3d51..d5e6427 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -200,6 +200,11 @@ void messageLoop() { bool packetLossCheck = false; bool switching = false; + u32 lostPackets = 0; + u32 lastLostPacketPlayerId = 0; + u32 lastLostPacketExpected = 0; + u32 lastLostPacketReceived = 0; + while (true) { u16 keys = ~REG_KEYS & KEY_ANY; @@ -244,12 +249,10 @@ void messageLoop() { // Check for packet loss if (packetLossCheck && message.data[0] != expected) { - log("Wait... p" + std::to_string(message.playerId) + "\n" + - "\nExpected: " + std::to_string(expected) + "\nReceived: " + - std::to_string(message.data[0]) + "\n\npacket loss? :("); - linkWireless->disconnect(); - hang(); - return; + lostPackets++; + lastLostPacketPlayerId = message.playerId; + lastLostPacketExpected = expected; + lastLostPacketReceived = message.data[0]; } } } @@ -289,6 +292,12 @@ void messageLoop() { "p" + std::to_string(i) + ": " + std::to_string(counters[i]) + "\n"; } output += "\n_buffer: " + std::to_string(linkWireless->getPendingCount()); + if (packetLossCheck && lostPackets > 0) { + output += "\n\n_lostPackets: " + std::to_string(lostPackets) + "\n"; + output += "_last: (" + std::to_string(lastLostPacketPlayerId) + ") " + + std::to_string(lastLostPacketReceived) + " [vs " + + std::to_string(lastLostPacketExpected) + "]"; + } // Print VBlankIntrWait(); From 82808514528dbe7dc703af98f7e67cc577d8712d Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 18:56:03 -0300 Subject: [PATCH 24/27] FIX: Making sure packet ids are consecutive numbers --- lib/LinkWireless.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index e7b4a52..6122c59 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -375,14 +375,16 @@ class LinkWireless { if (state == SERVING) { if (retransmission && packetId != LINK_WIRELESS_MSG_CONFIRMATION && - packetId <= lastPacketIdFromClients[remotePlayerId]) + lastPacketIdFromClients[remotePlayerId] > 0 && + packetId != lastPacketIdFromClients[remotePlayerId] + 1) goto skip; if (packetId != LINK_WIRELESS_MSG_CONFIRMATION) lastPacketIdFromClients[remotePlayerId] = packetId; } else { if (retransmission && packetId != LINK_WIRELESS_MSG_CONFIRMATION && - packetId <= lastPacketIdFromServer) + lastPacketIdFromServer > 0 && + packetId != lastPacketIdFromServer + 1) goto skip; playerCount = remotePlayerCount; From d843165b63c2aa0b3b621975095cefaf9de15c48 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 19:25:01 -0300 Subject: [PATCH 25/27] Resetting packet loss check --- examples/LinkWireless_demo/src/main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/examples/LinkWireless_demo/src/main.cpp b/examples/LinkWireless_demo/src/main.cpp index d5e6427..e0ac483 100644 --- a/examples/LinkWireless_demo/src/main.cpp +++ b/examples/LinkWireless_demo/src/main.cpp @@ -277,6 +277,12 @@ void messageLoop() { if (!switching && (keys & KEY_UP)) { switching = true; packetLossCheck = !packetLossCheck; + if (!packetLossCheck) { + lostPackets = 0; + lastLostPacketPlayerId = 0; + lastLostPacketExpected = 0; + lastLostPacketReceived = 0; + } } if (switching && (!(keys & KEY_UP))) switching = false; From 2c8b81e439e1447c374d4132898fb56f278aac10 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 19:34:13 -0300 Subject: [PATCH 26/27] Improving docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4cd2c67..5bf7e15 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Name | Type | Default | Description `forwarding` | **bool** | `true` | If `true`, the server forwards all messages to the clients. Otherwise, clients only see messages sent from the server (ignoring other peers). `retransmission` | **bool** | `true` | If `true`, the library handles retransmission for you, so there should be no packet loss. `maxPlayers` | **u8** | `5` | Maximum number of allowed players. -`msgTimeout` | **u32** | `5` | Timeout used by `receive(...)`. It's the maximum number of *receive calls* without a message from other connected player to disconnect. +`msgTimeout` | **u32** | `5` | Timeout used by `receive(messages)`. It's the maximum number of *receive calls* without a message from other connected player to disconnect. `multiReceiveTimeout` | **u32** | `1140` | An extra timeout used by `receive(messages, times)`. It's the maximum number of *vertical lines* without a message from anybody to disconnect *(228 vertical lines = 1 frame)*. `bufferSize` | **u32** | `30` | Number of *messages* that the queues will be able to store. @@ -173,7 +173,7 @@ Name | Return type | Description `getServerIds(serverIds)` | **bool** | Fills the `serverIds` vector with all the currently broadcasting servers. `send(data)` | **bool** | Enqueues `data` to be sent to other nodes. Note that this data will be sent in the next `receive(...)` call. `receive(messages)` | **bool** | Sends the pending data and fills the `messages` vector with incoming messages, checking for timeouts and forwarding if needed. This call doesn't block the hardware waiting for messages, it returns if there are no incoming messages. -`receive(messages, times)` | **bool** | Perform multiple `receive(...)` calls until successfully exchanging data a number of `times`. This can only be called if `retransmission` is on. +`receive(messages, times)` | **bool** | Performs multiple `receive(...)` calls until successfully exchanging data a number of `times`. This can only be called if `retransmission` is on. `receive(messages, times, cancel)` | **bool** | Like `receive(messages, times)` but accepts a `cancel` function. The library will continuously invoke it, and abort the transfer if it returns `true`. `disconnect()` | **bool** | Disconnects and resets the adapter. `getState()` | **LinkWireless::State** | Returns the current state (one of `LinkWireless::State::NEEDS_RESET`, `LinkWireless::State::AUTHENTICATED`, `LinkWireless::State::SERVING`, `LinkWireless::State::CONNECTING`, or `LinkWireless::State::CONNECTED`). From e839e6f23a8d90cb09155e158631ca40d8de5f68 Mon Sep 17 00:00:00 2001 From: Rodrigo Alfonso Date: Fri, 3 Feb 2023 19:52:13 -0300 Subject: [PATCH 27/27] Sending and receiving in the original order --- lib/LinkWireless.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 6122c59..164a3b9 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -34,7 +34,7 @@ // - 7) Disconnect: // linkWireless->disconnect(); // -------------------------------------------------------------------------- -// `data` restrictions: +// restrictions: // - servers can send up to 19 words of 32 bits at a time! // - clients can send up to 3 words of 32 bits at a time! // - if retransmission is on, these limits drop to 14 and 1! @@ -336,19 +336,17 @@ class LinkWireless { return false; } + if (!sendPendingMessages()) { + lastError = SEND_DATA_FAILED; + return false; + } + std::vector words; if (!receiveData(words)) { lastError = RECEIVE_DATA_FAILED; return false; } - if (state == SERVING || didReceiveAnyBytes) { - if (!sendPendingMessages()) { - lastError = SEND_DATA_FAILED; - return false; - } - } - if (_enableTimeouts) trackTimeouts();