LinkCable: Refactoring to use LinkRawCable internally

This commit is contained in:
Rodrigo Alfonso 2025-01-05 06:07:09 -03:00
parent 107624e68f
commit 8ec893b0ff
10 changed files with 73 additions and 102 deletions

View File

@ -119,7 +119,7 @@ You can also change these compile-time constants:
| `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. |
| `sync()` | - | Call this method whenever you need to fetch new data. It does not **wait** for new messages; instead, it collects and queues any available messages from the interrupt world for later processing with the `read(...)` method. |
| `waitFor(playerId)` | **bool** | Waits for data from player #`playerId`. Returns `true` on success, or `false` on disconnection. |
| `waitFor(playerId, cancel)` | **bool** | Like `waitFor(playerId)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the wait if it returns `true`. |
| `canRead(playerId)` | **bool** | Returns `true` if there are pending messages from player #`playerId`. Keep in mind that if this returns `false`, it will keep doing so until you _fetch new data_ with `sync()`. |
@ -180,8 +180,11 @@ You can change these compile-time constants:
| `isMaster()` | **bool** | Returns whether the console is connected as master or not. Returns garbage when the cable is not properly connected. |
| `isReady()` | **bool** | Returns whether all connected consoles have entered the multiplayer mode. Returns garbage when the cable is not properly connected. |
- don't send `0xFFFF`, it's a reserved value that means _disconnected client_
- only `transfer(...)` if `isReady()`
⚠️ don't send `0xFFFF`, it's a reserved value that means _disconnected client_
⚠️ only `transfer(...)` if `isReady()`
⚠️ if you're building a game, use `LinkCable`
# 📻 LinkWireless

View File

