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:
@@ -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
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user