17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-04 23:35:43 +01:00

feat(editor): include database, placing .inc templates from the node bank

This commit is contained in:
2026-10-02 23:57:35 +02:00
parent a612b0c96c
commit 968594c3e9
20 changed files with 1106 additions and 168 deletions

View File

@@ -114,6 +114,8 @@ void node_layers::clear()
m_sources.clear();
m_erased.clear();
m_templates.clear();
m_instances.clear();
m_instance = 0;
m_terraindirective = false;
}
@@ -208,7 +210,8 @@ void node_layers::visible(layer_handle Layer, bool const Visible)
}
if (Visible)
{
if (Node->m_layerhidden)
// nodes of a removed include stay out of sight
if (Node->m_layerhidden && (false == tracked(Node->m_instance) || false == instance(Node->m_instance).removed))
{
Node->visible(true);
Node->m_layerhidden = false;
@@ -296,7 +299,7 @@ bool node_layers::editable(basic_node const *Node, std::string *Reason) const
return true;
}
std::string reason;
if (Node->m_template)
if (Node->from_template())
{
reason = "it's a part of an include template (.inc)";
}
@@ -361,7 +364,7 @@ void node_layers::show_all()
// stores origin of a node defined directly in a layer file, to allow rewriting its definition on save
void node_layers::track(basic_node const *Node, source_span const &Span, glm::vec3 const &Angles, glm::vec3 const &Scale)
{
if (Node == nullptr || Node->m_template || false == valid(Node->layer()) || false == Span.valid())
if (Node == nullptr || Node->from_template() || false == valid(Node->layer()) || false == Span.valid())
{
return;
}
@@ -606,6 +609,18 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
Reason = "the target layer is included by the merged one";
return false;
}
if (false == source.context_end.matches(target.context_end))
{
// directives of includes placed in the editor are prepared for the end of their layer file
auto const unsaved{std::any_of(std::begin(m_instances), std::end(m_instances), [&](include_instance const &Instance) {
return false == Instance.dead && false == Instance.removed && false == Instance.directive.empty() && resolve(Instance.layer) == Source;
})};
if (unsaved)
{
Reason = "the merged layer has includes which weren't saved yet, save the scenery first";
return false;
}
}
if (source.created)
{
// nothing but the nodes made in the editor to move
@@ -667,6 +682,115 @@ 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); });
}
// indicates start of content of a template, included by directive at specified location of the layer file being loaded
void node_layers::instance_begin(std::string const &File, source_span const &Span)
{
if (m_instances.size() + 1 >= untracked_instance)
{
m_instance = untracked_instance;
return;
}
m_instances.emplace_back();
auto &included{m_instances.back()};
included.layer = handle();
included.file = &(*m_templates.emplace(File).first);
included.span = Span;
m_instance = static_cast<instance_handle>(m_instances.size());
}
// true if specified include can be removed in the editor. optionally explains why it can't
bool node_layers::removable(instance_handle Instance, std::string *Reason) const
{
std::string reason;
if (false == tracked(Instance) || instance(Instance).dead)
{
reason = "it's a part of an include template (.inc)";
}
else
{
auto const includelayer{resolve(instance(Instance).layer)};
if (false == valid(includelayer) || layer(includelayer).removed)
{
reason = "its layer is no longer a part of the scenery";
}
else if (false == writable(includelayer, &reason))
{
// reason was filled by the call
}
else if (layer(includelayer).locked)
{
reason = "its layer is locked";
}
}
if (false == reason.empty() && Reason != nullptr)
{
*Reason = reason;
}
return reason.empty();
}
// drops specified include from the scenery, or brings it back
void node_layers::removed(instance_handle Instance, bool Removed)
{
if (false == tracked(Instance) || m_instances[Instance - 1].dead || m_instances[Instance - 1].removed == Removed)
{
return;
}
m_instances[Instance - 1].removed = Removed;
auto const includelayer{resolve(m_instances[Instance - 1].layer)};
auto const layervisible{false == valid(includelayer) || layer(includelayer).visible};
auto const update_node = [=, this](basic_node *Node, layer_item const Item) {
if (Node == nullptr || Node->m_instance != Instance)
{
return;
}
count(Node->layer(), Item, Removed ? -1 : 1);
if (Removed)
{
if (Node->visible())
{
Node->visible(false);
Node->m_layerhidden = true;
}
}
else if (Node->m_layerhidden && layervisible)
{
Node->visible(true);
Node->m_layerhidden = false;
}
};
for (auto *modelinstance : simulation::Instances.sequence())
{
update_node(modelinstance, layer_item::model);
}
for (auto *path : simulation::Paths.sequence())
{
update_node(path, layer_item::track);
}
for (auto *traction : simulation::Traction.sequence())
{
update_node(traction, layer_item::traction);
}
}
// registers include directive made in the editor, to be written to the file of specified layer on save
instance_handle node_layers::place(layer_handle Layer, std::string const &File, std::string const &Directive)
{
Layer = resolve(Layer);
if (false == valid(Layer) || m_instances.size() + 1 >= untracked_instance)
{
return 0;
}
m_instances.emplace_back();
auto &included{m_instances.back()};
included.layer = Layer;
included.file = &(*m_templates.emplace(File).first);
included.directive = Directive;
included.context = layer(Layer).context_end;
return static_cast<instance_handle>(m_instances.size());
}
} // namespace scene
//---------------------------------------------------------------------------

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "scene/scenenode.h"
#include <filesystem>
#include <limits>
#include <set>
namespace scene
@@ -93,6 +94,21 @@ struct basic_layer
std::size_t item_count() const;
};
// include directive bringing an *.inc template into a scenery layer file
struct include_instance
{
layer_handle layer{null_handle}; // layer of the file holding the directive
std::string const *file{nullptr}; // name of the included template
source_span span; // location of the whole directive in the file; invalid for directive which wasn't saved yet
std::string directive; // text of the directive, for include placed in the editor and not saved yet
layer_context context; // placement the directive of include placed in the editor was prepared for
bool removed{false}; // the include is to be dropped from the scenery
bool dead{false}; // removal of the include was saved
};
// marks nodes defined by a template which was included in a way the editor doesn't keep track of
instance_handle const untracked_instance{std::numeric_limits<instance_handle>::max()};
// origin of a node defined in a scenery layer file, with the placement it was loaded with
struct node_source
{
@@ -197,6 +213,18 @@ class node_layers
{
return m_templates;
}
// indicates start of content of a template, included by directive at specified location of the layer file being loaded
void instance_begin(std::string const &File, source_span const &Span);
// indicates end of content of the template
void instance_end()
{
m_instance = 0;
}
// include whose template is being loaded, 0 if there's none
instance_handle instance() const
{
return m_instance;
}
// updates placement in effect for the statements which follow
void context(layer_context const &Context)
{
@@ -229,6 +257,26 @@ class node_layers
bool merge(layer_handle Source, layer_handle Target);
// true if there are layer changes awaiting save. NOTE: doesn't account for modified nodes
bool pending() const;
// includes of *.inc templates. the editor handles each as a whole: it can be placed and removed, but not taken apart
// true if specified handle stands for an include the editor keeps track of
bool tracked(instance_handle const Instance) const
{
return Instance != 0 && Instance <= m_instances.size();
}
// grants access to specified include. NOTE: the include has to be tracked
include_instance const &instance(instance_handle const Instance) const
{
return m_instances[Instance - 1];
}
// true if specified include can be removed in the editor. optionally explains why it can't
bool removable(instance_handle Instance, std::string *Reason = nullptr) const;
// drops specified include from the scenery, or brings it back. its nodes are hidden right away, the directive
// is erased from the layer file on save; what else the template defines stays in the scene until the next load
void removed(instance_handle Instance, bool Removed);
// registers include directive made in the editor, to be written to the file of specified layer on save.
// the directive has to be prepared for the placement in effect at the end of that file. returns: handle to the include
instance_handle place(layer_handle Layer, std::string const &File, std::string const &Directive);
// true if specified layer is included, directly or not, by the other one
bool is_descendant(layer_handle Layer, layer_handle const Ancestor) const;
@@ -254,6 +302,8 @@ class node_layers
std::unordered_map<basic_node const *, node_source> m_sources; // origins of nodes which can be rewritten on save
std::vector<std::pair<layer_handle, source_span>> m_erased; // definitions of nodes deleted since the load or the last save
std::set<std::string> m_templates; // *.inc templates used by the scenery
std::vector<include_instance> m_instances; // includes of the templates; instance handle is index in the vector + 1
instance_handle m_instance{0}; // helper, include whose template is being loaded
bool m_terraindirective{false};
};

View File

@@ -294,9 +294,15 @@ struct node_layers::composition
layer_handle layer;
source_span span;
};
struct instance_place
{
instance_handle instance;
source_span span;
};
std::string text;
std::vector<node_place> nodes;
std::vector<site_place> sites;
std::vector<instance_place> instances;
// adds another piece of text at the end
void append(composition const &Other)
@@ -315,6 +321,12 @@ struct node_layers::composition
place.span.end += offset;
sites.emplace_back(place);
}
for (auto place : Other.instances)
{
place.span.begin += offset;
place.span.end += offset;
instances.emplace_back(place);
}
}
};
@@ -324,6 +336,7 @@ struct node_layers::save_state
std::map<layer_handle, file_patch> patches;
std::map<layer_handle, std::vector<basic_node const *>> nodes; // unchanged nodes defined in each file
std::map<layer_handle, std::vector<layer_handle>> includes; // unchanged include directives in each file, by included layer
std::map<layer_handle, std::vector<instance_handle>> instances; // unchanged includes of templates in each file
std::string eol{"\r\n"};
std::string error;
};
@@ -477,6 +490,13 @@ bool node_layers::compose(save_state &State, layer_handle const Layer, compositi
Output.sites.push_back({included, shift(layer(included).sites.front().span)});
}
}
if (auto const lookup{State.instances.find(Layer)}; lookup != State.instances.end())
{
for (auto const included : lookup->second)
{
Output.instances.push_back({included, shift(instance(included).span)});
}
}
return true;
}
@@ -608,7 +628,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
state.patches[source.layer].edits.emplace_back(std::move(edit));
rewritten.emplace(Node);
}
else if (Model != nullptr && false == Node->m_template)
else if (Model != nullptr && false == Node->from_template())
{
// created in the editor. the definition is placed at the end of the layer file, with the placement in effect there
std::string text;
@@ -654,6 +674,41 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
state.patches[erased.first].edits.emplace_back(std::move(edit));
}
// 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)
{
auto const &included{m_instances[idx]};
if (included.dead)
{
continue;
}
if (included.span.valid())
{
if (included.removed)
{
file_patch::edit edit;
edit.begin = included.span.begin;
edit.end = included.span.end;
state.patches[included.layer].edits.emplace_back(std::move(edit));
}
}
else if (false == included.removed && false == included.directive.empty())
{
auto const target{resolve(included.layer)};
if (false == is_output(target))
{
continue;
}
if (false == included.context.matches(layer(target).context_end))
{
// shouldn't happen, merge of layers is refused when it'd lead to this
return fail("include \"" + *included.file + "\" was placed for different origin, rotation or scale than its layer ends with");
}
placed[target].emplace_back(static_cast<instance_handle>(idx + 1));
}
}
// layer changes
std::map<layer_handle, std::vector<layer_handle>> appended; // merged layers placed at the end of their target
std::set<layer_handle> absorbed; // merged layers which cease to exist as files
@@ -748,7 +803,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 ||
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()))
{
outputs.emplace_back(candidate);
@@ -782,6 +837,14 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
}
state.includes[included.sites.front().parent].emplace_back(candidate);
}
for (std::size_t idx = 0; idx < m_instances.size(); ++idx)
{
auto const &included{m_instances[idx]};
if (false == included.dead && false == included.removed && included.span.valid() && state.content.count(included.layer) != 0)
{
state.instances[included.layer].emplace_back(static_cast<instance_handle>(idx + 1));
}
}
std::map<layer_handle, composition> texts;
for (auto const output : outputs)
@@ -822,6 +885,17 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
text.text += definition.second + eol;
}
}
if (auto const lookup{placed.find(output)}; lookup != placed.end())
{
for (auto const included : lookup->second)
{
auto const &directive{instance(included).directive};
ensure_newline(text.text, eol);
auto const offset{static_cast<std::streamoff>(text.text.size())};
text.instances.push_back({included, {offset, offset + static_cast<std::streamoff>(directive.size())}});
text.text += directive + eol;
}
}
if (output == root)
{
for (auto const &statement : Rootstatements)
@@ -927,6 +1001,13 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
source.scale = (model != nullptr ? model->Scale() : glm::vec3{1.f});
}
}
for (auto const &place : texts[output].instances)
{
auto &included{m_instances[place.instance - 1]};
included.layer = output;
included.span = place.span;
included.directive.clear();
}
for (auto const &place : texts[output].sites)
{
auto &included{layer(place.layer)};
@@ -961,6 +1042,11 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
{
stat(output);
}
for (auto &included : m_instances)
{
// removed includes are gone from the files now
included.dead = included.dead || included.removed;
}
m_erased.clear();
if (removedterrain)
{

View File

@@ -718,8 +718,8 @@ memory_node::deserialize( cParser &Input, node_data const &Nodedata ) {
basic_node::basic_node( scene::node_data const &Nodedata ) :
m_name( Nodedata.name ),
m_template( Nodedata.is_template ),
m_layer( Nodedata.layer )
m_layer( Nodedata.layer ),
m_instance( Nodedata.instance )
{
uuid = UID::random();
node_type = Nodedata.type;

View File

@@ -71,7 +71,7 @@ struct node_data {
std::string name;
std::string type;
layer_handle layer { null_handle }; // scenery layer the node is defined in, if any
bool is_template { false }; // the node is defined by an *.inc template rather than directly by a scenery layer file
instance_handle instance { 0 }; // include of an *.inc template which defines the node, if it isn't defined directly by a scenery layer file
};
// holds unique piece of geometry, covered with single material
@@ -365,6 +365,9 @@ public:
layer( scene::layer_handle Layer );
scene::layer_handle
layer() const;
// true if the node is defined by an *.inc template
bool
from_template() const { return m_instance != 0; }
void
mark_dirty() { m_dirty = true; }
bool
@@ -381,9 +384,11 @@ public:
bool m_visible { true }; // visibility flag
std::string m_name;
bool m_dirty { false };
bool m_layerhidden { false }; // the node was visible until its scenery layer was hidden
bool m_template { false }; // the node is defined by an *.inc template, which the editor can't rewrite. set only for scenery opened for editing
bool m_layerhidden { false }; // the node was visible until the editor hid its scenery layer, or the include which defines it
scene::layer_handle m_layer { null_handle }; // scenery layer this node belongs to, if any
// include of an *.inc template which defines the node; 0 if the node is defined directly by a scenery layer file.
// such nodes can't be rewritten one by one on scenery save. set only for scenery opened for editing
scene::instance_handle m_instance { 0 };
UID uuid;
private: