Merge pull request #2 from rodri042/frame-count

Fix speed / stability issues
This commit is contained in:
Rodrigo Alfonso
2020-11-17 17:19:02 -03:00
committed by GitHub
6 changed files with 145 additions and 81 deletions

View File

@@ -13,7 +13,7 @@ All the complexity is abstracted in a single header file that exposes an easy-to
- Include [LinkConnection.h](lib/LinkConnection.h) in your game code, and read its comment with instructions.
- Check out the [examples](examples) folder
* Builds are available in *Releases*.
* They can be tested on real GBAs or with emulators (*NO$GBA*, *mGBA*, or *VBA-M*).
* They can be tested on real GBAs or with emulators (*NO$GBA* or *VBA-M*).
## Constructor options
@@ -21,10 +21,13 @@ All the complexity is abstracted in a single header file that exposes an easy-to
Name | Type | Default | Description
--- | --- | --- | ---
`startNow` | **bool** | `true` | Automatically starts serial communication. Otherwise, you'll need to call `linkConnection->activate()`.
`baudRate` | **BaudRate** | `BaudRate::BAUD_RATE_3` | Sets a specific baud rate.
`timeout` | **u32** | `3` | Number of frames without an `II_SERIAL` IRQ to reset the connection.
`bufferSize` | **u32** | `60` | Number of messages that the queues will be able to store.
`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** | `10` | Number of *messages* that the queues will be able to store.
`speed` | **u16** | `100` | Number of *1024cycles* (61.04μs) ticks between messages *(100 = 6,104ms)*. It's the interval of Timer #`sendTimerId`.
`sendTimerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending.
`waitTimerId` | **u8** *(0~3)* | `2` | GBA Timer to use for waiting.
## Makefile actions
@@ -140,4 +143,4 @@ Above is not counting the additional CPU time that must be spent on initiating a
> Fast One-Way Transmission
Beside for the actual SIO Multiplayer mode, you can also use SIO Normal mode for fast one-way data transfer from Master unit to all Child unit(s). See chapter about SIO Normal mode for details.
```
```

View File

