commits
tags
#include "torlinkc/ui/engine_thread.hpp"
#include <chrono>
#include <ftxui/component/event.hpp>
#include "torlinkc/engine/bootguard.hpp"
#include "torlinkc/engine/persist.hpp"
#include "torlinkc/engine/reconcile.hpp"
namespace torlinkc::ui {
namespace {
constexpr auto kTickInterval = std::chrono::milliseconds(500);
}
EngineThread::EngineThread(ftxui::ScreenInteractive& screen, AppState& state) : screen_(screen), state_(state) {}
EngineThread::~EngineThread() { stop(); }
void EngineThread::start(Config config) {
running_ = true;
thread_ = std::thread([this, config = std::move(config)]() mutable { run(std::move(config)); });
}
void EngineThread::stop() {
running_ = false;
if (thread_.joinable()) thread_.join();
}
void EngineThread::post(std::function<void(DownloadQueue&)> fn) {
std::lock_guard<std::mutex> lock(commandsMutex_);
commands_.push_back(std::move(fn));
}
void EngineThread::run(Config config) {
DownloadQueue queue;
queue.setTrackers(config.trackers);
const bool safe = wasBootInterrupted();
armBootMarker();
queue.restoreHistory(loadHistory());
queue.restore(reconcileQueue(loadQueue()), RestoreOptions{safe});
queue.restoreSeeds(loadSeeds(), RestoreOptions{safe});
if (safe) {
screen_.Post([this] { state_.notice = "previous run did not shut down cleanly -- restored paused (safe mode)"; });
screen_.PostEvent(ftxui::Event::Custom);
}
const auto bootAt = std::chrono::steady_clock::now();
bool settled = false;
while (running_) {
std::deque<std::function<void(DownloadQueue&)>> pending;
{
std::lock_guard<std::mutex> lock(commandsMutex_);
std::swap(pending, commands_);
}
for (auto& fn : pending) fn(queue);
queue.tick();
if (!settled && std::chrono::steady_clock::now() - bootAt > std::chrono::milliseconds(kBootSettleMs)) {
// The boot survived long enough to be worth trusting.
disarmBootMarker();
settled = true;
}
publishSnapshot(queue);
std::this_thread::sleep_for(kTickInterval);
}
queue.suspend();
}
void EngineThread::publishSnapshot(DownloadQueue& queue) {
auto items = queue.getItems();
auto seeds = queue.getSeeds();
auto history = queue.getHistory();
screen_.Post([this, items = std::move(items), seeds = std::move(seeds), history = std::move(history)]() mutable {
state_.items = std::move(items);
state_.seeds = std::move(seeds);
state_.history = std::move(history);
});
// A bare Post()'d closure updates state but doesn't itself wake FTXUI's
// main loop to redraw -- only a real Event does. PostEvent(Event::Custom)
// is FTXUI's documented no-op event for exactly this: force a redraw after
// a background thread mutates state, without it needing to mean anything
// to any component's OnEvent.
screen_.PostEvent(ftxui::Event::Custom);
}
} // namespace torlinkc::ui