mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-04-25 08:07:59 -05:00
LinkCable: Refactoring to use LinkRawCable internally
This commit is contained in:
parent
107624e68f
commit
8ec893b0ff
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user