foxygit / Torlinkc Log in
commits tags

/src/ui/engine_thread.cpp · 2.81 KB

raw
#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