From a2db6ddea563fbd2f85cb98f2fab4c8f32e74019 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Sat, 25 Jul 2020 23:00:29 -0400 Subject: [PATCH 1/5] Add link to Snap releases. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c081df62..8f27e6a83 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Clock Signal Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve. -[Releases](https://github.com/TomHarte/CLK/releases) are hosted on GitHub. +macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal). On the Mac it is a native Cocoa application; under Linux, BSD and other UNIXes and UNIX-alikes it can be built either with Qt or with SDL; the Qt build should be considered preliminary and is currently closely bound to X11 as Qt doesn't abstract game-like keyboard handling. From 7c05b1788e73e5ffa59be70e1af6609f7baa2593 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jul 2020 20:25:52 -0400 Subject: [PATCH 2/5] Ensures proper thread confinement for `updateStatusBarText`. --- OSBindings/Qt/mainwindow.cpp | 11 ++++++----- OSBindings/Qt/mainwindow.h | 6 +++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 3afe5e70e..8286fe45f 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -1338,24 +1338,25 @@ void MainWindow::addActivityObserver() { } void MainWindow::register_led(const std::string &name) { + std::lock_guard guard(ledStatusesLock); ledStatuses[name] = false; - updateStatusBarText(); + QMetaObject::invokeMethod(this, "updateStatusBarText"); } void MainWindow::set_led_status(const std::string &name, bool isLit) { + std::lock_guard guard(ledStatusesLock); ledStatuses[name] = isLit; - updateStatusBarText(); // Assumption here: Qt's attempt at automatic thread confinement will work here. + QMetaObject::invokeMethod(this, "updateStatusBarText"); } void MainWindow::updateStatusBarText() { QString fullText; - bool isFirst = true; + std::lock_guard guard(ledStatusesLock); for(const auto &pair: ledStatuses) { - if(!isFirst) fullText += " | "; + if(!fullText.isEmpty()) fullText += " | "; fullText += QString::fromStdString(pair.first); fullText += " "; fullText += pair.second ? "■" : "□"; - isFirst = false; } statusBar()->showMessage(fullText); } diff --git a/OSBindings/Qt/mainwindow.h b/OSBindings/Qt/mainwindow.h index 4bbe4d108..91f3cab2d 100644 --- a/OSBindings/Qt/mainwindow.h +++ b/OSBindings/Qt/mainwindow.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "audiobuffer.h" @@ -80,6 +81,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat private slots: void startMachine(); + void updateStatusBarText(); private: void start_appleII(); @@ -144,9 +146,11 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat void register_led(const std::string &) override; void set_led_status(const std::string &, bool) override; + + std::recursive_mutex ledStatusesLock; std::map ledStatuses; + void addActivityObserver(); - void updateStatusBarText(); }; #endif // MAINWINDOW_H From 71c3f58c9922e957768199fedf027651a032a575 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jul 2020 20:40:38 -0400 Subject: [PATCH 3/5] Provides user feedback upon improper command-line usage. --- OSBindings/Qt/mainwindow.cpp | 12 ++++++++++-- OSBindings/Qt/mainwindow.h | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 8286fe45f..8a85b99dc 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -45,7 +45,9 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { MainWindow::MainWindow(const QString &fileName) { init(); - launchFile(fileName); + if(!launchFile(fileName)) { + setUIPhase(UIPhase::SelectingMachine); + } } void MainWindow::deleteMachine() { @@ -210,11 +212,17 @@ void MainWindow::insertFile(const QString &fileName) { mediaTarget->insert_media(media); } -void MainWindow::launchFile(const QString &fileName) { +bool MainWindow::launchFile(const QString &fileName) { targets = Analyser::Static::GetTargets(fileName.toStdString()); if(!targets.empty()) { openFileName = QFileInfo(fileName).fileName(); launchMachine(); + return true; + } else { + QMessageBox msgBox; + msgBox.setText("Unable to open file: " + fileName); + msgBox.exec(); + return false; } } diff --git a/OSBindings/Qt/mainwindow.h b/OSBindings/Qt/mainwindow.h index 91f3cab2d..22be7e739 100644 --- a/OSBindings/Qt/mainwindow.h +++ b/OSBindings/Qt/mainwindow.h @@ -102,7 +102,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat QAction *insertAction = nullptr; void insertFile(const QString &fileName); - void launchFile(const QString &fileName); + bool launchFile(const QString &fileName); void launchTarget(std::unique_ptr &&); void restoreSelections(); From db8e1b0edfee096f9e563d15d2075075b0417ad0 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jul 2020 20:45:47 -0400 Subject: [PATCH 4/5] Adds feedback on unidentified ROMs. --- OSBindings/Qt/mainwindow.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 8a85b99dc..2d0882f27 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -715,6 +715,7 @@ void MainWindow::dropEvent(QDropEvent* event) { bool foundROM = false; const auto appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation).toStdString(); + QString unusedRoms; for(const auto &url: event->mimeData()->urls()) { const char *const name = url.toLocalFile().toUtf8(); FILE *const file = fopen(name, "rb"); @@ -724,6 +725,7 @@ void MainWindow::dropEvent(QDropEvent* event) { CRC::CRC32 generator; const uint32_t crc = generator.compute_crc(*contents); + bool wasUsed = false; for(const auto &rom: missingRoms) { if(std::find(rom.crc32s.begin(), rom.crc32s.end(), crc) != rom.crc32s.end()) { foundROM = true; @@ -739,10 +741,22 @@ void MainWindow::dropEvent(QDropEvent* event) { FILE *const target = fopen(destination.c_str(), "wb"); fwrite(contents->data(), 1, contents->size(), target); fclose(target); + + wasUsed = true; } } + + if(!wasUsed) { + if(!unusedRoms.isEmpty()) unusedRoms += ", "; + unusedRoms += url.fileName(); + } } + if(!unusedRoms.isEmpty()) { + QMessageBox msgBox; + msgBox.setText("Couldn't identify ROMs: " + unusedRoms); + msgBox.exec(); + } if(foundROM) launchMachine(); } break; } From 3db4a8c312cd6bfe63d90255eacdbb16185b5566 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Mon, 27 Jul 2020 21:08:00 -0400 Subject: [PATCH 5/5] Attempts to improve vsync deadline estimation. Also increases probability of bad estimation being discarded. --- ClockReceiver/VSyncPredictor.hpp | 16 ++++++++++------ OSBindings/Qt/scantargetwidget.cpp | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ClockReceiver/VSyncPredictor.hpp b/ClockReceiver/VSyncPredictor.hpp index 229a8d636..f34ed55c5 100644 --- a/ClockReceiver/VSyncPredictor.hpp +++ b/ClockReceiver/VSyncPredictor.hpp @@ -63,6 +63,13 @@ class VSyncPredictor { frame_duration_ = Nanos(1'000'000'000.0f / rate); } + /*! + @returns The time this class currently believes a whole frame occupies. + */ + Time::Nanos frame_duration() { + return frame_duration_; + } + /*! Adds a record of how much jitter was experienced in scheduling; these values will be factored into the @c suggested_draw_time if supplied. @@ -87,15 +94,13 @@ class VSyncPredictor { (if those figures are being supplied). */ Nanos suggested_draw_time() { - const auto mean = redraw_period_.mean() - timer_jitter_.mean() - vsync_jitter_.mean(); + const auto mean = redraw_period_.mean() + timer_jitter_.mean() + vsync_jitter_.mean(); const auto variance = redraw_period_.variance() + timer_jitter_.variance() + vsync_jitter_.variance(); // Permit three standard deviations from the mean, to cover 99.9% of cases. - const auto period = mean - Nanos(3.0f * sqrt(float(variance))); + const auto period = mean + Nanos(3.0f * sqrt(float(variance))); - assert(abs(period) < 10'000'000'000); - - return last_vsync_ + period; + return last_vsync_ + frame_duration_ - period; } private: @@ -109,7 +114,6 @@ class VSyncPredictor { } void post(Time::Nanos value) { - assert(abs(value) < 10'000'000'000); // 10 seconds is a very liberal maximum. sum_ -= history_[write_pointer_]; sum_ += value; history_[write_pointer_] = value; diff --git a/OSBindings/Qt/scantargetwidget.cpp b/OSBindings/Qt/scantargetwidget.cpp index 99072b486..3fbbc7c53 100644 --- a/OSBindings/Qt/scantargetwidget.cpp +++ b/OSBindings/Qt/scantargetwidget.cpp @@ -86,7 +86,7 @@ void ScanTargetWidget::vsync() { const auto time_now = Time::nanos_now(); requestedRedrawTime = vsyncPredictor.suggested_draw_time(); const auto delay_time = (requestedRedrawTime - time_now) / 1'000'000; - if(delay_time > 0) { + if(delay_time > 0 && delay_time < vsyncPredictor.frame_duration()) { QTimer::singleShot(delay_time, this, SLOT(repaint())); } else { requestedRedrawTime = 0;