mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 05:09:20 +02:00
Merge branch 'feat/modern-editor' of https://github.com/MaSzyna-EU07/maszyna into feat/modern-editor
This commit is contained in:
@@ -503,6 +503,30 @@ basic_cell::erase( TMemCell *Memorycell ) {
|
||||
std::end( m_memorycells ) );
|
||||
}
|
||||
|
||||
void
|
||||
basic_cell::erase( TTraction *Traction ) {
|
||||
|
||||
m_traction.erase(
|
||||
std::remove( std::begin( m_traction ), std::end( m_traction ), Traction ),
|
||||
std::end( m_traction ) );
|
||||
}
|
||||
|
||||
void
|
||||
basic_cell::unregister_end( TTraction *Traction ) {
|
||||
|
||||
m_directories.traction.erase(
|
||||
std::remove( std::begin( m_directories.traction ), std::end( m_directories.traction ), Traction ),
|
||||
std::end( m_directories.traction ) );
|
||||
}
|
||||
|
||||
void
|
||||
basic_cell::erase( TEventLauncher *Launcher ) {
|
||||
|
||||
m_eventlaunchers.erase(
|
||||
std::remove( std::begin( m_eventlaunchers ), std::end( m_eventlaunchers ), Launcher ),
|
||||
std::end( m_eventlaunchers ) );
|
||||
}
|
||||
|
||||
// registers provided path in the lookup directory of the cell
|
||||
void
|
||||
basic_cell::register_end( TTrack *Path ) {
|
||||
|
||||
@@ -155,6 +155,14 @@ public:
|
||||
// removes provided memory cell from the cell
|
||||
void
|
||||
erase( TMemCell *Memorycell );
|
||||
// removes provided traction piece from the cell
|
||||
void
|
||||
erase( TTraction *Traction );
|
||||
void
|
||||
unregister_end( TTraction *Traction );
|
||||
// removes provided event launcher from the cell
|
||||
void
|
||||
erase( TEventLauncher *Launcher );
|
||||
// find a vehicle located nearest to specified point, within specified radius. reurns: located vehicle and distance
|
||||
std::tuple<TDynamicObject *, float>
|
||||
find( glm::dvec3 const &Point, float const Radius, bool const Onlycontrolled, bool const Findbycoupler ) const;
|
||||
|
||||
@@ -17,7 +17,10 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/utilities.h"
|
||||
|
||||
#include <array>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
|
||||
namespace scene
|
||||
{
|
||||
@@ -37,6 +40,17 @@ glm::dvec3 layer_context::to_local(glm::dvec3 Location) const
|
||||
return Location;
|
||||
}
|
||||
|
||||
glm::dvec3 layer_context::to_world(glm::dvec3 Location) const
|
||||
{
|
||||
if (rotation != glm::vec3{0.f})
|
||||
{
|
||||
Location = glm::rotateY<double>(Location, glm::radians(static_cast<double>(rotation.y)));
|
||||
}
|
||||
Location *= glm::dvec3{scale};
|
||||
Location += offset;
|
||||
return Location;
|
||||
}
|
||||
|
||||
bool layer_context::matches(layer_context const &Other) const
|
||||
{
|
||||
return glm::all(glm::epsilonEqual(offset, Other.offset, 1e-4)) && glm::all(glm::epsilonEqual(rotation, Other.rotation, 1e-4f)) && glm::all(glm::epsilonEqual(scale, Other.scale, 1e-5f));
|
||||
@@ -62,6 +76,7 @@ layer_handle node_layers::open(std::string const &Name)
|
||||
m_layers.back().parent = handle();
|
||||
m_layers.back().late = m_initialized;
|
||||
stat(layerhandle);
|
||||
scan(layerhandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -119,6 +134,8 @@ void node_layers::clear()
|
||||
m_instance = 0;
|
||||
m_initialized = false;
|
||||
m_terraindirective = false;
|
||||
m_marked.clear();
|
||||
m_marking.clear();
|
||||
}
|
||||
|
||||
// indicates the scenario initialization (FirstInit) is being performed
|
||||
@@ -767,7 +784,104 @@ bool node_layers::merge(layer_handle Source, layer_handle Target)
|
||||
// true if there are layer changes awaiting save. NOTE: doesn't account for modified nodes
|
||||
bool node_layers::pending() const
|
||||
{
|
||||
return std::any_of(std::begin(m_layers), std::end(m_layers), [](basic_layer const &Layer) { return false == Layer.dead && (Layer.created || Layer.removed || Layer.merged != null_handle); });
|
||||
return false == m_marking.empty() ||
|
||||
std::any_of(std::begin(m_layers), std::end(m_layers), [](basic_layer const &Layer) { return false == Layer.dead && (Layer.created || Layer.removed || Layer.merged != null_handle); });
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// marks of the lines the editor keeps track of
|
||||
std::array<char const *, 2> const editormarks{"//$p", "//$b"};
|
||||
} // namespace
|
||||
|
||||
void node_layers::scan(layer_handle const Layer)
|
||||
{
|
||||
m_marked.erase(Layer);
|
||||
std::ifstream file{path(Layer), std::ios_base::binary};
|
||||
if (false == file.is_open())
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::string content{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
|
||||
if (content.find("//$") == std::string::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
std::size_t begin{0};
|
||||
while (begin < content.size())
|
||||
{
|
||||
auto end{content.find('\n', begin)};
|
||||
end = (end == std::string::npos ? content.size() : end + 1);
|
||||
auto const first{content.find_first_not_of(" \t", begin)};
|
||||
if (first < end)
|
||||
{
|
||||
for (auto const *mark : editormarks)
|
||||
{
|
||||
auto const length{std::char_traits<char>::length(mark)};
|
||||
if (content.compare(first, length, mark) != 0 || (first + length < end && content[first + length] != ' ' && content[first + length] != '\t' && content[first + length] != '\r' && content[first + length] != '\n'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto text{content.substr(first + length, end - first - length)};
|
||||
while (false == text.empty() && (text.back() == '\n' || text.back() == '\r'))
|
||||
{
|
||||
text.pop_back();
|
||||
}
|
||||
text.erase(0, text.find_first_not_of(" \t"));
|
||||
m_marked[Layer].push_back({mark, {static_cast<std::streamoff>(begin), static_cast<std::streamoff>(end)}, std::move(text)});
|
||||
break;
|
||||
}
|
||||
}
|
||||
begin = end;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> node_layers::marked(layer_handle const Layer, std::string const &Mark) const
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
if (auto const pending{m_marking.find({Layer, Mark})}; pending != m_marking.end())
|
||||
{
|
||||
return pending->second;
|
||||
}
|
||||
if (auto const lookup{m_marked.find(Layer)}; lookup != m_marked.end())
|
||||
{
|
||||
for (auto const &line : lookup->second)
|
||||
{
|
||||
if (line.mark == Mark)
|
||||
{
|
||||
result.push_back(line.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<layer_handle> node_layers::marked_layers(std::string const &Mark) const
|
||||
{
|
||||
std::vector<layer_handle> result;
|
||||
for (auto const &entry : m_marked)
|
||||
{
|
||||
if (listed(entry.first) && std::any_of(entry.second.begin(), entry.second.end(), [&](marked_line const &Line) { return Line.mark == Mark; }))
|
||||
{
|
||||
result.push_back(entry.first);
|
||||
}
|
||||
}
|
||||
for (auto const &entry : m_marking)
|
||||
{
|
||||
if (entry.first.second == Mark && std::find(result.begin(), result.end(), entry.first.first) == result.end())
|
||||
{
|
||||
result.push_back(entry.first.first);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void node_layers::mark(layer_handle const Layer, std::string const &Mark, std::vector<std::string> Lines)
|
||||
{
|
||||
if (valid(Layer))
|
||||
{
|
||||
m_marking[{Layer, Mark}] = std::move(Lines);
|
||||
}
|
||||
}
|
||||
|
||||
// indicates start of content of a template, included by directive at specified location of the layer file being loaded
|
||||
|
||||
@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include <filesystem>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace scene
|
||||
@@ -44,6 +45,8 @@ struct layer_context
|
||||
|
||||
// converts world space location to the one which produces it when loaded with this context in effect
|
||||
glm::dvec3 to_local(glm::dvec3 Location) const;
|
||||
// reverse of to_local()
|
||||
glm::dvec3 to_world(glm::dvec3 Location) const;
|
||||
bool matches(layer_context const &Other) const;
|
||||
};
|
||||
|
||||
@@ -261,6 +264,14 @@ class node_layers
|
||||
return m_terraindirective;
|
||||
}
|
||||
|
||||
// editor data kept in the scenery files as comment lines starting with a mark, e.g. "//$p ...", which the simulation skips
|
||||
// text of the lines with specified mark in specified layer, with the mark taken off
|
||||
std::vector<std::string> marked(layer_handle Layer, std::string const &Mark) const;
|
||||
// layers whose files hold lines with specified mark
|
||||
std::vector<layer_handle> marked_layers(std::string const &Mark) const;
|
||||
// lines to take the place of the ones with specified mark in the layer file, on save. the mark is added to each
|
||||
void mark(layer_handle Layer, std::string const &Mark, std::vector<std::string> Lines);
|
||||
|
||||
// layer management. changes are applied to the scenery files by save()
|
||||
// creates empty layer, included from specified one. returns: handle to the new layer, or null_handle with the explanation in Reason
|
||||
layer_handle create(std::string Name, layer_handle Parent, std::string &Reason);
|
||||
@@ -281,6 +292,11 @@ class node_layers
|
||||
{
|
||||
return Instance != 0 && Instance <= m_instances.size();
|
||||
}
|
||||
// handles of the includes run from 1 up to this number
|
||||
std::size_t instance_count() const
|
||||
{
|
||||
return m_instances.size();
|
||||
}
|
||||
// grants access to specified include. NOTE: the include has to be tracked
|
||||
include_instance const &instance(instance_handle const Instance) const
|
||||
{
|
||||
@@ -319,9 +335,17 @@ class node_layers
|
||||
struct file_patch;
|
||||
struct composition;
|
||||
struct save_state;
|
||||
struct marked_line
|
||||
{
|
||||
std::string mark;
|
||||
source_span span; // whole line, its end included
|
||||
std::string text;
|
||||
};
|
||||
// methods
|
||||
std::string path(layer_handle const Layer) const;
|
||||
void stat(layer_handle const Layer);
|
||||
// finds the marked lines in the layer file
|
||||
void scan(layer_handle const Layer);
|
||||
bool compose(save_state &State, layer_handle const Layer, composition &Output, int const Depth) const;
|
||||
// members
|
||||
std::vector<basic_layer> m_layers; // layer handle is index in the vector + 1
|
||||
@@ -335,6 +359,8 @@ class node_layers
|
||||
instance_handle m_instance{0}; // helper, include whose template is being loaded
|
||||
bool m_initialized{false}; // helper, the scenario initialization was performed already
|
||||
bool m_terraindirective{false};
|
||||
std::map<layer_handle, std::vector<marked_line>> m_marked;
|
||||
std::map<std::pair<layer_handle, std::string>, std::vector<std::string>> m_marking; // changes awaiting save
|
||||
};
|
||||
|
||||
extern node_layers Layers;
|
||||
|
||||
@@ -16,6 +16,9 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "simulation/simulation.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Traction.h"
|
||||
#include "world/EvLaunch.h"
|
||||
#include "world/Event.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/utilities.h"
|
||||
@@ -235,6 +238,54 @@ bool patch_memcell(std::string &Text, glm::dvec3 const &Location, std::string &E
|
||||
return true;
|
||||
}
|
||||
|
||||
// definition layout: node <max> <min> <name> traction <supply> <voltage> <current> <resistivity> <material> <thickness> <damage>
|
||||
// <point 1> <point 2> <point 3> <point 4> <minimal height> <segment length> <wires> <offset> <vis> [parallel <name>] endtraction
|
||||
bool patch_traction(std::string &Text, TTraction const &Traction, glm::dvec3 const &Offset, std::string &Error)
|
||||
{
|
||||
auto const tokens{tokenize(Text)};
|
||||
if (tokens.size() < 30 || tokens[0].text != "node" || tokens[4].text != "traction" || tokens.back().text != "endtraction")
|
||||
{
|
||||
Error = "unexpected layout of traction definition";
|
||||
return false;
|
||||
}
|
||||
std::vector<text_change> changes;
|
||||
std::size_t index{12};
|
||||
for (auto const &point : {Traction.pPoint1, Traction.pPoint2, Traction.pPoint3, Traction.pPoint4})
|
||||
{
|
||||
auto const local{point - Offset};
|
||||
for (auto const value : {local.x, local.y, local.z})
|
||||
{
|
||||
changes.push_back({tokens[index].begin, tokens[index].end, number(value)});
|
||||
++index;
|
||||
}
|
||||
}
|
||||
auto const &p1{Traction.pPoint1};
|
||||
auto const &p2{Traction.pPoint2};
|
||||
auto const &p3{Traction.pPoint3};
|
||||
auto const &p4{Traction.pPoint4};
|
||||
changes.push_back({tokens[24].begin, tokens[24].end, number((p3.y - p1.y + p4.y - p2.y) * 0.5 - Traction.fHeightDifference)});
|
||||
if (Traction.iNumSections > 0)
|
||||
{
|
||||
// the load truncates length / segment length, the margin keeps the rounding of the number from losing a section
|
||||
changes.push_back({tokens[25].begin, tokens[25].end, number(glm::length(p1 - p2) / (Traction.iNumSections + 0.001))});
|
||||
}
|
||||
apply(Text, changes);
|
||||
return true;
|
||||
}
|
||||
|
||||
// definition layout: node <max> <min> <name> eventlauncher <x> <y> <z> <radius> ... end
|
||||
bool patch_launcher(std::string &Text, glm::dvec3 const &Location, std::string &Error)
|
||||
{
|
||||
auto const tokens{tokenize(Text)};
|
||||
if (tokens.size() < 9 || tokens[0].text != "node" || tokens[4].text != "eventlauncher")
|
||||
{
|
||||
Error = "unexpected layout of event launcher definition";
|
||||
return false;
|
||||
}
|
||||
apply(Text, {{tokens[5].begin, tokens[5].end, number(Location.x)}, {tokens[6].begin, tokens[6].end, number(Location.y)}, {tokens[7].begin, tokens[7].end, number(Location.z)}});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string detect_eol(std::string const &Text, std::string const &Fallback)
|
||||
{
|
||||
auto const linebreak{Text.find('\n')};
|
||||
@@ -732,6 +783,66 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
return fail(state.error);
|
||||
}
|
||||
}
|
||||
auto const push_edit = [&](layer_handle const Layer, source_span const &Span, std::string Text, basic_node const *Node) {
|
||||
file_patch::edit edit;
|
||||
edit.begin = Span.begin;
|
||||
edit.end = Span.end;
|
||||
edit.length = Text.size();
|
||||
edit.text = std::move(Text);
|
||||
edit.node = Node;
|
||||
state.patches[Layer].edits.emplace_back(std::move(edit));
|
||||
};
|
||||
// traction and event launchers are rewritten when the editor moved them along with the track they belong to
|
||||
std::vector<basic_node *> patchednodes;
|
||||
auto const patch_node = [&](basic_node *Node, auto const &Patch) {
|
||||
auto const lookup{m_sources.find(Node)};
|
||||
if (lookup == m_sources.end() || false == Node->dirty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
auto const &source{lookup->second};
|
||||
if (false == is_output(resolve(source.layer)) || false == writable(source.layer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (false == load(source.layer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
auto const &content{state.content[source.layer]};
|
||||
if (source.span.end > static_cast<std::streamoff>(content.size()))
|
||||
{
|
||||
state.error = "definition of \"" + Node->name() + "\" is out of bounds of file \"" + layer(source.layer).name + "\"";
|
||||
return false;
|
||||
}
|
||||
auto text{content.substr(static_cast<std::size_t>(source.span.begin), static_cast<std::size_t>(source.span.end - source.span.begin))};
|
||||
std::string error;
|
||||
if (false == Patch(text, source, error))
|
||||
{
|
||||
state.error = error + " of \"" + Node->name() + "\" in file \"" + layer(source.layer).name + "\"";
|
||||
return false;
|
||||
}
|
||||
push_edit(source.layer, source.span, std::move(text), Node);
|
||||
rewritten.emplace(Node);
|
||||
patchednodes.push_back(Node);
|
||||
return true;
|
||||
};
|
||||
for (auto *traction : simulation::Traction.sequence())
|
||||
{
|
||||
if (traction != nullptr &&
|
||||
false == patch_node(traction, [&](std::string &Text, node_source const &Source, std::string &Error) { return patch_traction(Text, *traction, Source.context.offset, Error); }))
|
||||
{
|
||||
return fail(state.error);
|
||||
}
|
||||
}
|
||||
for (auto *launcher : simulation::Events.launchers())
|
||||
{
|
||||
if (launcher != nullptr &&
|
||||
false == patch_node(launcher, [&](std::string &Text, node_source const &Source, std::string &Error) { return patch_launcher(Text, Source.context.to_local(launcher->location()), Error); }))
|
||||
{
|
||||
return fail(state.error);
|
||||
}
|
||||
}
|
||||
std::vector<TTrack *> savedpaths;
|
||||
std::vector<TTrack const *> droppedpaths;
|
||||
auto const path_text = [](TTrack &Path, glm::dvec3 const &Offset) {
|
||||
@@ -778,21 +889,16 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
{
|
||||
return fail(state.error);
|
||||
}
|
||||
file_patch::edit edit;
|
||||
edit.begin = source.span.begin;
|
||||
edit.end = source.span.end;
|
||||
if (path->m_editorremoved)
|
||||
{
|
||||
push_edit(source.layer, source.span, {}, nullptr);
|
||||
droppedpaths.push_back(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
edit.text = path_text(*path, source.context.offset);
|
||||
edit.length = edit.text.size();
|
||||
edit.node = path;
|
||||
push_edit(source.layer, source.span, path_text(*path, source.context.offset), path);
|
||||
savedpaths.push_back(path);
|
||||
}
|
||||
state.patches[source.layer].edits.emplace_back(std::move(edit));
|
||||
rewritten.emplace(path);
|
||||
}
|
||||
// roads. the lanes aren't saved, they're generated from the definition of the road
|
||||
@@ -994,6 +1100,33 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
state.patches[erased.first].edits.emplace_back(std::move(edit));
|
||||
}
|
||||
|
||||
// editor data in the comment lines: the old lines go, the new ones are added to the file like new definitions
|
||||
std::map<layer_handle, std::vector<std::string>> markedtext;
|
||||
for (auto const &marking : m_marking)
|
||||
{
|
||||
auto const markedlayer{marking.first.first};
|
||||
auto const &mark{marking.first.second};
|
||||
if (auto const lookup{m_marked.find(markedlayer)}; lookup != m_marked.end())
|
||||
{
|
||||
for (auto const &line : lookup->second)
|
||||
{
|
||||
if (line.mark == mark)
|
||||
{
|
||||
push_edit(markedlayer, line.span, {}, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const target{resolve(markedlayer)};
|
||||
if (marking.second.empty() || false == is_output(target))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (auto const &text : marking.second)
|
||||
{
|
||||
markedtext[target].push_back(mark + ' ' + text);
|
||||
}
|
||||
}
|
||||
|
||||
// includes of templates
|
||||
std::map<layer_handle, std::vector<instance_handle>> placed; // directives made in the editor, by target layer
|
||||
for (std::size_t idx = 0; idx < m_instances.size(); ++idx)
|
||||
@@ -1133,7 +1266,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
continue;
|
||||
}
|
||||
if (layer(candidate).created || state.patches.count(candidate) != 0 || appended.count(candidate) != 0 || created.count(candidate) != 0 || placed.count(candidate) != 0 ||
|
||||
(candidate == root && false == Rootstatements.empty()))
|
||||
markedtext.count(candidate) != 0 || (candidate == root && false == Rootstatements.empty()))
|
||||
{
|
||||
outputs.emplace_back(candidate);
|
||||
}
|
||||
@@ -1227,6 +1360,14 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
added.text += directive + eol;
|
||||
}
|
||||
}
|
||||
if (auto const lookup{markedtext.find(output)}; lookup != markedtext.end())
|
||||
{
|
||||
for (auto const &line : lookup->second)
|
||||
{
|
||||
ensure_newline(added.text, eol);
|
||||
added.text += line + eol;
|
||||
}
|
||||
}
|
||||
if (output == root)
|
||||
{
|
||||
for (auto const &statement : Rootstatements)
|
||||
@@ -1414,7 +1555,17 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
for (auto const output : written)
|
||||
{
|
||||
stat(output);
|
||||
scan(output);
|
||||
}
|
||||
for (auto const &marking : m_marking)
|
||||
{
|
||||
if (resolve(marking.first.first) != marking.first.first)
|
||||
{
|
||||
// the lines of the merged layer are now in the file of its target
|
||||
m_marked.erase(marking.first.first);
|
||||
}
|
||||
}
|
||||
m_marking.clear();
|
||||
for (auto &included : m_instances)
|
||||
{
|
||||
// removed includes are gone from the files now
|
||||
@@ -1425,6 +1576,10 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
|
||||
{
|
||||
path->m_dirty = false;
|
||||
}
|
||||
for (auto *node : patchednodes)
|
||||
{
|
||||
node->m_dirty = false;
|
||||
}
|
||||
for (auto const *path : droppedpaths)
|
||||
{
|
||||
m_sources.erase(path);
|
||||
|
||||
Reference in New Issue
Block a user