@@ -19,6 +19,11 @@ void init() {
// (2) Add the interrupt service routines
irq_add(II_VBLANK, LINK_ISR_VBLANK);
irq_add(II_SERIAL, LINK_ISR_SERIAL);
irq_add(II_TIMER3, LINK_ISR_TIMER);
irq_add(II_TIMER2, NULL);
// (3) Initialize the library
linkConnection->activate();
}
int main() {
@@ -27,9 +32,9 @@ int main() {
u16 data[LINK_MAX_PLAYERS];
while (1) {
// (3) Send/read messages messages
// (4) Send/read messages messages
u16 keys = ~REG_KEYS & KEY_ANY;
u16 message = (keys << 1) | 1;
u16 message = keys + 1;
linkConnection->send(message);
auto linkState = linkConnection->linkState.get();
@@ -39,7 +44,7 @@ int main() {
for (u32 i = 0; i < linkState->playerCount; i++) {
while (linkState->hasMessage(i))
data[i] = linkState->readMessage(i) >> 1;
data[i] = linkState->readMessage(i) - 1;
output += "Player " + std::to_string(i) + ": " +
std::to_string(data[i]) + "\n";

View File

@@ -9,7 +9,7 @@ void setUpInterrupts();
void printTutorial();
static std::shared_ptr<GBAEngine> engine{new GBAEngine()};
static std::unique_ptr<TestScene> testScene{new TestScene(engine)};
LinkConnection* linkConnection = new LinkConnection(false);
LinkConnection* linkConnection = new LinkConnection();
int main() {
setUpInterrupts();
@@ -57,11 +57,11 @@ inline void ISR_reset() {
inline void setUpInterrupts() {
irq_init(NULL);
// VBlank
// LinkConnection
irq_add(II_VBLANK, LINK_ISR_VBLANK);
// Link connection
irq_add(II_SERIAL, LINK_ISR_SERIAL);
irq_add(II_TIMER3, LINK_ISR_TIMER);
irq_add(II_TIMER2, NULL);
// A+B+START+SELECT
REG_KEYCNT = 0b1100000000001111;
@@ -74,10 +74,10 @@ void printTutorial() {
DEBULOG("START: turn on connection");
DEBULOG("(on connection, p1 sends 999)");
DEBULOG("");
DEBULOG("A: send 555 once per frame");
DEBULOG("B: send counter once");
DEBULOG("L: send 1, then 2");
DEBULOG("R: send 43981, then 257");
DEBULOG("A: send counter++ (once)");
DEBULOG("B: send counter++ (cont)");
DEBULOG("L: send counter++ twice (once)");
DEBULOG("R: send counter++ twice (cont)");
DEBULOG("SELECT: force lag (9k lines)");
DEBULOG("DOWN: turn off connection");
DEBULOG("");

View File

@@ -42,6 +42,8 @@ void TestScene::tick(u16 keys) {
if (engine->isTransitioning())
return;
frameCounter++;
// check keys
aHandler->setIsPressed(keys & KEY_A);
bHandler->setIsPressed(keys & KEY_B);
@@ -67,24 +69,21 @@ void TestScene::tick(u16 keys) {
// determine which value should be sent
u16 value = LINK_NO_DATA;
if (!initialized && linkConnection->linkState->currentPlayerId == 1) {
if (!initialized && linkState->currentPlayerId == 1) {
initialized = true;
value = 999;
}
if (aHandler->getIsPressed())
value = 555;
if (bHandler->hasBeenPressedNow()) {
if (aHandler->getIsPressed() || bHandler->hasBeenPressedNow()) {
counter++;
value = counter;
}
// send data
if (lHandler->hasBeenPressedNow()) {
send(1);
send(2);
} else if (rHandler->hasBeenPressedNow()) {
send(43981);
send(257);
if (rHandler->getIsPressed() || lHandler->hasBeenPressedNow()) {
counter++;
send(counter);
counter++;
send(counter);
} else if (value != LINK_NO_DATA)
send(value);
@@ -93,7 +92,8 @@ void TestScene::tick(u16 keys) {
for (u32 i = 0; i < linkState->playerCount; i++)
while (linkState->hasMessage(i)) {
u16 message = linkState->readMessage(i);
if (i != linkState->currentPlayerId)
DEBULOG("<-p" + asStr(i) + ": " + asStr(message));
if (i != linkState->currentPlayerId && message != 10000)
DEBULOG("<-p" + asStr(i) + ": " + asStr(message) + " (frame " +
asStr(frameCounter) + ")");
}
}

View File

@@ -23,6 +23,7 @@ class TestScene : public Scene {
u32 counter = 0;
bool isConnected = false;
bool initialized = false;
u32 frameCounter = 0;
void log(std::string text);
};

View File

@@ -1,6 +1,7 @@
#ifndef LINK_CONNECTION_H
#define LINK_CONNECTION_H
#include <tonc_bios.h>
#include <tonc_core.h>
#include <tonc_memdef.h>
#include <tonc_memmap.h>
@@ -11,9 +12,14 @@
#define LINK_MAX_PLAYERS 4
#define LINK_DISCONNECTED 0xFFFF
#define LINK_NO_DATA 0x0
#define LINK_TRANSFER_VCOUNT_WAIT 2
#define LINK_DEFAULT_TIMEOUT 3
#define LINK_DEFAULT_BUFFER_SIZE 60
#define LINK_DEFAULT_REMOTE_TIMEOUT 5
#define LINK_DEFAULT_BUFFER_SIZE 10
#define LINK_DEFAULT_SPEED 100
#define LINK_DEFAULT_SEND_TIMER_ID 3
#define LINK_DEFAULT_WAIT_TIMER_ID 2
#define LINK_TRANSFER_WAIT_CYCLES 1000
#define LINK_BASE_FREQUENCY TM_FREQ_1024
#define LINK_BIT_SLAVE 2
#define LINK_BIT_READY 3
#define LINK_BITS_PLAYER_ID 4
@@ -31,10 +37,15 @@
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkConnection* linkConnection = new LinkConnection();
// - 2) Add the interrupt service routines:
// - 2) Add the required interrupt service routines:
// irq_init(NULL);
// irq_add(II_VBLANK, LINK_ISR_VBLANK);
// irq_add(II_SERIAL, LINK_ISR_SERIAL);
// - 3) Send/read messages by using:
// irq_add(II_TIMER3, LINK_ISR_TIMER);
// irq_add(II_TIMER2, NULL);
// - 3) Initialize the library with:
// linkConnection->activate();
// - 4) Send/read messages by using:
// linkConnection->send(...);
// linkConnection->linkState
@@ -43,15 +54,19 @@
// (they mean 'disconnected' and 'no data' respectively)
void LINK_ISR_VBLANK();
void LINK_ISR_TIMER();
void LINK_ISR_SERIAL();
u16 LINK_QUEUE_POP(std::queue<u16>& q);
void LINK_QUEUE_CLEAR(std::queue<u16>& q);
const u16 LINK_TIMER_IRQ_IDS[] = {IRQ_TIMER0, IRQ_TIMER1, IRQ_TIMER2,
IRQ_TIMER3};
struct LinkState {
u8 playerCount;
u8 currentPlayerId;
std::queue<u16> _incomingMessages[LINK_MAX_PLAYERS];
std::queue<u16> _outgoingMessages;
int _timeouts[LINK_MAX_PLAYERS];
bool _IRQFlag;
u32 _IRQTimeout;
@@ -81,18 +96,22 @@ class LinkConnection {
};
std::unique_ptr<struct LinkState> linkState{new LinkState()};
explicit LinkConnection(bool startNow = true,
BaudRate baudRate = BAUD_RATE_3,
explicit LinkConnection(BaudRate baudRate = BAUD_RATE_3,
u32 timeout = LINK_DEFAULT_TIMEOUT,
u32 bufferSize = LINK_DEFAULT_BUFFER_SIZE) {
u32 remoteTimeout = LINK_DEFAULT_REMOTE_TIMEOUT,
u32 bufferSize = LINK_DEFAULT_BUFFER_SIZE,
u16 speed = LINK_DEFAULT_SPEED,
u8 sendTimerId = LINK_DEFAULT_SEND_TIMER_ID,
u8 waitTimerId = LINK_DEFAULT_WAIT_TIMER_ID) {
this->baudRate = baudRate;
this->timeout = timeout;
this->remoteTimeout = remoteTimeout;
this->bufferSize = bufferSize;
this->speed = speed;
this->sendTimerId = sendTimerId;
this->waitTimerId = waitTimerId;
if (startNow)
activate();
else
stop();
stop();
}
bool isActive() { return isEnabled; }
@@ -115,77 +134,100 @@ class LinkConnection {
push(linkState->_outgoingMessages, data);
}
bool isReady() {
return isBitHigh(LINK_BIT_READY) && !isBitHigh(LINK_BIT_ERROR);
}
void _onVBlank() {
if (!isEnabled || resetIfNeeded())
if (!isEnabled)
return;
if (!linkState->_IRQFlag) {
if (!linkState->_IRQFlag)
linkState->_IRQTimeout++;
if (linkState->_IRQTimeout >= timeout)
reset();
else if (isMaster())
transfer(LINK_NO_DATA, true);
}
linkState->_IRQFlag = false;
}
void _onTimer() {
if (!isEnabled)
return;
if (didTimeout()) {
reset();
return;
}
if (isMaster() && isReady() && !isSending())
sendPendingData();
}
void _onSerial() {
if (!isEnabled || resetIfNeeded())
if (!isEnabled)
return;
wait();
if (resetIfNeeded())
return;
linkState->_IRQFlag = true;
linkState->_IRQTimeout = 0;
linkState->playerCount = 0;
linkState->currentPlayerId =
(REG_SIOCNT & (0b11 << LINK_BITS_PLAYER_ID)) >> LINK_BITS_PLAYER_ID;
u8 newPlayerCount = 0;
for (u32 i = 0; i < LINK_MAX_PLAYERS; i++) {
u16 data = REG_SIOMULTI[i];
if (data != LINK_DISCONNECTED) {
if (data != LINK_NO_DATA)
push(linkState->_incomingMessages[i], data);
linkState->playerCount++;
} else
LINK_QUEUE_CLEAR(linkState->_incomingMessages[i]);
newPlayerCount++;
linkState->_timeouts[i] = 0;
} else if (linkState->_timeouts[i] > 0) {
linkState->_timeouts[i]++;
if (linkState->_timeouts[i] >= (int)remoteTimeout) {
LINK_QUEUE_CLEAR(linkState->_incomingMessages[i]);
linkState->_timeouts[i] = -1;
} else
newPlayerCount++;
}
}
if (linkState->isConnected())
linkState->playerCount = newPlayerCount;
linkState->currentPlayerId =
(REG_SIOCNT & (0b11 << LINK_BITS_PLAYER_ID)) >> LINK_BITS_PLAYER_ID;
if (!isMaster())
sendPendingData();
}
private:
BaudRate baudRate;
u32 timeout;
u32 remoteTimeout;
u32 bufferSize;
u32 speed;
u8 sendTimerId;
u8 waitTimerId;
bool isEnabled = false;
bool isReady() { return isBitHigh(LINK_BIT_READY); }
bool hasError() { return isBitHigh(LINK_BIT_ERROR); }
bool isMaster() { return !isBitHigh(LINK_BIT_SLAVE); }
bool isSending() { return isBitHigh(LINK_BIT_START); }
bool didTimeout() { return linkState->_IRQTimeout >= timeout; }
void sendPendingData() {
transfer(LINK_QUEUE_POP(linkState->_outgoingMessages));
}
void transfer(u16 data, bool force = false) {
bool shouldNotify = isMaster() && (data != LINK_NO_DATA || force);
if (shouldNotify)
setBitLow(LINK_BIT_START);
wait(LINK_TRANSFER_VCOUNT_WAIT);
void transfer(u16 data) {
REG_SIOMLT_SEND = data;
if (shouldNotify)
if (isMaster()) {
wait();
setBitHigh(LINK_BIT_START);
}
}
bool resetIfNeeded() {
if (!isReady()) {
if (!isReady() || hasError()) {
reset();
return true;
}
@@ -202,19 +244,25 @@ class LinkConnection {
void resetState() {
linkState->playerCount = 0;
linkState->currentPlayerId = 0;
for (u32 i = 0; i < LINK_MAX_PLAYERS; i++)
for (u32 i = 0; i < LINK_MAX_PLAYERS; i++) {
LINK_QUEUE_CLEAR(linkState->_incomingMessages[i]);
linkState->_timeouts[i] = -1;
}
LINK_QUEUE_CLEAR(linkState->_outgoingMessages);
linkState->_IRQFlag = false;
linkState->_IRQTimeout = 0;
}
void stop() {
stopTimer();
LINK_SET_LOW(REG_RCNT, LINK_BIT_GENERAL_PURPOSE_LOW);
LINK_SET_HIGH(REG_RCNT, LINK_BIT_GENERAL_PURPOSE_HIGH);
}
void start() {
startTimer();
LINK_SET_LOW(REG_RCNT, LINK_BIT_GENERAL_PURPOSE_HIGH);
REG_SIOCNT = baudRate;
REG_SIOMLT_SEND = 0;
@@ -222,6 +270,15 @@ class LinkConnection {
setBitHigh(LINK_BIT_IRQ);
}
void stopTimer() {
REG_TM[sendTimerId].cnt = REG_TM[sendTimerId].cnt & (~TM_ENABLE);
}
void startTimer() {
REG_TM[sendTimerId].start = -speed;
REG_TM[sendTimerId].cnt = TM_ENABLE | TM_IRQ | LINK_BASE_FREQUENCY;
}
void push(std::queue<u16>& q, u16 value) {
if (q.size() >= bufferSize)
LINK_QUEUE_POP(q);
@@ -229,19 +286,13 @@ class LinkConnection {
q.push(value);
}
void wait(u32 verticalLines) {
u32 lines = 0;
u32 vCount = REG_VCOUNT;
while (lines < verticalLines) {
if (REG_VCOUNT != vCount) {
lines++;
vCount = REG_VCOUNT;
}
};
void wait() {
REG_TM[waitTimerId].start = -LINK_TRANSFER_WAIT_CYCLES;
REG_TM[waitTimerId].cnt = TM_ENABLE | TM_IRQ | TM_FREQ_1;
IntrWait(1, LINK_TIMER_IRQ_IDS[waitTimerId]);
REG_TM[waitTimerId].cnt = 0;
}
bool isMaster() { return !isBitHigh(LINK_BIT_SLAVE); }
bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { LINK_SET_HIGH(REG_SIOCNT, bit); }
void setBitLow(u8 bit) { LINK_SET_LOW(REG_SIOCNT, bit); }
@@ -253,6 +304,10 @@ inline void LINK_ISR_VBLANK() {
linkConnection->_onVBlank();
}
inline void LINK_ISR_TIMER() {
linkConnection->_onTimer();
}
inline void LINK_ISR_SERIAL() {
linkConnection->_onSerial();
}