17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 23:49:19 +02:00
Files
maszyna/scene/scenenodegroups.cpp
maj00r c15e200e37 Merge the track layout editor into cpp20-modules, ported to modules
The editor branch was written against headers and the PCH, both of which this
branch has done away with, so its own files were ported rather than merged: the
five headers it added are now interface units and their .cpp files module
implementation units.

- editor/{puwg1992,polandmap,turnouts,orthophoto} become eu07.editor.* modules,
  application/planpanel becomes eu07.application.planpanel. The plan tool keeps
  to its own module rather than joining the eu07.simcore partitions: it stands on
  the editor's libraries and eu07.application.uilayer, not on the simulation core,
  so :editoruilayer can export import it without a cycle.
- glm arrives by import eu07.glm, never by include, as the rest of the tree does.
  maj0sted is header-only and glm-free, so its headers stay includes in the global
  module fragment - every unit that names those types includes them itself, since
  a global fragment is not visible across units.
- the one textual conflict was :editoruilayer's include block against this
  branch's imports; resolved this branch's way plus the planpanel import.

Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF) and starts into the editor on
empty ground with -editor alone.
2026-09-13 12:36:21 +02:00

361 lines
11 KiB
C++

module;
#include <array>
#include <chrono>
#include <deque>
#include <limits>
#include <mutex>
#include <sstream>
#include <type_traits>
#include <stack>
#include <vector>
#include <algorithm>
#include "utilities/Globals_macros.h"
#include <cstddef>
#include <iterator>
#include <memory>
#include <ostream>
#include <string>
#include <typeinfo>
#include <unordered_map>
#include <utility>
#include "global_include/interfaces/ITexture_macros.h"
module eu07.scene.scenenodegroups;
import eu07.glm;
import eu07.simulation.simulation;
import eu07.utilities.globals;
import eu07.simcore;
import eu07.widgets.map_objects;
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
namespace scene {
node_groups Groups;
// requests creation of a new node group. returns: handle to the group
scene::group_handle
node_groups::create() {
m_activegroup.push( create_handle() );
return handle();
}
// indicates creation of current group ended. returns: handle to the parent group or null_handle if group stack is empty
scene::group_handle
node_groups::close()
{
if( false == m_activegroup.empty() ) {
auto const closinggroup { m_activegroup.top() };
m_activegroup.pop();
// if the completed group holds only one item and there's no chance more items will be added, disband it
if( true == m_activegroup.empty()
|| m_activegroup.top() != closinggroup ) {
auto lookup { m_groupmap.find( closinggroup ) };
if( lookup != m_groupmap.end()
&& lookup->second.nodes.size() + lookup->second.events.size() <= 1 ) {
erase( lookup );
}
}
}
return handle();
}
void
node_groups::clear()
{
// nodes/events are owned by the region (or already gone with it); do not dereference them
m_groupmap.clear();
while( false == m_activegroup.empty() ) {
m_activegroup.pop();
}
}
bool node_groups::assign_cross_switch(map::track_switch& sw, std::string &sw_name, std::string const &id, size_t idx)
{
sw.action[idx] = simulation::Events.FindEvent(sw_name + ":" + id);
if (!sw.action[idx])
sw.action[idx] = simulation::Events.FindEvent(sw_name + id);
if (!sw.action[idx])
return false;
multi_event *multi = dynamic_cast<multi_event*>(sw.action[idx]);
if (!multi)
return false;
auto names = multi->dump_children_names();
for (auto it = names.begin(); it != names.end(); it++) {
*it = it->substr(sw_name.size());
if (it->size() > 4)
continue;
int pos_a = it->find_last_of('a');
int pos_b = it->find_last_of('b');
int pos_c = it->find_last_of('c');
int pos_d = it->find_last_of('d');
int pos_0 = it->find_last_of('0');
int pos_1 = it->find_last_of('1');
int pos;
if (pos_a > pos_b && pos_a > pos_c && pos_a > pos_d)
pos = 0;
else if (pos_b > pos_a && pos_b > pos_c && pos_b > pos_d)
pos = 1;
else if (pos_c > pos_a && pos_c > pos_b && pos_c > pos_d)
pos = 2;
else
pos = 3;
sw.preview[idx][pos] = pos_0 > pos_1 ? '0' : '1';
}
return true;
}
void
node_groups::update_map()
{
map::Objects.entries.clear();
std::unordered_map<std::string, std::shared_ptr<map::track_switch>> last_switch_map;
for (auto const &pair : m_groupmap) {
auto const &group = pair.second;
for (basic_node *node : group.nodes) {
std::string postfix { "_sem_mem" };
if (typeid(*node) == typeid(TMemCell) && node->name().ends_with(postfix)) {
std::string sem_name = node->name().substr(0, node->name().length() - postfix.length());
auto sem_info = std::make_shared<map::semaphore>();
map::Objects.entries.push_back(sem_info);
sem_info->location = node->location();
sem_info->memcell = static_cast<TMemCell*>(node);
sem_info->name = sem_name;
for (basic_event *event : group.events) {
if (event->name().starts_with(sem_name)
&& event->name().substr(sem_name.length()).find("sem") == std::string::npos) {
sem_info->events.push_back(event);
}
}
for (basic_node *node : group.nodes)
if (auto *model = dynamic_cast<TAnimModel*>(node))
if (model->name().starts_with(sem_name))
sem_info->models.push_back(model);
}
if (Global.map_manualswitchcontrol) {
if (TTrack *track = dynamic_cast<TTrack*>(node)) {
if (track->eType != tt_Switch)
continue;
basic_event *sw_p = simulation::Events.FindEvent(track->name() + "+");
basic_event *sw_m = simulation::Events.FindEvent(track->name() + "-");
if (sw_p && sw_m) {
auto map_launcher = std::make_shared<map::track_switch>();
map::Objects.entries.push_back(map_launcher);
map_launcher->location = node->location();
map_launcher->name = node->name();
map_launcher->action[0] = sw_p;
map_launcher->action[1] = sw_m;
map_launcher->track[0] = track;
map_launcher->preview[0][0] = '0';
map_launcher->preview[1][0] = '1';
continue;
}
std::string sw_name = track->name();
if (sw_name.size() <= 2)
continue;
char lastc = sw_name.back();
sw_name.pop_back();
if (sw_name.back() == '_')
sw_name.pop_back();
if (!(simulation::Events.FindEvent(sw_name + ":ac") || simulation::Events.FindEvent(sw_name + "ac")))
continue;
std::shared_ptr<map::track_switch> last_switch;
auto it = last_switch_map.find(sw_name);
if (it != last_switch_map.end()) {
last_switch = it->second;
} else {
last_switch = std::make_shared<map::track_switch>();
last_switch->name = sw_name;
last_switch_map.insert(std::make_pair(sw_name, last_switch));
}
if (lastc < 'a' || lastc > 'd')
continue;
last_switch->track[lastc - 'a'] = track;
for (auto trk : last_switch->track)
if (!trk)
goto skip_e;
if (!assign_cross_switch(*last_switch, sw_name, "ac", 0))
skip_e: continue;
if (!assign_cross_switch(*last_switch, sw_name, "ad", 1))
continue;
if (!assign_cross_switch(*last_switch, sw_name, "bc", 2))
continue;
if (!assign_cross_switch(*last_switch, sw_name, "bd", 3))
continue;
last_switch->location = (last_switch->track[0]->location() + last_switch->track[1]->location() + last_switch->track[2]->location() + last_switch->track[3]->location()) / 4.0;
map::Objects.entries.push_back(last_switch);
last_switch_map.erase(sw_name);
}
} else {
if (TEventLauncher *launcher = dynamic_cast<TEventLauncher*>(node)) {
if (!launcher || !launcher->Event1 || !launcher->Event2)
continue;
auto map_launcher = std::make_shared<map::launcher>();
map::Objects.entries.push_back(map_launcher);
map_launcher->location = node->location();
map_launcher->name = node->name();
map_launcher->first_event = launcher->Event1;
map_launcher->second_event = launcher->Event2;
if (map_launcher->name.empty())
map_launcher->name = launcher->Event1->name();
if (launcher->Event1->name().find_first_of("-+:") != std::string::npos)
map_launcher->type = map::launcher::track_switch;
else
map_launcher->type = map::launcher::level_crossing;
}
}
}
}
}
// returns current active group, or null_handle if group stack is empty
group_handle
node_groups::handle() const {
return m_activegroup.empty() ? null_handle : m_activegroup.top();
}
// places provided node in specified group
void
node_groups::insert( scene::group_handle const Group, scene::basic_node *Node ) {
// TBD, TODO: automatically unregister the node from its current group?
Node->group( Group );
if( Group == null_handle ) { return; }
auto &nodesequence { m_groupmap[ Group ].nodes };
if( std::find( std::begin( nodesequence ), std::end( nodesequence ), Node ) == std::end( nodesequence ) ) {
// don't add the same node twice
nodesequence.emplace_back( Node );
}
}
// places provided event in specified group
void
node_groups::insert( scene::group_handle const Group, basic_event *Event ) {
// TBD, TODO: automatically unregister the event from its current group?
Event->group( Group );
if( Group == null_handle ) { return; }
auto &eventsequence { m_groupmap[ Group ].events };
if( std::find( std::begin( eventsequence ), std::end( eventsequence ), Event ) == std::end( eventsequence ) ) {
// don't add the same node twice
eventsequence.emplace_back( Event );
}
}
// sends basic content of the class in legacy (text) format to provided stream
void
node_groups::export_as_text( std::ostream &Output, bool Dirty ) const {
for( auto const &group : m_groupmap ) {
bool any = false;
for( auto *node : group.second.nodes ) {
if (node->dirty() != Dirty)
continue;
// HACK: auto-generated memory cells aren't exported, so we check for this
// TODO: is_exportable as basic_node method
if( typeid(*node) == typeid(TMemCell)
&& false == static_cast<TMemCell *>(node)->is_exportable ) {
continue;
}
if (!any)
Output << "group\n";
any = true;
node->export_as_text( Output );
}
for( auto *event : group.second.events ) {
if (Dirty)
continue;
if (!any)
Output << "group\n";
any = true;
event->export_as_text( Output );
}
if (any)
Output << "endgroup\n";
}
}
// removes specified group from the group list and group information from the group's nodes
void
node_groups::erase( group_map::const_iterator Group ) {
for( auto *node : Group->second.nodes ) {
node->group( null_handle );
}
for( auto *event : Group->second.events ) {
event->group( null_handle );
}
m_groupmap.erase( Group );
}
// creates handle for a new group
group_handle
node_groups::create_handle() {
// NOTE: for simplification nested group structure are flattened
return m_activegroup.empty() ? m_groupmap.size() + 1 : // new group isn't created until node registration
m_activegroup.top();
}
} // scene
//---------------------------------------------------------------------------