feat(editor): EntityWriter — persisteix el bloc entities: al sortir
This commit is contained in:
1 parent
b4e36944d7
commit
ecb4625465
6 files changed
+213
-9
No files matched your search
@@ -0,0 +1,115 @@
|
||||
#include "game/entities/entity_writer.hpp"
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
namespace EntityWriter {
|
||||
|
||||
namespace {
|
||||
|
||||
// Cerca la línia que comença per `<key>:` a columna 0. Mateix
|
||||
// patró que TilemapWriter::findDataLine però parametritzat. Si
|
||||
// un dia paga la pena, podem extreure'l a un helper compartit.
|
||||
auto findKeyLine(const std::string& content, const std::string& key) -> std::size_t {
|
||||
const std::string PREFIX = key + ":";
|
||||
std::size_t pos = 0;
|
||||
while (pos < content.size()) {
|
||||
const bool AT_LINE_START = (pos == 0 || content[pos - 1] == '\n');
|
||||
if (AT_LINE_START && content.compare(pos, PREFIX.size(), PREFIX) == 0) {
|
||||
const std::size_t AFTER = pos + PREFIX.size();
|
||||
if (AFTER >= content.size() || content[AFTER] == '\n' || content[AFTER] == '\r' || content[AFTER] == ' ' || content[AFTER] == '\t' || content[AFTER] == '#') {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
const auto NL = content.find('\n', pos);
|
||||
if (NL == std::string::npos) { break; }
|
||||
pos = NL + 1;
|
||||
}
|
||||
return std::string::npos;
|
||||
}
|
||||
|
||||
// Avança mentre trobe línies indentades (espai/tab) o línies buides.
|
||||
// A diferència del TilemapWriter, ací incloem línies buides dins
|
||||
// del bloc: l'editor de YAML deixa sovint blanc entre grups
|
||||
// d'entitats, i volem absorbir-lo per no deixar restes orfes
|
||||
// entre el bloc nou i el següent top-level.
|
||||
auto advancePastBlock(const std::string& content, std::size_t pos) -> std::size_t {
|
||||
while (pos < content.size()) {
|
||||
const char C = content[pos];
|
||||
const bool IS_INDENT = (C == ' ' || C == '\t');
|
||||
const bool IS_BLANK = (C == '\n' || C == '\r');
|
||||
if (!IS_INDENT && !IS_BLANK) { break; }
|
||||
const auto NL = content.find('\n', pos);
|
||||
pos = (NL == std::string::npos) ? content.size() : NL + 1;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
auto buildBlock(const std::vector<Spawn>& spawns) -> std::string {
|
||||
std::string out;
|
||||
for (const auto& s : spawns) {
|
||||
out += " - type: " + s.type + "\n";
|
||||
if (s.sarc_col.has_value() && s.sarc_row.has_value()) {
|
||||
out += " sarc_position: [" + std::to_string(*s.sarc_col) + ", " + std::to_string(*s.sarc_row) + "]\n";
|
||||
} else if (s.col.has_value() && s.row.has_value()) {
|
||||
out += " position: [" + std::to_string(*s.col) + ", " + std::to_string(*s.row) + "]\n";
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
auto saveEntitiesBlock(const std::string& path, const std::vector<Spawn>& spawns) -> bool {
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
if (!in) { return false; }
|
||||
std::ostringstream ss;
|
||||
ss << in.rdbuf();
|
||||
const std::string CONTENT = ss.str();
|
||||
in.close();
|
||||
|
||||
const auto KEY_POS = findKeyLine(CONTENT, "entities");
|
||||
if (KEY_POS == std::string::npos) { return false; }
|
||||
|
||||
const auto LINE_END = CONTENT.find('\n', KEY_POS);
|
||||
const auto AFTER_KEY = (LINE_END == std::string::npos) ? CONTENT.size() : LINE_END + 1;
|
||||
const auto BLOCK_END = advancePastBlock(CONTENT, AFTER_KEY);
|
||||
|
||||
// Mantenim una línia en blanc darrere del bloc si abans n'hi
|
||||
// havia: ajuda a separar visualment el `entities:` de la pròxima
|
||||
// secció. Si no n'hi havia, no afegim res.
|
||||
const std::string TAIL = CONTENT.substr(BLOCK_END);
|
||||
const std::string NEW_CONTENT =
|
||||
CONTENT.substr(0, AFTER_KEY) + buildBlock(spawns) + TAIL;
|
||||
|
||||
const auto TMP_PATH = path + ".tmp";
|
||||
{
|
||||
std::ofstream out(TMP_PATH, std::ios::binary | std::ios::trunc);
|
||||
if (!out) { return false; }
|
||||
out.write(NEW_CONTENT.data(), static_cast<std::streamsize>(NEW_CONTENT.size()));
|
||||
out.close();
|
||||
if (!out) {
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(TMP_PATH, ec);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::rename(TMP_PATH, path, ec);
|
||||
if (ec) {
|
||||
std::filesystem::remove(TMP_PATH, ec);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace EntityWriter
|
||||
|
||||
#endif // _DEBUG
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _DEBUG
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Reescriu NOMÉS el bloc `entities:` d'un YAML de nivell, preservant byte
|
||||
// a byte la resta del fitxer: comentaris, ordre de claus, format del
|
||||
// `geometry:` i `data:`, encoding i line endings entre línies no tocades.
|
||||
// Pensat exclusivament per a l'editor (Debug).
|
||||
//
|
||||
// Format de sortida (estable, llegible a l'editor de text):
|
||||
// entities:
|
||||
// - type: torch
|
||||
// position: [4, 2]
|
||||
// - type: sarcophagus
|
||||
// sarc_position: [0, 0]
|
||||
// - type: door
|
||||
//
|
||||
// Semàntica del bloc origen:
|
||||
// - Línia de capçalera: primera aparició de "entities:" a columna 0.
|
||||
// Si manca, el writer FALLA i no toca el fitxer.
|
||||
// - Contingut: línies indentades (espai o tab). Una línia buida o una
|
||||
// línia a columna 0 marquen la fi del bloc.
|
||||
//
|
||||
// Si el fitxer original no té el bloc `entities:`, el writer no inventa
|
||||
// ubicació: retorna false per que el caller decidisca (mostrar error,
|
||||
// avortar, etc.) — no és funció del writer escollir l'estil del YAML.
|
||||
//
|
||||
// Escriptura atòmica: via `<path>.tmp` + `std::filesystem::rename`. Si
|
||||
// l'escriptura falla a meitat, el YAML original queda intacte.
|
||||
namespace EntityWriter {
|
||||
|
||||
// Cada spawn duplica l'estructura mínima del que el lector espera:
|
||||
// type sempre, i un dels (position, sarc_position) o cap (cas door).
|
||||
struct Spawn {
|
||||
std::string type;
|
||||
std::optional<int> col;
|
||||
std::optional<int> row;
|
||||
std::optional<int> sarc_col;
|
||||
std::optional<int> sarc_row;
|
||||
};
|
||||
|
||||
auto saveEntitiesBlock(const std::string& path, const std::vector<Spawn>& spawns) -> bool;
|
||||
|
||||
} // namespace EntityWriter
|
||||
|
||||
#endif // _DEBUG
|
||||
Reference in new issue
Block a user