@ -48,6 +48,8 @@
#include "_link_common.hpp"
#include "LinkRawCable.hpp"
#ifndef LINK_CABLE_QUEUE_SIZE
/**
* @brief Buffer size (how many incoming and outgoing messages the queues can
@ -85,23 +87,9 @@ class LinkCable {
static constexpr auto BASE_FREQUENCY = Link::_TM_FREQ_1024;
static constexpr int REMOTE_TIMEOUT_OFFLINE = -1;
static constexpr int BIT_SLAVE = 2;
static constexpr int BIT_READY = 3;
static constexpr int BITS_PLAYER_ID = 4;
static constexpr int BIT_ERROR = 6;
static constexpr int BIT_START = 7;
static constexpr int BIT_MULTIPLAYER = 13;
static constexpr int BIT_IRQ = 14;
static constexpr int BIT_GENERAL_PURPOSE_LOW = 14;
static constexpr int BIT_GENERAL_PURPOSE_HIGH = 15;
public:
enum BaudRate {
BAUD_RATE_0, // 9600 bps
BAUD_RATE_1, // 38400 bps
BAUD_RATE_2, // 57600 bps
BAUD_RATE_3 // 115200 bps
};
using BaudRate = LinkRawCable::BaudRate;
/**
* @brief Constructs a new LinkCable object.
@ -115,7 +103,7 @@ class LinkCable {
* \warning You can use `Link::perFrame(...)` to convert from *packets per
* frame* to *interval values*.
*/
explicit LinkCable(BaudRate baudRate = BAUD_RATE_1,
explicit LinkCable(BaudRate baudRate = BaudRate::BAUD_RATE_1,
u32 timeout = LINK_CABLE_DEFAULT_TIMEOUT,
u16 interval = LINK_CABLE_DEFAULT_INTERVAL,
u8 sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID) {
@ -177,7 +165,9 @@ class LinkCable {
[[nodiscard]] u8 currentPlayerId() { return state.currentPlayerId; }
/**
* @brief Call this method every time you need to fetch new data.
* @brief Call this method whenever you need to fetch new data. It doesn't
* block the system; it simply collects and queues a set of messages from the
* interrupt world for later processing with the `read(...)` method.
*/
void sync() {
if (!isEnabled)
@ -277,6 +267,8 @@ class LinkCable {
startTimer();
}
~LinkCable() { delete linkRawCable; }
/**
* @brief This method is called by the VBLANK interrupt handler.
* \warning This is internal API!
@ -311,17 +303,20 @@ class LinkCable {
if (!isEnabled)
return;
if (!isReady() || hasError()) {
if (!LinkRawCable::allReady() || LinkRawCable::hasError()) {
reset();
return;
}
auto response = LinkRawCable::getData();
state.currentPlayerId = response.playerId;
_state.IRQFlag = true;
_state.IRQTimeout = 0;
u8 newPlayerCount = 0;
for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++) {
u16 data = Link::_REG_SIOMULTI[i];
u16 data = response.data[i];
if (data != LINK_CABLE_DISCONNECTED) {
if (data != LINK_CABLE_NO_DATA && i != state.currentPlayerId)
@ -339,12 +334,10 @@ class LinkCable {
}
state.playerCount = newPlayerCount;
state.currentPlayerId =
(Link::_REG_SIOCNT & (0b11 << BITS_PLAYER_ID)) >> BITS_PLAYER_ID;
Link::_REG_SIOMLT_SEND = LINK_CABLE_NO_DATA;
LinkRawCable::setData(LINK_CABLE_NO_DATA);
if (!isMaster())
if (!LinkRawCable::isMasterNode())
sendPendingData();
copyState();
@ -358,7 +351,8 @@ class LinkCable {
if (!isEnabled)
return;
if (isMaster() && isReady() && !isSending())
if (LinkRawCable::isMasterNode() && LinkRawCable::allReady() &&
!LinkRawCable::isSending())
sendPendingData();
copyState();
@ -394,15 +388,12 @@ class LinkCable {
bool IRQFlag;
};
LinkRawCable* linkRawCable = new LinkRawCable();
ExternalState state;
InternalState _state;
volatile bool isEnabled = false;
volatile bool isReadingMessages = false;
bool isMaster() { return !isBitHigh(BIT_SLAVE); }
bool isReady() { return isBitHigh(BIT_READY); }
bool hasError() { return isBitHigh(BIT_ERROR); }
bool isSending() { return isBitHigh(BIT_START); }
bool didTimeout() { return _state.IRQTimeout >= config.timeout; }
void sendPendingData() {
@ -415,10 +406,10 @@ class LinkCable {
}
void transfer(u16 data) {
Link::_REG_SIOMLT_SEND = data;
LinkRawCable::setData(data);
if (isMaster())
setBitHigh(BIT_START);
if (LinkRawCable::isMasterNode())
LinkRawCable::startTransfer();
}
void reset() {
@ -445,15 +436,14 @@ class LinkCable {
}
void stop() {
Link::_REG_SIOMLT_SEND = LINK_CABLE_NO_DATA;
stopTimer();
setGeneralPurposeMode();
linkRawCable->deactivate();
}
void start() {
startTimer();
setMultiPlayMode();
setInterruptsOn();
linkRawCable->activate(config.baudRate);
LinkRawCable::setInterruptsOn();
}
void stopTimer() {
@ -502,24 +492,6 @@ class LinkCable {
_state.msgTimeouts[playerId] = REMOTE_TIMEOUT_OFFLINE;
_state.msgFlags[playerId] = false;
}
void setInterruptsOn() { setBitHigh(BIT_IRQ); }
void setMultiPlayMode() {
Link::_REG_RCNT = Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_HIGH);
Link::_REG_SIOCNT = (1 << BIT_MULTIPLAYER);
Link::_REG_SIOCNT |= config.baudRate;
Link::_REG_SIOMLT_SEND = 0;
}
void setGeneralPurposeMode() {
Link::_REG_RCNT = (Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_LOW)) |
(1 << BIT_GENERAL_PURPOSE_HIGH);
}
bool isBitHigh(u8 bit) { return (Link::_REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { Link::_REG_SIOCNT |= 1 << bit; }
void setBitLow(u8 bit) { Link::_REG_SIOCNT &= ~(1 << bit); }
};
extern LinkCable* linkCable;

View File

@ -887,6 +887,7 @@ class LinkMobile {
using RequestQueue = Link::Queue<UserRequest, LINK_MOBILE_QUEUE_SIZE, false>;
LinkSPI* linkSPI = new LinkSPI();
RequestQueue userRequests;
AdapterConfiguration adapterConfiguration;
AsyncCommand asyncCommand;
@ -894,7 +895,6 @@ class LinkMobile {
u32 timeoutStateFrames = 0;
u32 pingFrameCount = 0;
Role role = Role::NO_P2P_CONNECTION;
LinkSPI* linkSPI = new LinkSPI();
State state = NEEDS_RESET;
PacketData nextCommandData;
u32 nextCommandDataSize = 0;

View File

@ -36,6 +36,7 @@
// considerations:
// - don't send 0xFFFF, it's a reserved value that means <disconnected client>
// - only transfer(...) if isReady()
// - if you're building a game, use `LinkCable`.
// --------------------------------------------------------------------------
#ifndef LINK_DEVELOPMENT
@ -51,6 +52,7 @@ static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v7.1.0";
/**
* @brief A low level handler for the Link Port (Multi-Play Mode).
* \warning If you're building a game, use `LinkCable`.
*/
class LinkRawCable {
private:
@ -206,13 +208,13 @@ class LinkRawCable {
* @brief Returns whether the console is connected as master or not. Returns
* garbage when the cable is not properly connected.
*/
[[nodiscard]] bool isMaster() { return !isBitHigh(BIT_SLAVE); }
[[nodiscard]] bool isMaster() { return isMasterNode(); }
/**
* @brief Returns whether all connected consoles have entered the multiplayer
* mode. Returns garbage when the cable is not properly connected.
*/
[[nodiscard]] bool isReady() { return isBitHigh(BIT_READY); }
[[nodiscard]] bool isReady() { return allReady(); }
/**
* @brief This method is called by the SERIAL interrupt handler.
@ -229,6 +231,31 @@ class LinkRawCable {
asyncData = getData();
}
// -------------
// Low-level API
// -------------
static void setData(u16 data) { Link::_REG_SIOMLT_SEND = data; }
[[nodiscard]] static Response getData() {
Response response = EMPTY_RESPONSE;
for (u32 i = 0; i < LINK_RAW_CABLE_MAX_PLAYERS; i++)
response.data[i] = Link::_REG_SIOMULTI[i];
response.playerId =
(Link::_REG_SIOCNT & (0b11 << BITS_PLAYER_ID)) >> BITS_PLAYER_ID;
return response;
}
[[nodiscard]] static bool isMasterNode() { return !isBitHigh(BIT_SLAVE); }
[[nodiscard]] static bool allReady() { return isBitHigh(BIT_READY); }
[[nodiscard]] static bool hasError() { return isBitHigh(BIT_ERROR); }
[[nodiscard]] static bool isSending() { return isBitHigh(BIT_START); }
static void startTransfer() { setBitHigh(BIT_START); }
static void stopTransfer() { setBitLow(BIT_START); }
static void setInterruptsOn() { setBitHigh(BIT_IRQ); }
static void setInterruptsOff() { setBitLow(BIT_IRQ); }
// -------------
private:
BaudRate baudRate = BaudRate::BAUD_RATE_1;
AsyncState asyncState = IDLE;
@ -243,35 +270,14 @@ class LinkRawCable {
}
void setGeneralPurposeMode() {
Link::_REG_SIOMLT_SEND = 0;
Link::_REG_RCNT = (Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_LOW)) |
(1 << BIT_GENERAL_PURPOSE_HIGH);
}
void setData(u16 data) { Link::_REG_SIOMLT_SEND = data; }
Response getData() {
Response response = EMPTY_RESPONSE;
for (u32 i = 0; i < LINK_RAW_CABLE_MAX_PLAYERS; i++)
response.data[i] = Link::_REG_SIOMULTI[i];
response.playerId =
(Link::_REG_SIOCNT & (0b11 << BITS_PLAYER_ID)) >> BITS_PLAYER_ID;
return response;
}
bool hasError() { return isBitHigh(BIT_ERROR); }
bool isSending() { return isBitHigh(BIT_START); }
void startTransfer() { setBitHigh(BIT_START); }
void stopTransfer() { setBitLow(BIT_START); }
void setInterruptsOn() { setBitHigh(BIT_IRQ); }
void setInterruptsOff() { setBitLow(BIT_IRQ); }
bool isBitHigh(u8 bit) { return (Link::_REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { Link::_REG_SIOCNT |= 1 << bit; }
void setBitLow(u8 bit) { Link::_REG_SIOCNT &= ~(1 << bit); }
static bool isBitHigh(u8 bit) { return (Link::_REG_SIOCNT >> bit) & 1; }
static void setBitHigh(u8 bit) { Link::_REG_SIOCNT |= 1 << bit; }
static void setBitLow(u8 bit) { Link::_REG_SIOCNT &= ~(1 << bit); }
};
extern LinkRawCable* linkRawCable;

View File

@ -968,9 +968,9 @@ class LinkRawWireless {
u16 previousAdapterData = 0xffff;
};
SessionState sessionState;
LinkSPI* linkSPI = new LinkSPI();
LinkGPIO* linkGPIO = new LinkGPIO();
SessionState sessionState;
State state = NEEDS_RESET;
volatile bool isEnabled = false;

View File

@ -208,7 +208,11 @@ class LinkUniversal {
}
/**
* @brief Call this method every time you need to fetch new data.
* @brief Call this method whenever you need to fetch new data, but at least
* once per frame, as it also maintains the connection state. It does not
* **wait** for new messages; instead, it collects and queues any available
* messages from the interrupt world for later processing with the `read(...)`
* method.
*/
void sync() {
if (!isEnabled)

View File

@ -907,11 +907,6 @@ class LinkWireless {
*/
Config config;
/**
* @brief The internal `LinkRawWireless` instance.
*/
LinkRawWireless* linkRawWireless = new LinkRawWireless();
private:
using MessageQueue = Link::Queue<Message, LINK_WIRELESS_QUEUE_SIZE, false>;
@ -982,6 +977,7 @@ class LinkWireless {
bool isActive;
};
LinkRawWireless* linkRawWireless = new LinkRawWireless();
SessionState sessionState;
AsyncCommand asyncCommand;
u32 nextAsyncCommandData[LINK_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH];

View File

@ -215,10 +215,9 @@ class LinkWirelessMultiboot {
delete linkWirelessOpenSDK;
}
private:
LinkRawWireless* linkRawWireless = new LinkRawWireless();
LinkWirelessOpenSDK* linkWirelessOpenSDK = new LinkWirelessOpenSDK();
private:
MultibootProgress progress;
volatile bool readyFlag = false;
volatile Result lastResult;

View File

@ -213,11 +213,6 @@ u32 C_LinkWireless_nextPendingPacketId(C_LinkWirelessHandle handle) {
return static_cast<LinkWireless*>(handle)->_nextPendingPacketId();
}
C_LinkRawWirelessHandle C_LinkWireless_getLinkRawWireless(
C_LinkWirelessHandle handle) {
return static_cast<LinkWireless*>(handle)->linkRawWireless;
}
void C_LinkWireless_onVBlank(C_LinkWirelessHandle handle) {
static_cast<LinkWireless*>(handle)->_onVBlank();
}

View File

@ -6,7 +6,6 @@ extern "C" {
#endif
#include <tonc_core.h>
#include "C_LinkRawWireless.h"
typedef void* C_LinkWirelessHandle;
@ -137,9 +136,6 @@ u32 C_LinkWireless_lastConfirmationFromServer(C_LinkWirelessHandle handle);
u32 C_LinkWireless_lastPacketIdFromServer(C_LinkWirelessHandle handle);
u32 C_LinkWireless_nextPendingPacketId(C_LinkWirelessHandle handle);
C_LinkRawWirelessHandle C_LinkWireless_getLinkRawWireless(
C_LinkWirelessHandle handle);
void C_LinkWireless_onVBlank(C_LinkWirelessHandle handle);
void C_LinkWireless_onSerial(C_LinkWirelessHandle handle);
void C_LinkWireless_onTimer(C_LinkWirelessHandle handle);