17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-24 19:11:52 +02:00

First implementation of graph vehicles

This commit is contained in:
2026-10-14 12:20:49 +02:00
parent 70abf879b3
commit 1ef465264c
121 changed files with 12292 additions and 729 deletions

View File

@@ -10,8 +10,10 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "vehicle/graph/graph_format.h"
#include <algorithm>
#include <fstream>
#include <iterator>
#include <map>
#include <set>
#include <nlohmann/json.hpp>
@@ -57,6 +59,32 @@ bool read_value(json const &Value, param_value &Result)
Result = param_value::make_text(Value.get<std::string>());
return true;
}
if (Value.is_array())
{
// table: array of rows, each row an array of numbers
std::vector<std::vector<double>> rows;
rows.reserve(Value.size());
for (auto const &row : Value)
{
if (false == row.is_array())
{
return false;
}
std::vector<double> cells;
cells.reserve(row.size());
for (auto const &cell : row)
{
if (false == cell.is_number())
{
return false;
}
cells.push_back(cell.get<double>());
}
rows.emplace_back(std::move(cells));
}
Result = param_value::make_table(std::move(rows));
return true;
}
return false;
}
@@ -71,7 +99,7 @@ void read_values(json const &Object, std::vector<named_value> &Values, std::vect
}
else
{
report(Diagnostics, severity::error, Node, What + " \"" + item.key() + "\" has to be a number, a boolean or a text");
report(Diagnostics, severity::error, Node, What + " \"" + item.key() + "\" has to be a number, a boolean, a text or a table (array of rows of numbers)");
}
}
}
@@ -195,6 +223,7 @@ bool parse_document_(std::string const &Text, document &Document, std::vector<di
node.id = item.value("id", "");
node.type = item.value("type", "");
node.title = item.value("title", "");
node.script = item.value("lua", "");
if (false == is_valid_id(node.id))
{
report(Diagnostics, severity::error, node.id, "node id has to be made of letters, digits, '_' and '-'");
@@ -253,6 +282,224 @@ bool parse_document_(std::string const &Text, document &Document, std::vector<di
} // namespace
namespace
{
struct endpoint
{
std::string node;
std::string port;
};
std::string directory_of(std::string const &Path)
{
auto const separator{Path.find_last_of("/\\")};
return separator == std::string::npos ? std::string{} : Path.substr(0, separator + 1);
}
bool expand_modules_(document &Document, std::string const &Directory, std::vector<diagnostic> &Diagnostics, std::vector<std::string> &Chain)
{
auto ok{true};
std::vector<node_desc> nodes;
std::vector<connection_desc> connections{Document.connections};
for (auto &node : Document.nodes)
{
if (node.type != module_type)
{
nodes.emplace_back(std::move(node));
continue;
}
std::string file;
for (auto const &param : node.params)
{
if (param.first == "file")
{
file = param.second.text;
}
else
{
report(Diagnostics, severity::warning, node.id, "unknown parameter \"" + param.first + "\" of the module was ignored");
}
}
std::replace(std::begin(file), std::end(file), '\\', '/');
if (file.empty())
{
report(Diagnostics, severity::error, node.id, "module without a file");
ok = false;
continue;
}
auto const path{(file.front() == '/' || (file.size() > 1 && file[1] == ':')) ? file : Directory + file};
if (std::find(std::begin(Chain), std::end(Chain), path) != std::end(Chain) || Chain.size() >= 16)
{
report(Diagnostics, severity::error, node.id, "module \"" + file + "\" uses itself");
ok = false;
continue;
}
// problems of the module file are reported under the module node
std::vector<diagnostic> problems;
document module;
auto loaded{load_document(path, module, problems)};
if (loaded)
{
Chain.push_back(path);
loaded = expand_modules_(module, directory_of(path), problems, Chain);
Chain.pop_back();
}
if (false == module.body.empty() || false == module.profiles.empty())
{
problems.push_back({severity::warning, "", "the body and the profiles of a module are ignored"});
}
for (auto const &problem : problems)
{
report(Diagnostics, problem.level, node.id + (problem.node.empty() ? "" : "/" + problem.node), "module \"" + file + "\": " + problem.message);
}
if (false == loaded)
{
ok = false;
continue;
}
auto const prefix{node.id + "/"};
std::set<std::string> inputs, outputs;
std::map<std::string, node_desc> inputnodes;
for (auto &inner : module.nodes)
{
// the interface of this module; blocks of the modules it uses have ids with '/', and an input block kept
// by such a module (for its unconnected input) isn't an input of this one
auto const own{inner.id.find('/') == std::string::npos};
if (own && inner.type == "module.input")
{
inputs.insert(inner.id);
inputnodes.emplace(inner.id, inner);
}
else if (own && inner.type == "module.output")
{
outputs.insert(inner.id);
}
else
{
inner.id = prefix + inner.id;
nodes.emplace_back(std::move(inner));
}
}
// connections inside the module
std::map<std::string, std::vector<endpoint>> inputtargets; // input of the module -> inputs of its blocks
std::map<std::string, endpoint> outputsources; // output of the module -> output of a block, or "input" node for a passed input
for (auto const &inner : module.connections)
{
auto const frominput{inputs.count(inner.from_node) > 0};
if (outputs.count(inner.to_node) > 0)
{
outputsources[inner.to_node] = frominput ? endpoint{inner.from_node, ""} : endpoint{prefix + inner.from_node, inner.from_port};
}
else if (frominput)
{
inputtargets[inner.from_node].push_back({prefix + inner.to_node, inner.to_port});
}
else
{
connections.push_back({prefix + inner.from_node, inner.from_port, prefix + inner.to_node, inner.to_port});
}
}
// connections of the module node in the graph using it
std::map<std::string, endpoint> externalsources; // input of the module -> what feeds it
std::map<std::string, std::vector<endpoint>> externaltargets; // output of the module -> what it feeds
for (auto it = std::begin(connections); it != std::end(connections);)
{
if (it->to_node == node.id)
{
if (inputs.count(it->to_port) == 0)
{
report(Diagnostics, severity::error, node.id, "module \"" + file + "\" has no input \"" + it->to_port + "\"");
ok = false;
}
externalsources[it->to_port] = {it->from_node, it->from_port};
it = connections.erase(it);
}
else if (it->from_node == node.id)
{
if (outputs.count(it->from_port) == 0)
{
report(Diagnostics, severity::error, node.id, "module \"" + file + "\" has no output \"" + it->from_port + "\"");
ok = false;
}
externaltargets[it->from_port].push_back({it->to_node, it->to_port});
it = connections.erase(it);
}
else
{
++it;
}
}
// an input fed from an output of the same module (a loop through the module) is fed by the block inside it
for (auto &source : externalsources)
{
if (source.second.node != node.id)
{
continue;
}
auto const inner{outputsources.find(source.second.port)};
if (inner != std::end(outputsources) && false == inner->second.port.empty())
{
source.second = inner->second;
}
}
// an input without a connection keeps its interface block, which gives the default value of the input
auto const source_of = [&](std::string const &Input) -> endpoint {
auto const source{externalsources.find(Input)};
if (source != std::end(externalsources))
{
return source->second;
}
auto const id{prefix + Input};
if (std::none_of(std::begin(nodes), std::end(nodes), [&](node_desc const &Node) { return Node.id == id; }))
{
auto interfacenode{inputnodes[Input]};
interfacenode.id = id;
nodes.emplace_back(std::move(interfacenode));
}
return {id, "out"};
};
for (auto const &input : inputtargets)
{
auto const source{source_of(input.first)};
for (auto const &target : input.second)
{
connections.push_back({source.node, source.port, target.node, target.port});
}
}
for (auto const &output : externaltargets)
{
auto source{outputsources.find(output.first)};
if (source == std::end(outputsources))
{
continue;
}
auto from{source->second};
if (from.port.empty())
{
// an input passed straight to an output
from = source_of(from.node);
}
for (auto const &target : output.second)
{
connections.push_back({from.node, from.port, target.node, target.port});
}
}
}
Document.nodes = std::move(nodes);
Document.connections = std::move(connections);
return ok;
}
} // namespace
bool expand_modules(document &Document, std::string const &Directory, std::vector<diagnostic> &Diagnostics)
{
std::vector<std::string> chain;
return expand_modules_(Document, Directory, Diagnostics, chain);
}
bool parse_document(std::string const &Text, document &Document, std::vector<diagnostic> &Diagnostics)
{
try