diff --git a/README.md b/README.md index c5b3e54..30c1649 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Name | Type | Default | Description `sendTimerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending. You can also change these compile-time constants: -- `LINK_CABLE_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outcoming messages the queues can store at max). The default value is `30`, which seems fine for most games. +- `LINK_CABLE_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max **per player**). The default value is `15`, which seems fine for most games. ## Methods @@ -61,9 +61,9 @@ Name | Return type | Description `isConnected()` | **bool** | Returns `true` if there are at least 2 connected players. `playerCount()` | **u8** *(0~4)* | Returns the number of connected players. `currentPlayerId()` | **u8** *(0~3)* | Returns the current player id. +`sync()` | - | Call this method every time you need to fetch new data. `canRead(playerId)` | **bool** | Returns `true` if there are pending messages from player #`playerId`. `read(playerId)` | **u16** | Returns one message from player #`playerId`. -`consume()` | - | Marks the current data as processed, enabling the library to fetch more. `send(data)` | - | Sends `data` to all connected players. ⚠️ `0xFFFF` and `0x0` are reserved values, so don't send them! @@ -165,7 +165,7 @@ Name | Type | Default | Description `asyncACKTimerId` | **s8** *(0~3 or -1)* | `-1` | GBA Timer to use for ACKs. If you have free timers, use one here to reduce CPU usage. You can also change these compile-time constants: -- `LINK_WIRELESS_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outcoming messages the queues can store at max). The default value is `30`, which seems fine for most games. +- `LINK_WIRELESS_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max). The default value is `30`, which seems fine for most games. - `LINK_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH`: to set the biggest allowed response from the adapter. The default value is `50`, which allows reading all user messages (max receive length is `21`) and -in theory- up to `7` broadcasting servers *(7 values per broadcast * 7 = 49 responses)*. This library was only tested with `4` adapters, so the real maximum is unknown. - `LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH` and `LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH`: to set the biggest allowed transfer per timer tick. Transfers contain retransmission headers and multiple user messages. These values must be in the range `[6;20]` for servers and `[2;4]` for clients. The default values are `20` and `4`, but you might want to set them a bit lower to reduce CPU usage. @@ -217,9 +217,7 @@ Name | Type | Default | Description ## Methods -The interface is the same as [👾 LinkCable](#methods), with one exception: instead of calling `consume()` at the end of your game loop, you call `sync()` at the start. - -Aditionally, it supports these methods: +The interface is the same as [👾 LinkCable](#methods). Aditionally, it supports these methods: Name | Return type | Description --- | --- | --- diff --git a/examples/LinkCableMultiboot_demo/src/main.cpp b/examples/LinkCableMultiboot_demo/src/main.cpp index b2c0689..7bed7ef 100644 --- a/examples/LinkCableMultiboot_demo/src/main.cpp +++ b/examples/LinkCableMultiboot_demo/src/main.cpp @@ -96,6 +96,7 @@ int main() { // Client mode // --- + linkCable->sync(); linkCable->send(keys + 1); std::string output = ""; @@ -119,8 +120,6 @@ int main() { output += std::string("Waiting... "); } - linkCable->consume(); - VBlankIntrWait(); log(output); } diff --git a/examples/LinkCable_basic/src/main.cpp b/examples/LinkCable_basic/src/main.cpp index ad24d00..6fa51c2 100644 --- a/examples/LinkCable_basic/src/main.cpp +++ b/examples/LinkCable_basic/src/main.cpp @@ -38,7 +38,10 @@ int main() { data[i] = 0; while (true) { - // (4) Send/read messages + // (4) Sync + linkCable->sync(); + + // (5) Send/read messages u16 keys = ~REG_KEYS & KEY_ANY; linkCable->send(keys + 1); // (avoid using 0) @@ -63,9 +66,6 @@ int main() { output += std::string("Waiting..."); } - // (5) Mark the current state copy (front buffer) as consumed - linkCable->consume(); - VBlankIntrWait(); log(output); } diff --git a/examples/LinkCable_full/src/scenes/TestScene.cpp b/examples/LinkCable_full/src/scenes/TestScene.cpp index f468648..37ff10b 100644 --- a/examples/LinkCable_full/src/scenes/TestScene.cpp +++ b/examples/LinkCable_full/src/scenes/TestScene.cpp @@ -42,10 +42,8 @@ void TestScene::tick(u16 keys) { if (engine->isTransitioning()) return; -#ifdef USE_LINK_UNIVERSAL // sync link->sync(); -#endif frameCounter++; @@ -103,9 +101,4 @@ void TestScene::tick(u16 keys) { } } } - -#ifndef USE_LINK_UNIVERSAL - // mark link buffer as consumed - link->consume(); -#endif } diff --git a/examples/LinkCable_stress/src/main.cpp b/examples/LinkCable_stress/src/main.cpp index 9d3d690..f91c007 100644 --- a/examples/LinkCable_stress/src/main.cpp +++ b/examples/LinkCable_stress/src/main.cpp @@ -35,6 +35,8 @@ int main() { bool error = false; while (true) { + linkCable->sync(); + std::string output = ""; if (linkCable->isConnected()) { auto playerCount = linkCable->playerCount(); @@ -68,8 +70,6 @@ int main() { error = false; } - linkCable->consume(); - VBlankIntrWait(); log(output); diff --git a/lib/LinkCable.h b/lib/LinkCable.h index d105703..600db5d 100644 --- a/lib/LinkCable.h +++ b/lib/LinkCable.h @@ -14,6 +14,9 @@ // irq_add(II_TIMER3, LINK_CABLE_ISR_TIMER); // - 3) Initialize the library with: // linkCable->activate(); +// - 4) Sync: +// linkUniversal->sync(); +// // (put this line at the start of your game loop) // - 4) Send/read messages by using: // bool isConnected = linkCable->isConnected(); // u8 playerCount = linkCable->playerCount(); @@ -23,9 +26,6 @@ // u16 message = linkCable->read(!currentPlayerId); // // ... // } -// - 5) Mark the current state copy (front buffer) as consumed: -// linkCable->consume(); -// // (put this line at the end of your game loop) // -------------------------------------------------------------------------- // (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug. // That can cause packet loss. You might want to use libugba's instead. @@ -39,7 +39,7 @@ #include // Buffer size -#define LINK_CABLE_QUEUE_SIZE 30 +#define LINK_CABLE_QUEUE_SIZE 15 #define LINK_CABLE_MAX_PLAYERS 4 #define LINK_CABLE_DISCONNECTED 0xFFFF @@ -63,7 +63,7 @@ #define LINK_CABLE_SET_LOW(REG, BIT) REG &= ~(1 << BIT) #define LINK_CABLE_BARRIER asm volatile("" ::: "memory") -static volatile char LINK_CABLE_VERSION[] = "LinkCable/v5.1.1"; +static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.0.0"; void LINK_CABLE_ISR_VBLANK(); void LINK_CABLE_ISR_SERIAL(); @@ -103,8 +103,8 @@ class LinkCable { } void clear() { - while (!isEmpty()) - pop(); + front = count = 0; + rear = -1; } int size() { return count; } @@ -133,46 +133,49 @@ class LinkCable { bool isActive() { return isEnabled; } void activate() { + isEnabled = false; reset(); isEnabled = true; } void deactivate() { isEnabled = false; - isStateReady = false; - isStateConsumed = false; - isResetting = false; resetState(); stop(); } bool isConnected() { - return $state.playerCount > 1 && - $state.currentPlayerId < $state.playerCount; + return state.playerCount > 1 && state.currentPlayerId < state.playerCount; } - u8 playerCount() { return $state.playerCount; } - u8 currentPlayerId() { return $state.currentPlayerId; } + u8 playerCount() { return state.playerCount; } + u8 currentPlayerId() { return state.currentPlayerId; } + + void sync() { + if (!isEnabled) + return; + + LINK_CABLE_BARRIER; + isReadingMessages = true; + LINK_CABLE_BARRIER; + + for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++) + move(_state.pendingMessages[i], state.incomingMessages[i]); + + LINK_CABLE_BARRIER; + isReadingMessages = false; + LINK_CABLE_BARRIER; + + if (!isConnected()) + for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++) + state.incomingMessages[i].clear(); + } bool canRead(u8 playerId) { - if (!isStateReady || isStateConsumed) - return false; - - LINK_CABLE_BARRIER; - - return !$state.incomingMessages[playerId].isEmpty(); + return !state.incomingMessages[playerId].isEmpty(); } - u16 read(u8 playerId) { - if (!isStateReady || isStateConsumed) - return LINK_CABLE_NO_DATA; - - LINK_CABLE_BARRIER; - - return $state.incomingMessages[playerId].pop(); - } - - void consume() { isStateConsumed = true; } + u16 read(u8 playerId) { return state.incomingMessages[playerId].pop(); } void send(u16 data) { if (data == LINK_CABLE_DISCONNECTED || data == LINK_CABLE_NO_DATA) @@ -188,9 +191,9 @@ class LinkCable { isAddingMessage = false; LINK_CABLE_BARRIER; - if (isResetting) { + if (isAddingWhileResetting) { _state.outgoingMessages.clear(); - isResetting = false; + isAddingWhileResetting = false; } } @@ -210,8 +213,8 @@ class LinkCable { if (!isEnabled) return; - if (resetIfNeeded()) { - copyState(); + if (!isReady() || hasError()) { + reset(); return; } @@ -224,17 +227,18 @@ class LinkCable { if (data != LINK_CABLE_DISCONNECTED) { if (data != LINK_CABLE_NO_DATA && i != state.currentPlayerId) - state.incomingMessages[i].push(data); + _state.tmpMessagesToReceive[i].push(data); newPlayerCount++; - _state.timeouts[i] = 0; - } else if (_state.timeouts[i] > LINK_CABLE_REMOTE_TIMEOUT_OFFLINE) { + setOnline(i); + } else if (isOnline(i)) { _state.timeouts[i]++; if (_state.timeouts[i] >= (int)config.remoteTimeout) { - state.incomingMessages[i].clear(); - _state.timeouts[i] = LINK_CABLE_REMOTE_TIMEOUT_OFFLINE; - } else + _state.tmpMessagesToReceive[i].clear(); + setOffline(i); + } else { newPlayerCount++; + } } } @@ -255,7 +259,6 @@ class LinkCable { if (didTimeout()) { reset(); - copyState(); return; } @@ -282,20 +285,20 @@ class LinkCable { struct InternalState { U16Queue outgoingMessages; + U16Queue pendingMessages[LINK_CABLE_MAX_PLAYERS]; + U16Queue tmpMessagesToReceive[LINK_CABLE_MAX_PLAYERS]; int timeouts[LINK_CABLE_MAX_PLAYERS]; bool IRQFlag; u32 IRQTimeout; }; - ExternalState state; // (updated state / back buffer) - ExternalState $state; // (visible state / front buffer) - InternalState _state; // (internal state) + ExternalState state; + InternalState _state; Config config; - bool isEnabled = false; - volatile bool isStateReady = false; - volatile bool isStateConsumed = false; + volatile bool isEnabled = false; + volatile bool isReadingMessages = false; volatile bool isAddingMessage = false; - volatile bool isResetting = false; + volatile bool isAddingWhileResetting = false; bool isReady() { return isBitHigh(LINK_CABLE_BIT_READY); } bool hasError() { return isBitHigh(LINK_CABLE_BIT_ERROR); } @@ -319,15 +322,6 @@ class LinkCable { setBitHigh(LINK_CABLE_BIT_START); } - bool resetIfNeeded() { - if (!isReady() || hasError()) { - reset(); - return true; - } - - return false; - } - void reset() { resetState(); stop(); @@ -337,17 +331,21 @@ class LinkCable { void resetState() { state.playerCount = 0; state.currentPlayerId = 0; + + if (isAddingMessage) + isAddingWhileResetting = true; + else + _state.outgoingMessages.clear(); + for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++) { - state.incomingMessages[i].clear(); - _state.timeouts[i] = LINK_CABLE_REMOTE_TIMEOUT_OFFLINE; + if (!isReadingMessages) + _state.pendingMessages[i].clear(); + + _state.tmpMessagesToReceive[i].clear(); + setOffline(i); } _state.IRQFlag = false; _state.IRQTimeout = 0; - - if (isAddingMessage || isResetting) - isResetting = true; - else - _state.outgoingMessages.clear(); } void stop() { @@ -379,21 +377,28 @@ class LinkCable { } void copyState() { - if (isStateReady && !isStateConsumed) + if (isReadingMessages) return; - LINK_CABLE_BARRIER; - $state.playerCount = state.playerCount; - $state.currentPlayerId = state.currentPlayerId; for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++) { - $state.incomingMessages[i].clear(); - while (!state.incomingMessages[i].isEmpty()) - $state.incomingMessages[i].push(state.incomingMessages[i].pop()); + if (isOnline(i)) + move(_state.tmpMessagesToReceive[i], _state.pendingMessages[i]); + else + _state.pendingMessages[i].clear(); } - LINK_CABLE_BARRIER; - isStateReady = true; - isStateConsumed = false; - LINK_CABLE_BARRIER; + } + + void move(U16Queue& src, U16Queue& dst) { + while (!src.isEmpty()) + dst.push(src.pop()); + } + + bool isOnline(u8 playerId) { + return _state.timeouts[playerId] != LINK_CABLE_REMOTE_TIMEOUT_OFFLINE; + } + void setOnline(u8 playerId) { _state.timeouts[playerId] = 0; } + void setOffline(u8 playerId) { + _state.timeouts[playerId] = LINK_CABLE_REMOTE_TIMEOUT_OFFLINE; } bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; } diff --git a/lib/LinkCableMultiboot.h b/lib/LinkCableMultiboot.h index b81fc76..4068e1f 100644 --- a/lib/LinkCableMultiboot.h +++ b/lib/LinkCableMultiboot.h @@ -60,7 +60,7 @@ return error(FAILURE_DURING_HANDSHAKE); static volatile char LINK_CABLE_MULTIBOOT_VERSION[] = - "LinkCableMultiboot/v5.1.1"; + "LinkCableMultiboot/v6.0.0"; const u8 LINK_CABLE_MULTIBOOT_CLIENT_IDS[] = {0b0010, 0b0100, 0b1000}; diff --git a/lib/LinkGPIO.h b/lib/LinkGPIO.h index 7569b62..67476fb 100644 --- a/lib/LinkGPIO.h +++ b/lib/LinkGPIO.h @@ -37,7 +37,7 @@ #define LINK_GPIO_SET_HIGH(REG, BIT) REG |= 1 << BIT #define LINK_GPIO_SET_LOW(REG, BIT) REG &= ~(1 << BIT) -static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v5.1.1"; +static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.0.0"; const u8 LINK_GPIO_DATA_BITS[] = {2, 3, 1, 0}; const u8 LINK_GPIO_DIRECTION_BITS[] = {6, 7, 5, 4}; diff --git a/lib/LinkSPI.h b/lib/LinkSPI.h index adda160..11b4b35 100644 --- a/lib/LinkSPI.h +++ b/lib/LinkSPI.h @@ -52,7 +52,7 @@ #define LINK_SPI_SET_HIGH(REG, BIT) REG |= 1 << BIT #define LINK_SPI_SET_LOW(REG, BIT) REG &= ~(1 << BIT) -static volatile char LINK_SPI_VERSION[] = "LinkSPI/v5.1.1"; +static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.0.0"; class LinkSPI { public: @@ -192,7 +192,7 @@ class LinkSPI { bool waitMode = false; AsyncState asyncState = IDLE; u32 asyncData = 0; - bool isEnabled = false; + volatile bool isEnabled = false; void setNormalMode() { LINK_SPI_SET_LOW(REG_RCNT, LINK_SPI_BIT_GENERAL_PURPOSE_HIGH); diff --git a/lib/LinkUniversal.h b/lib/LinkUniversal.h index 47fb66d..2ba6a1a 100644 --- a/lib/LinkUniversal.h +++ b/lib/LinkUniversal.h @@ -53,7 +53,7 @@ #define LINK_UNIVERSAL_SERVE_WAIT_FRAMES 60 #define LINK_UNIVERSAL_SERVE_WAIT_FRAMES_RANDOM 30 -static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v5.1.1"; +static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.0.0"; void LINK_UNIVERSAL_ISR_VBLANK(); void LINK_UNIVERSAL_ISR_SERIAL(); @@ -153,6 +153,9 @@ class LinkUniversal { __qran_seed += REG_RCNT; __qran_seed += REG_SIOCNT; + if (mode == LINK_CABLE) + linkCable->sync(); + switch (state) { case INITIALIZING: { waitCount++; @@ -209,9 +212,6 @@ class LinkUniversal { break; } } - - if (mode == LINK_CABLE) - linkCable->consume(); } bool canRead(u8 playerId) { return !incomingMessages[playerId].isEmpty(); } @@ -282,7 +282,7 @@ class LinkUniversal { u32 switchWait = 0; u32 subWaitCount = 0; u32 serveWait = 0; - bool isEnabled = false; + volatile bool isEnabled = false; void receiveCableMessages() { for (u32 i = 0; i < LINK_UNIVERSAL_MAX_PLAYERS; i++) { diff --git a/lib/LinkWireless.h b/lib/LinkWireless.h index 5f91b1e..5e267a0 100644 --- a/lib/LinkWireless.h +++ b/lib/LinkWireless.h @@ -128,7 +128,7 @@ if (!reset()) \ return false; -static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v5.1.1"; +static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v6.0.0"; void LINK_WIRELESS_ISR_VBLANK(); void LINK_WIRELESS_ISR_SERIAL(); @@ -661,8 +661,8 @@ class LinkWireless { } void clear() { - while (!isEmpty()) - pop(); + front = count = 0; + rear = -1; } int size() { return count; } @@ -761,7 +761,7 @@ class LinkWireless { volatile bool isAddingMessage = false; volatile bool isPendingClearActive = false; Error lastError = NONE; - bool isEnabled = false; + volatile bool isEnabled = false; void forwardMessageIfNeeded(Message& message) { if (state == SERVING && config.forwarding && sessionState.playerCount > 2)