build: split core/ from frontends/; prepare for multiple GUI/TUI targets
Reorganise the tree into business vs frontend as separate directories:
src/core/{domain,imports,app} (was system/, imports/, app/)
src/frontends/tui/ (was tui/ + main.cpp)
tests/tui/ (the FTXUI-coupled helper test)
All cross-dir #include paths rewritten; same-dir includes untouched.
CMake: essim_core is the frontend-agnostic business library — links libzip,
pugixml and bsdl, NO GUI toolkit. Each frontend is a self-contained
src/frontends/<name>/ (own CMakeLists, toolkit, main.cpp) that links
essim_core, selected with -DESSIM_FRONTEND=<name> (default tui; 'none' = core +
tests only, no toolkit fetched). FTXUI moved into the tui frontend. Tests are
split: essim_tests links essim_core (no FTXUI), essim_tui_tests links essim_tui.
Verified: default tui build green (ctest 2/2); ESSIM_FRONTEND=none builds the
core + tests with FTXUI never fetched and no `essim` binary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
192
src/core/domain/persist.cpp
Normal file
192
src/core/domain/persist.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
#include "persist.hpp"
|
||||
|
||||
#include "bsdl_model.hpp"
|
||||
#include "connect.hpp"
|
||||
#include "modules.hpp"
|
||||
#include "parts.hpp"
|
||||
#include "pins.hpp"
|
||||
#include "signals.hpp"
|
||||
#include "system.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
// Tab-delimited tokeniser. Empty trailing fields are preserved.
|
||||
std::vector<std::string> split_tab(const std::string &line) {
|
||||
std::vector<std::string> out;
|
||||
std::string cur;
|
||||
for (char c : line) {
|
||||
if (c == '\t') { out.push_back(std::move(cur)); cur.clear(); }
|
||||
else cur.push_back(c);
|
||||
}
|
||||
out.push_back(std::move(cur));
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool save_system(const System *sys, const std::string &filename, std::string &error)
|
||||
{
|
||||
if (!sys) { error = "no system to save"; return false; }
|
||||
std::ofstream f(filename);
|
||||
if (!f) { error = "cannot open " + filename + " for writing"; return false; }
|
||||
|
||||
f << "# essim system snapshot v1\n";
|
||||
|
||||
for (auto &mkv : *sys->modules()) {
|
||||
Module *mod = mkv.second;
|
||||
f << "M\t" << mod->name << "\n";
|
||||
for (auto &pkv : *mod) {
|
||||
Part *p = pkv.second;
|
||||
f << "P\t" << p->name << "\t" << p->connector_type << "\n";
|
||||
if (!p->bsdl_path.empty())
|
||||
f << "B\t" << p->bsdl_path << "\n";
|
||||
for (auto &nkv : *p) {
|
||||
Pin *pin = nkv.second;
|
||||
Signal *s = pin->signal();
|
||||
f << "N\t" << pin->name << "\t" << (s ? s->name : "");
|
||||
const char *otag = nc_origin_tag(pin->nc_origin);
|
||||
if (*otag) f << "\t" << otag;
|
||||
f << "\n";
|
||||
}
|
||||
}
|
||||
// Signal types: only persist non-default (Other) overrides.
|
||||
for (auto &skv : *mod->signals) {
|
||||
Signal *s = skv.second;
|
||||
if (s->type == SignalType::Other) continue;
|
||||
f << "S\t" << s->name << "\t" << signal_type_name(s->type) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &ckv : *sys->connections()) {
|
||||
Connection *c = ckv.second;
|
||||
f << "C\t" << c->name
|
||||
<< "\t" << (c->m1 ? c->m1->name : "")
|
||||
<< "\t" << (c->p1 ? c->p1->name : "")
|
||||
<< "\t" << (c->m2 ? c->m2->name : "")
|
||||
<< "\t" << (c->p2 ? c->p2->name : "")
|
||||
<< "\t" << c->transform_name << "\n";
|
||||
for (auto &wp : c->pin_map) {
|
||||
Pin *a = wp.first;
|
||||
Pin *b = wp.second;
|
||||
f << "W"
|
||||
<< "\t" << (a && a->prnt && a->prnt->prnt ? a->prnt->prnt->name : "")
|
||||
<< "\t" << (a && a->prnt ? a->prnt->name : "")
|
||||
<< "\t" << (a ? a->name : "")
|
||||
<< "\t" << (b && b->prnt && b->prnt->prnt ? b->prnt->prnt->name : "")
|
||||
<< "\t" << (b && b->prnt ? b->prnt->name : "")
|
||||
<< "\t" << (b ? b->name : "")
|
||||
<< "\n";
|
||||
}
|
||||
}
|
||||
return f.good();
|
||||
}
|
||||
|
||||
System *restore_system(const std::string &filename, std::string &error)
|
||||
{
|
||||
std::ifstream f(filename);
|
||||
if (!f) { error = "cannot open " + filename; return nullptr; }
|
||||
|
||||
System *sys = new System();
|
||||
Module *cur_mod = nullptr;
|
||||
Part *cur_part = nullptr;
|
||||
Connection *cur_conn = nullptr;
|
||||
|
||||
std::string line;
|
||||
int lineno = 0;
|
||||
|
||||
auto fail = [&](const std::string &msg) {
|
||||
error = "line " + std::to_string(lineno) + ": " + msg;
|
||||
delete sys;
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
while (std::getline(f, line)) {
|
||||
++lineno;
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
auto fs = split_tab(line);
|
||||
if (fs.empty()) continue;
|
||||
const std::string &tag = fs[0];
|
||||
|
||||
try {
|
||||
if (tag == "M") {
|
||||
if (fs.size() < 2) return fail("M needs <name>");
|
||||
cur_mod = sys->modules()->merge(fs[1]);
|
||||
cur_part = nullptr;
|
||||
} else if (tag == "P") {
|
||||
if (!cur_mod) return fail("P outside module");
|
||||
if (fs.size() < 2) return fail("P needs <name>");
|
||||
cur_part = new Part(fs[1]);
|
||||
if (fs.size() >= 3) cur_part->connector_type = fs[2];
|
||||
cur_mod->add(cur_part);
|
||||
} else if (tag == "N") {
|
||||
if (!cur_part || !cur_mod) return fail("N outside part");
|
||||
if (fs.size() < 3) return fail("N needs <pin> <signal>");
|
||||
Pin *pin = new Pin(fs[1]);
|
||||
cur_part->add(pin);
|
||||
if (!fs[2].empty()) {
|
||||
Signal *s = cur_mod->signals->merge(fs[2]);
|
||||
s->add(pin);
|
||||
pin->connect(s);
|
||||
} else if (fs.size() >= 4) {
|
||||
NcOrigin o;
|
||||
if (nc_origin_from_tag(fs[3], o)) pin->nc_origin = o;
|
||||
}
|
||||
} else if (tag == "S") {
|
||||
if (!cur_mod) return fail("S outside module");
|
||||
if (fs.size() < 3) return fail("S needs <signal> <type>");
|
||||
Signal *s;
|
||||
try { s = cur_mod->signals->get(fs[1]); }
|
||||
catch (const std::exception &) { continue; }
|
||||
SignalType t;
|
||||
if (signal_type_from_name(fs[2], t)) s->type = t;
|
||||
} else if (tag == "C") {
|
||||
if (fs.size() < 7) return fail("C needs <name> <m1> <p1> <m2> <p2> <transform>");
|
||||
Module *m1 = sys->modules()->get(fs[2]);
|
||||
Module *m2 = sys->modules()->get(fs[4]);
|
||||
Part *p1 = m1->get(fs[3]);
|
||||
Part *p2 = m2->get(fs[5]);
|
||||
cur_conn = new Connection(fs[1], m1, p1, m2, p2);
|
||||
cur_conn->transform_name = fs[6];
|
||||
sys->connections()->add(cur_conn);
|
||||
} else if (tag == "W") {
|
||||
if (!cur_conn) return fail("W outside connection");
|
||||
if (fs.size() < 7) return fail("W needs <m1> <p1> <pin1> <m2> <p2> <pin2>");
|
||||
Module *m1 = sys->modules()->get(fs[1]);
|
||||
Module *m2 = sys->modules()->get(fs[4]);
|
||||
Part *p1 = m1->get(fs[2]);
|
||||
Part *p2 = m2->get(fs[5]);
|
||||
Pin *pin1 = p1->get(fs[3]);
|
||||
Pin *pin2 = p2->get(fs[6]);
|
||||
cur_conn->pin_map.emplace_back(pin1, pin2);
|
||||
} else if (tag == "B") {
|
||||
if (!cur_part) return fail("B outside part");
|
||||
if (fs.size() < 2) return fail("B needs <path>");
|
||||
cur_part->bsdl_path = fs[1];
|
||||
} else {
|
||||
return fail("unknown tag '" + tag + "'");
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
return fail(std::string(tag) + ": " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
// Re-derive BSDL-sourced pin specs from each attached model: we persisted the
|
||||
// .bsd path (`B` tag), not the derived data, so re-parse and re-apply here.
|
||||
// Models that no longer parse are skipped silently.
|
||||
for (auto &mkv : *sys->modules()) {
|
||||
for (auto &pkv : *mkv.second) {
|
||||
Part *p = pkv.second;
|
||||
if (p->bsdl_path.empty()) continue;
|
||||
BsdlModel m = BsdlModel::from_file(p->bsdl_path);
|
||||
if (m.valid()) apply_bsdl(p, m);
|
||||
}
|
||||
}
|
||||
|
||||
return sys;
|
||||
}
|
||||
Reference in New Issue
Block a user