Skip to content

Commit 4775d76

Browse files
committed
Detect erratic tick rate.
1 parent 419eeab commit 4775d76

File tree

13 files changed

+155
-33
lines changed

13 files changed

+155
-33
lines changed

ClientSource/Connection/BotBase.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class BotBaseController{
3131

3232
public:
3333
virtual ~BotBaseController() = default;
34+
virtual void stop(std::string error_message = "") = 0;
3435

3536
virtual Logger& logger() = 0;
3637
virtual State state() const = 0;

ClientSource/Connection/PABotBase.cpp

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ void PABotBase::connect(){
9191
SerialPABotBase::DeviceRequest_seqnum_reset(), nullptr
9292
).convert<PABB_MSG_ACK_REQUEST>(logger(), response);
9393
}
94-
void PABotBase::stop(){
94+
void PABotBase::stop(std::string error_message){
9595
auto scope_check = m_sanitizer.check_scope();
9696

9797
// Make sure only one thread can get in here.
@@ -100,6 +100,11 @@ void PABotBase::stop(){
100100
return;
101101
}
102102

103+
if (!error_message.empty()){
104+
ReadSpinLock lg(m_state_lock);
105+
m_error_message = std::move(error_message);
106+
}
107+
103108
// Wake everyone up.
104109
{
105110
std::lock_guard<std::mutex> lg(m_sleep_lock);
@@ -149,7 +154,8 @@ void PABotBase::wait_for_all_requests(const Cancellable* cancelled){
149154
throw OperationCancelledException();
150155
}
151156
if (m_state.load(std::memory_order_acquire) != State::RUNNING){
152-
throw InvalidConnectionStateException();
157+
ReadSpinLock lg0(m_state_lock);
158+
throw InvalidConnectionStateException(m_error_message);
153159
}
154160
{
155161
ReadSpinLock lg1(m_state_lock, "PABotBase::wait_for_all_requests()");
@@ -488,8 +494,11 @@ void PABotBase::on_recv_message(BotBaseMessage message){
488494
return;
489495
}
490496
const pabb_MsgInfoInvalidType* params = (const pabb_MsgInfoInvalidType*)message.body.c_str();
491-
m_error_message = "PABotBase incompatibility. Device does not recognize message type: " + std::to_string(params->type);
492-
m_logger.log(m_error_message, COLOR_RED);
497+
{
498+
WriteSpinLock lg(m_state_lock);
499+
m_error_message = "PABotBase incompatibility. Device does not recognize message type: " + std::to_string(params->type);
500+
m_logger.log(m_error_message, COLOR_RED);
501+
}
493502
m_error.store(true, std::memory_order_release);
494503
std::lock_guard<std::mutex> lg0(m_sleep_lock);
495504
m_cv.notify_all();
@@ -501,8 +510,11 @@ void PABotBase::on_recv_message(BotBaseMessage message){
501510
}
502511
const pabb_MsgInfoMissedRequest* params = (const pabb_MsgInfoMissedRequest*)message.body.c_str();
503512
if (params->seqnum == 1){
504-
m_error_message = "Serial connection has been interrupted.";
505-
m_logger.log(m_error_message, COLOR_RED);
513+
{
514+
WriteSpinLock lg(m_state_lock);
515+
m_error_message = "Serial connection has been interrupted.";
516+
m_logger.log(m_error_message, COLOR_RED);
517+
}
506518
m_error.store(true, std::memory_order_release);
507519
std::lock_guard<std::mutex> lg0(m_sleep_lock);
508520
m_cv.notify_all();
@@ -511,7 +523,10 @@ void PABotBase::on_recv_message(BotBaseMessage message){
511523
}
512524
case PABB_MSG_ERROR_DISCONNECTED:{
513525
m_logger.log("The console has disconnected the controller.", COLOR_RED);
514-
m_error_message = "Disconnected by console.";
526+
{
527+
WriteSpinLock lg(m_state_lock);
528+
m_error_message = "Disconnected by console.";
529+
}
515530
m_error.store(true, std::memory_order_release);
516531
std::lock_guard<std::mutex> lg0(m_sleep_lock);
517532
m_cv.notify_all();
@@ -630,7 +645,7 @@ uint64_t PABotBase::try_issue_request(
630645

631646
State state = m_state.load(std::memory_order_acquire);
632647
if (state != State::RUNNING){
633-
throw InvalidConnectionStateException();
648+
throw InvalidConnectionStateException(m_error_message);
634649
}
635650
if (m_error.load(std::memory_order_acquire)){
636651
throw ConnectionException(&m_logger, m_error_message);
@@ -710,7 +725,7 @@ uint64_t PABotBase::try_issue_command(
710725

711726
State state = m_state.load(std::memory_order_acquire);
712727
if (state != State::RUNNING){
713-
throw InvalidConnectionStateException();
728+
throw InvalidConnectionStateException(m_error_message);
714729
}
715730
if (m_error.load(std::memory_order_acquire)){
716731
throw ConnectionException(&m_logger, m_error_message);
@@ -803,9 +818,11 @@ uint64_t PABotBase::issue_request(
803818
throw OperationCancelledException();
804819
}
805820
if (m_state.load(std::memory_order_acquire) != State::RUNNING){
806-
throw InvalidConnectionStateException();
821+
ReadSpinLock lg0(m_state_lock);
822+
throw InvalidConnectionStateException(m_error_message);
807823
}
808824
if (m_error.load(std::memory_order_acquire)){
825+
ReadSpinLock lg0(m_state_lock);
809826
throw ConnectionException(&m_logger, m_error_message);
810827
}
811828
m_cv.wait(lg);
@@ -846,9 +863,11 @@ uint64_t PABotBase::issue_command(
846863
throw OperationCancelledException();
847864
}
848865
if (m_state.load(std::memory_order_acquire) != State::RUNNING){
849-
throw InvalidConnectionStateException();
866+
ReadSpinLock lg0(m_state_lock);
867+
throw InvalidConnectionStateException(m_error_message);
850868
}
851869
if (m_error.load(std::memory_order_acquire)){
870+
ReadSpinLock lg0(m_state_lock);
852871
throw ConnectionException(&m_logger, m_error_message);
853872
}
854873
m_cv.wait(lg);
@@ -914,7 +933,7 @@ BotBaseMessage PABotBase::wait_for_request(uint64_t seqnum, const Cancellable* c
914933
if (state != State::RUNNING){
915934
m_pending_requests.erase(iter);
916935
m_cv.notify_all();
917-
throw InvalidConnectionStateException();
936+
throw InvalidConnectionStateException(m_error_message);
918937
}
919938
if (m_error.load(std::memory_order_acquire)){
920939
m_pending_requests.erase(iter);

ClientSource/Connection/PABotBase.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class PABotBase : public BotBaseController, private PABotBaseConnection{
5656
using PABotBaseConnection::set_sniffer;
5757

5858
void connect();
59-
void stop();
59+
virtual void stop(std::string error_message = "") override;
6060

6161
std::chrono::time_point<std::chrono::system_clock> last_ack() const{
6262
return m_last_ack.load(std::memory_order_acquire);

Common/Cpp/Containers/CircularBuffer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class CircularBuffer{
3232
void clear() noexcept;
3333

3434
size_t empty() const{ return m_front == m_back; }
35+
size_t full() const{ return m_back - m_front >= m_capacity; }
3536
size_t size() const{ return m_back - m_front; }
3637

3738
const Object& front() const;

Common/Cpp/Exceptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ class ProgramCancelledException : public Exception{
5454
// - Non-infra are allowed to catch and rethrow this exception.
5555
class InvalidConnectionStateException : public Exception{
5656
public:
57+
InvalidConnectionStateException(std::string message) : m_message(std::move(message)) {}
5758
virtual const char* name() const override{ return "InvalidConnectionStateException"; }
59+
virtual std::string message() const override{ return m_message; }
60+
protected:
61+
std::string m_message;
5862
};
5963

6064

SerialPrograms/Source/NintendoSwitch/Controllers/SerialPABotBase/NintendoSwitch_SerialPABotBase_Controller.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ SerialPABotBase_Controller::SerialPABotBase_Controller(
5656
void SerialPABotBase_Controller::cancel_all_commands(){
5757
std::lock_guard<std::mutex> lg(m_state_lock);
5858
if (!is_ready()){
59-
throw InvalidConnectionStateException();
59+
throw InvalidConnectionStateException(error_string());
6060
}
6161
m_serial->stop_all_commands();
6262
this->clear_on_next();
6363
}
6464
void SerialPABotBase_Controller::replace_on_next_command(){
6565
std::lock_guard<std::mutex> lg(m_state_lock);
6666
if (!is_ready()){
67-
throw InvalidConnectionStateException();
67+
throw InvalidConnectionStateException(error_string());
6868
}
6969
m_serial->next_command_interrupt();
7070
this->clear_on_next();
@@ -82,7 +82,7 @@ void SerialPABotBase_Controller::wait_for_all(const Cancellable* cancellable){
8282
}
8383

8484
if (!is_ready()){
85-
throw InvalidConnectionStateException();
85+
throw InvalidConnectionStateException(error_string());
8686
}
8787

8888
this->issue_wait_for_all(cancellable);

SerialPrograms/Source/NintendoSwitch/Controllers/SerialPABotBase/NintendoSwitch_SerialPABotBase_Controller.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,22 @@ class SerialPABotBase_Controller : public ControllerWithScheduler{
2323
SerialPABotBase::SerialPABotBase_Connection& connection
2424
);
2525

26+
void stop_with_error(std::string error_message){
27+
{
28+
WriteSpinLock lg(m_error_lock);
29+
m_error_string = error_message;
30+
}
31+
m_serial->stop(std::move(error_message));
32+
}
33+
2634
bool is_ready() const{
27-
return m_serial && m_handle.is_ready();
35+
return m_serial
36+
&& m_serial->state() == BotBaseController::State::RUNNING
37+
&& m_handle.is_ready();
38+
}
39+
std::string error_string() const{
40+
ReadSpinLock lg(m_error_lock);
41+
return m_error_string;
2842
}
2943

3044

@@ -41,6 +55,9 @@ class SerialPABotBase_Controller : public ControllerWithScheduler{
4155
SerialPABotBase::SerialPABotBase_Connection& m_handle;
4256
BotBaseController* m_serial;
4357
ControllerFeatures m_supported_features;
58+
59+
mutable SpinLock m_error_lock;
60+
std::string m_error_string;
4461
};
4562

4663

SerialPrograms/Source/NintendoSwitch/Controllers/SerialPABotBase/NintendoSwitch_SerialPABotBase_PokkenController.cpp

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ void SerialPABotBase_PokkenController::push_state(const Cancellable* cancellable
6666
// Must be called inside "m_state_lock".
6767

6868
if (!is_ready()){
69-
throw InvalidConnectionStateException();
69+
throw InvalidConnectionStateException(error_string());
7070
}
7171

7272
int dpad_x = 0;
@@ -248,6 +248,67 @@ class ExtendedLengthCounter{
248248

249249

250250

251+
class TickRateTracker{
252+
public:
253+
TickRateTracker(double expected_ticks_per_second)
254+
: m_expected_ticks_per_second(expected_ticks_per_second)
255+
// , m_history(10)
256+
{}
257+
258+
259+
double push_ticks(uint64_t ticks){
260+
WallClock now = current_time();
261+
262+
// if (m_history.full()){
263+
// m_history.pop_front();
264+
// }
265+
266+
if (ticks <= m_last_ticks){
267+
m_last_push = WallClock::min();
268+
m_last_ticks = 0;
269+
m_consecutive_off = 0;
270+
}
271+
272+
double ticks_per_second = 0;
273+
274+
if (m_last_push != WallClock::min()){
275+
uint64_t elapsed_ticks = ticks - m_last_ticks;
276+
double elapsed_seconds = std::chrono::duration_cast<std::chrono::microseconds>(now - m_last_push).count() * 0.000001;
277+
ticks_per_second = elapsed_ticks / elapsed_seconds;
278+
// m_history.push_back(ticks_per_second);
279+
280+
double rate_error = std::abs(ticks_per_second - m_expected_ticks_per_second) / m_expected_ticks_per_second;
281+
if (rate_error > 0.1){
282+
m_consecutive_off++;
283+
}else{
284+
m_consecutive_off = 0;
285+
}
286+
287+
}
288+
289+
m_last_push = now;
290+
m_last_ticks = ticks;
291+
292+
return ticks_per_second;
293+
}
294+
295+
size_t consecutive_off_readings() const{
296+
return m_consecutive_off;
297+
}
298+
299+
300+
private:
301+
double m_expected_ticks_per_second;
302+
WallClock m_last_push = WallClock::min();
303+
uint64_t m_last_ticks = 0;
304+
305+
size_t m_consecutive_off = 0;
306+
307+
// CircularBuffer<double> m_history;
308+
};
309+
310+
311+
251312
void SerialPABotBase_PokkenController::status_thread(){
252313
constexpr std::chrono::milliseconds PERIOD(1000);
253314
std::atomic<WallClock> last_ack(current_time());
@@ -261,7 +322,7 @@ void SerialPABotBase_PokkenController::status_thread(){
261322

262323
auto last = current_time() - last_ack.load(std::memory_order_relaxed);
263324
std::chrono::duration<double> seconds = last;
264-
if (last > 2 * PERIOD){
325+
if (last > 2 * PERIOD && is_ready()){
265326
std::string text = "Last Ack: " + tostr_fixed(seconds.count(), 3) + " seconds ago";
266327
m_handle.set_status_line1(text, COLOR_RED);
267328
// m_logger.log("Connection issue detected. Turning on all logging...");
@@ -284,6 +345,7 @@ void SerialPABotBase_PokkenController::status_thread(){
284345

285346

286347
ExtendedLengthCounter clock_tracker;
348+
TickRateTracker tick_rate_tracker(m_use_milliseconds ? 1000 : TICKS_PER_SECOND);
287349
WallClock next_ping = current_time();
288350
while (true){
289351
if (m_stopping.load(std::memory_order_relaxed) || !m_handle.is_ready()){
@@ -299,6 +361,7 @@ void SerialPABotBase_PokkenController::status_thread(){
299361
).convert<PABB_MSG_ACK_REQUEST_I32>(logger(), response);
300362
last_ack.store(current_time(), std::memory_order_relaxed);
301363
uint64_t wallclock = clock_tracker.push_short_value(response.data);
364+
double ticks_per_second = tick_rate_tracker.push_ticks(wallclock);
302365

303366
if (wallclock == 0){
304367
m_handle.set_status_line1(
@@ -307,15 +370,17 @@ void SerialPABotBase_PokkenController::status_thread(){
307370
);
308371
}else{
309372
m_handle.set_status_line1(
310-
"Up Time: " + ticks_to_time(
311-
m_use_milliseconds
312-
? 1000
313-
: NintendoSwitch::TICKS_PER_SECOND,
314-
wallclock
315-
),
316-
theme_friendly_darkblue()
373+
"Ticks / Second: " + tostr_fixed(ticks_per_second, 3),
374+
tick_rate_tracker.consecutive_off_readings() == 0
375+
? theme_friendly_darkblue()
376+
: COLOR_RED
317377
);
318378
}
379+
380+
if (tick_rate_tracker.consecutive_off_readings() >= 10){
381+
error = "Tick rate is erratic. Arduino/Teensy is not reliable on Switch 2.";
382+
}
383+
319384
}catch (OperationCancelledException&){
320385
break;
321386
}catch (InvalidConnectionStateException&){
@@ -329,6 +394,7 @@ void SerialPABotBase_PokkenController::status_thread(){
329394
}
330395
if (!error.empty()){
331396
m_handle.set_status_line1(error, COLOR_RED);
397+
stop_with_error(std::move(error));
332398
}
333399

334400
// cout << "lock()" << endl;

SerialPrograms/Source/NintendoSwitch/Controllers/SysbotBase/SysbotBase3_ProController.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const ControllerFeatures& ProController_SysbotBase3::controller_features() const
4848
void ProController_SysbotBase3::cancel_all_commands(){
4949
std::lock_guard<std::mutex> lg(m_state_lock);
5050
if (m_stopping){
51-
throw InvalidConnectionStateException();
51+
throw InvalidConnectionStateException("");
5252
}
5353

5454
uint64_t queued = m_next_seqnum - m_next_expected_seqnum_ack;
@@ -63,7 +63,7 @@ void ProController_SysbotBase3::cancel_all_commands(){
6363
void ProController_SysbotBase3::replace_on_next_command(){
6464
std::lock_guard<std::mutex> lg(m_state_lock);
6565
if (m_stopping){
66-
throw InvalidConnectionStateException();
66+
throw InvalidConnectionStateException("");
6767
}
6868

6969
uint64_t queued = m_next_seqnum - m_next_expected_seqnum_ack;
@@ -79,7 +79,7 @@ void ProController_SysbotBase3::wait_for_all(const Cancellable* cancellable){
7979
std::unique_lock<std::mutex> lg(m_state_lock);
8080
while (true){
8181
if (m_stopping){
82-
throw InvalidConnectionStateException();
82+
throw InvalidConnectionStateException("");
8383
}
8484
if (cancellable){
8585
cancellable->throw_if_cancelled();
@@ -144,7 +144,7 @@ void ProController_SysbotBase3::push_state(const Cancellable* cancellable, WallD
144144
cancellable->throw_if_cancelled();
145145
}
146146
if (m_stopping){
147-
throw InvalidConnectionStateException();
147+
throw InvalidConnectionStateException("");
148148
}
149149

150150
// Flags map to: https://github.com/switchbrew/libnx/blob/master/nx/include/switch/services/hid.h#L584

0 commit comments

Comments
 (0)