Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,16 @@ add_library(splasheditdoc STATIC
src/editor/gizmo.cpp
src/editor/live_export.cpp
src/editor/play.cpp
src/editor/project.cpp)
src/editor/project.cpp
src/editor/serial.cpp
src/editor/hardware.cpp
src/editor/unirom.cpp)
target_include_directories(splasheditdoc PUBLIC src)
find_package(Threads REQUIRED)
target_link_libraries(splasheditdoc PUBLIC splashcore Threads::Threads)
if(WIN32)
target_link_libraries(splasheditdoc PUBLIC ws2_32)
endif()
if(MSVC)
target_compile_options(splasheditdoc PRIVATE /W4 /utf-8)
else()
Expand Down
99 changes: 99 additions & 0 deletions src/editor/hardware.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "editor/hardware.hh"

#include <fstream>
#include <iterator>

#include "editor/unirom.hh"

namespace editor {

HardwareRun::~HardwareRun() { stop(); }

bool HardwareRun::active() const {
const Phase p = phase_.load();
return p == Phase::Connecting || p == Phase::Uploading || p == Phase::Running;
}

std::string HardwareRun::error() const {
std::lock_guard<std::mutex> lock(m_);
return error_;
}

std::deque<std::string> HardwareRun::lines() const {
std::lock_guard<std::mutex> lock(m_);
return lines_;
}

uint64_t HardwareRun::lineCount() const {
std::lock_guard<std::mutex> lock(m_);
return count_;
}

void HardwareRun::add(std::string line) {
std::lock_guard<std::mutex> lock(m_);
lines_.push_back(std::move(line));
if (lines_.size() > 2000) lines_.pop_front();
++count_;
}

void HardwareRun::fail(const std::string& why) {
{
std::lock_guard<std::mutex> lock(m_);
error_ = why;
}
add(why);
phase_ = Phase::Failed;
}

HardwareRun::Opener HardwareRun::port(std::string spec) {
return [spec = std::move(spec)](std::string* err) { return openLink(spec, 115200, err); };
}

void HardwareRun::stop() {
cancel_ = true;
if (thread_.joinable()) thread_.join();
if (active()) phase_ = Phase::Stopped;
}

void HardwareRun::start(Opener open, std::filesystem::path exe, std::filesystem::path dir) {
stop();
cancel_ = false;
progress_ = 0;
{
std::lock_guard<std::mutex> lock(m_);
lines_.clear();
count_ = 0;
error_.clear();
}
phase_ = Phase::Connecting;
thread_ = std::thread([this, open = std::move(open), exe = std::move(exe), dir = std::move(dir)] {
std::ifstream in(exe, std::ios::binary);
std::vector<uint8_t> bytes((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
if (bytes.empty()) return fail("Cannot read the psxsplash build.");
std::string err;
std::unique_ptr<Link> link = open(&err);
if (!link) return fail(err);
if (cancel_) {
phase_ = Phase::Stopped;
return;
}
phase_ = Phase::Uploading;
add("Uploading psxsplash...");
if (!uniromUpload(*link, bytes, &err, [this](int p) { progress_ = p; }, &cancel_)) {
if (cancel_) {
phase_ = Phase::Stopped;
return;
}
return fail(err);
}
phase_ = Phase::Running;
add("Running. Files are served from " + dir.string());
PcdrvHost host(dir);
const bool ok = host.serve(*link, cancel_, [this](const std::string& l) { add(l); },
[this](const std::string& e) { add("[file] " + e); }, &err);
if (!ok && !cancel_) return fail(err);
phase_ = Phase::Stopped;
});
}

} // namespace editor
59 changes: 59 additions & 0 deletions src/editor/hardware.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Run on hardware: one background session that uploads psxsplash to a console
// over a Link and then serves its PCdrv files, with what it printed kept for
// the editor to show. No SDL.
#pragma once

#include <atomic>
#include <cstdint>
#include <deque>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>

#include "editor/serial.hh"

namespace editor {

class HardwareRun {
public:
enum class Phase { Idle, Connecting, Uploading, Running, Failed, Stopped };
using Opener = std::function<std::unique_ptr<Link>(std::string* err)>;

HardwareRun() = default;
~HardwareRun();
HardwareRun(const HardwareRun&) = delete;
HardwareRun& operator=(const HardwareRun&) = delete;

// Opens the link with `open`, uploads `exe`, then serves files from `dir`
// until stop(). A session already running is stopped first.
void start(Opener open, std::filesystem::path exe, std::filesystem::path dir);
// The usual opener: a serial port (or tcp:HOST:PORT) at 115200.
static Opener port(std::string spec);
void stop();

Phase phase() const { return phase_.load(); }
bool active() const; // connecting, uploading or running
int progress() const { return progress_.load(); }
std::string error() const;
// The console's text and the file calls, oldest first, the newest 2000.
std::deque<std::string> lines() const;
uint64_t lineCount() const;

private:
void add(std::string line);
void fail(const std::string& why);

std::thread thread_;
std::atomic<bool> cancel_{false};
std::atomic<Phase> phase_{Phase::Idle};
std::atomic<int> progress_{0};
mutable std::mutex m_;
std::deque<std::string> lines_;
uint64_t count_ = 0;
std::string error_;
};

} // namespace editor
15 changes: 14 additions & 1 deletion src/editor/play.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ PlayTools loadPlayTools(const fs::path& file) {
else if (key == "psxsplash") t.psxsplash = p;
else if (key == "bios") t.bios = p;
else if (key == "disc") t.disc = p;
else if (key == "port") t.port = value;
}
return t;
}
Expand All @@ -44,7 +45,8 @@ bool savePlayTools(const fs::path& file, const PlayTools& t) {
out << "redux=" << utf8(t.redux) << "\n"
<< "psxsplash=" << utf8(t.psxsplash) << "\n"
<< "bios=" << utf8(t.bios) << "\n"
<< "disc=" << utf8(t.disc) << "\n";
<< "disc=" << utf8(t.disc) << "\n"
<< "port=" << t.port << "\n";
return bool(out);
}

Expand Down Expand Up @@ -94,6 +96,8 @@ PlayTools withDefaults(PlayTools t, const fs::path& bundle) {
fromEnv(t.psxsplash, "SPLASHEDIT_PSXSPLASH");
fromEnv(t.bios, "SPLASHEDIT_BIOS");
fromEnv(t.disc, "SPLASHEDIT_DISC_ENGINE");
if (t.port.empty())
if (const char* v = std::getenv("SPLASHEDIT_PORT"); v && *v) t.port = v;
fromBundle(t.redux, bundle,
{"redux/pcsx-redux.exe", "redux/PCSX-Redux.app/Contents/MacOS/PCSX-Redux", "redux/pcsx-redux"});
fromBundle(t.psxsplash, bundle, {"engine/psxsplash.ps-exe"});
Expand Down Expand Up @@ -123,6 +127,15 @@ std::vector<std::string> missingDiscTools(const PlayTools& t) {
return out;
}

std::vector<std::string> missingHardwareTools(const PlayTools& t) {
std::vector<std::string> out;
std::error_code ec;
if (t.psxsplash.empty()) out.push_back("The psxsplash build (.ps-exe) is not set.");
else if (!fs::is_regular_file(t.psxsplash, ec)) out.push_back("The psxsplash build not found: " + utf8(t.psxsplash));
if (t.port.empty()) out.push_back("No serial port is set.");
return out;
}

splash::ExportResult exportForPlay(const splash::Scene& scene, const fs::path& projectRoot, const fs::path& dir) {
splash::ExportResult r;
std::error_code ec;
Expand Down
8 changes: 6 additions & 2 deletions src/editor/play.hh
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ namespace editor {
// `disc` is the CD-ROM build of psxsplash that Export puts on the disc.
struct PlayTools {
std::filesystem::path redux, psxsplash, bios, disc;
std::string port; // Run on hardware: a serial port, or tcp:HOST:PORT
};

// key=value lines: redux=, psxsplash=, bios=, disc=. A missing file reads as empty.
// key=value lines: redux=, psxsplash=, bios=, disc=, port=. A missing file reads as empty.
PlayTools loadPlayTools(const std::filesystem::path& file);
bool savePlayTools(const std::filesystem::path& file, const PlayTools& tools);

// Fills empty entries from SPLASHEDIT_REDUX, SPLASHEDIT_PSXSPLASH,
// SPLASHEDIT_BIOS and SPLASHEDIT_DISC_ENGINE, then from the copies shipped in
// SPLASHEDIT_BIOS, SPLASHEDIT_DISC_ENGINE and SPLASHEDIT_PORT, then from the copies shipped in
// `bundle` (the directory holding the editor: redux/ and engine/), then looks
// for pcsx-redux on PATH.
PlayTools withDefaults(PlayTools tools, const std::filesystem::path& bundle = {});
Expand All @@ -34,6 +35,9 @@ std::vector<std::string> missingTools(const PlayTools& tools);
// The same for Export: only the disc build.
std::vector<std::string> missingDiscTools(const PlayTools& tools);

// The same for Run on hardware: the psxsplash build and a port.
std::vector<std::string> missingHardwareTools(const PlayTools& tools);

// Writes dir/scene_0.{splashpack,vram,spu}, the names psxsplash's PCdrv
// loader opens for the first scene.
splash::ExportResult exportForPlay(const splash::Scene& scene, const std::filesystem::path& projectRoot,
Expand Down
Loading
Loading