0.4.1
Loading...
Searching...
No Matches
FlowManager.cpp
Go to the documentation of this file.
1// This file is part of INSTINCT, the INS Toolkit for Integrated
2// Navigation Concepts and Training by the Institute of Navigation of
3// the University of Stuttgart, Germany.
4//
5// This Source Code Form is subject to the terms of the Mozilla Public
6// License, v. 2.0. If a copy of the MPL was not distributed with this
7// file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
10
11#include "util/Json.hpp"
12
14namespace nm = NAV::NodeManager;
15
16#include <implot.h>
17#include <imgui_node_editor.h>
18namespace ed = ax::NodeEditor;
19
20#include "NodeRegistry.hpp"
21
23#include "internal/Node/Pin.hpp"
26
32
33#include <fstream>
34#include <set>
35#include <iomanip>
36#include <string>
37#include <memory>
38
39#include <iostream>
40
41namespace NAV::flow
42{
43namespace
44{
45
46bool unsavedChanges = false;
47
48constexpr int loadingFramesToWait = 2;
49
50std::string currentFilename;
51std::filesystem::path programRootPath;
52
53// The current number for the rotated parent folder
54size_t currentRotatedParentFolderNumber;
55
56} // namespace
57
58bool saveLastActions = true;
60
61} // namespace NAV::flow
62
64{
65 if (currentFilename.empty())
66 {
67 globalAction = GlobalActions::SaveAs;
68 }
69 else
70 {
71 SaveFlowAs(currentFilename);
72 }
73}
74
75void NAV::flow::SaveFlowAs(const std::string& filepath)
76{
77 std::ofstream filestream(filepath);
78
79 if (!filestream.good())
80 {
81 std::cerr << "Save Flow error: Could not open file: " << filepath;
82 return;
83 }
84
85 json j;
86 for (const auto& node : nm::m_Nodes())
87 {
88 j["nodes"]["node-" + std::to_string(size_t(node->id))] = *node;
89 j["nodes"]["node-" + std::to_string(size_t(node->id))]["data"] = node->save();
90
91 for (const auto& outputPin : node->outputPins)
92 {
93 for (const auto& link : outputPin.links)
94 {
95 auto& jLink = j["links"]["link-" + std::to_string(size_t(link.linkId))];
96 jLink["id"] = size_t(link.linkId);
97 jLink["startPinId"] = size_t(outputPin.id);
98 jLink["endPinId"] = size_t(link.connectedPinId);
99 }
100 }
101 }
103 {
104 j["implot"]["style"] = ImPlot::GetStyle();
105 j["implot"]["prefereFlowOverGlobal"] = gui::windows::prefereFlowOverGlobal;
106 }
107
108 j["fonts"]["useBigDefaultFont"] = gui::NodeEditorApplication::isUsingBigDefaultFont();
109 j["fonts"]["useBigWindowFont"] = gui::NodeEditorApplication::isUsingBigWindowFont();
110 j["fonts"]["useBigPanelFont"] = gui::NodeEditorApplication::isUsingBigPanelFont();
111 j["fonts"]["useBigMonoFont"] = gui::NodeEditorApplication::isUsingBigMonoFont();
112 j["leftPane"]["hide"] = gui::NodeEditorApplication::hideLeftPane;
113 j["leftPane"]["leftWidth"] = gui::NodeEditorApplication::leftPaneWidth;
114 j["leftPane"]["rightWidth"] = gui::NodeEditorApplication::rightPaneWidth;
115 j["bottomViewHeight"] = gui::NodeEditorApplication::bottomViewHeight;
117 j["lightMode"] = gui::windows::nodeEditorLightMode;
118 j["gridLinesEnabled"] = ed::GetStyle().Colors[ed::StyleColor_Grid].w;
119 j["transparentWindows"] = ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w;
120
121 // if (ImGui::Checkbox("Light mode", &nodeEditorLightMode))
122 // {
123 // ApplyDarkLightMode(NodeEditorApplication::m_colors);
124 // flow::ApplyChanges();
125 // }
126
127 j["colormaps"] = ColormapsFlow;
128 j["commonLog"] = CommonLog::save();
129
130 filestream << std::setw(4) << j << std::endl; // NOLINT(performance-avoid-endl)
131
132 unsavedChanges = false;
133}
134
135bool NAV::flow::LoadFlow(const std::string& filepath)
136{
137 LOG_TRACE("called for path {}", filepath);
138 bool loadSuccessful = true;
139
140 try
141 {
142 std::ifstream filestream(filepath);
143
144 if (!filestream.good())
145 {
146 LOG_ERROR("Load Flow error: Could not open file: {}", filepath);
147 return false;
148 }
149
151
152 json j;
153 filestream >> j;
154
155 saveLastActions = false;
156
158
159 if (!j.contains("commonLog")) { CommonLog::restore(json{}); }
160 LoadJson(j);
161
162#ifdef TESTING
163 nm::CallPreInitCallback();
164#endif
165
166 if (!ConfigManager::Get<bool>("noinit"))
167 {
168 if (ConfigManager::Get<bool>("nogui"))
169 {
171 {
172 loadSuccessful = false;
173 }
174 }
175 else
176 {
178 }
179 }
180
181 if (!ConfigManager::Get<bool>("nogui"))
182 {
183 loadingFrameCount = ImGui::GetFrameCount();
184 }
185 unsavedChanges = false;
186 saveLastActions = true;
187 currentFilename = filepath;
188
189 if (!ConfigManager::Get<bool>("nogui"))
190 {
193 }
194
195 std::string path = filepath;
196 if (path.find(GetProgramRootPath().string()) != std::string::npos)
197 {
198 path = path.substr(GetProgramRootPath().string().size());
199 if (path.starts_with('\\') || path.starts_with('/')) { path = path.substr(1); }
200 }
201
202 LOG_INFO("Loaded flow file: {}", path);
203 }
204 catch (const std::exception& e)
205 {
206 LOG_ERROR("Loading flow file failed with error: {}", e.what());
207 loadSuccessful = false;
208 }
209
210 return loadSuccessful;
211}
212
213bool NAV::flow::LoadJson(const json& j, bool requestNewIds)
214{
215 bool loadSuccessful = true;
216
217 if (j.contains("implot"))
218 {
220
221 if (j.at("implot").contains("prefereFlowOverGlobal"))
222 {
223 j.at("implot").at("prefereFlowOverGlobal").get_to(gui::windows::prefereFlowOverGlobal);
224 }
225
226 std::filesystem::path filepath = flow::GetProgramRootPath();
227 if (std::filesystem::path inputPath{ ConfigManager::Get<std::string>("implot-config") };
228 inputPath.is_relative())
229 {
230 filepath /= inputPath;
231 }
232 else
233 {
234 filepath = inputPath;
235 }
236
237 if (gui::windows::prefereFlowOverGlobal || !std::filesystem::exists(filepath))
238 {
239 if (!ConfigManager::Get<bool>("nogui"))
240 {
241 if (j.at("implot").contains("style"))
242 {
243 j.at("implot").at("style").get_to(ImPlot::GetStyle());
244 }
245 }
246 }
247 }
248 else
249 {
251 }
252
253 if (j.contains("colormaps"))
254 {
255 j.at("colormaps").get_to(ColormapsFlow);
256 }
257 else
258 {
259 ColormapsFlow.clear();
260 }
261 if (j.contains("commonLog")) { CommonLog::restore(j.at("commonLog")); }
262
263 if (!ConfigManager::Get<bool>("nogui"))
264 {
265 if (j.contains("fonts"))
266 {
267 if (j.at("fonts").contains("useBigDefaultFont"))
268 {
269 gui::NodeEditorApplication::swapDefaultFont(j.at("fonts").at("useBigDefaultFont").get<bool>());
270 }
271 if (j.at("fonts").contains("useBigWindowFont"))
272 {
273 gui::NodeEditorApplication::swapWindowFont(j.at("fonts").at("useBigWindowFont").get<bool>());
274 }
275 if (j.at("fonts").contains("useBigPanelFont"))
276 {
277 gui::NodeEditorApplication::swapPanelFont(j.at("fonts").at("useBigPanelFont").get<bool>());
278 }
279 if (j.at("fonts").contains("useBigMonoFont"))
280 {
281 gui::NodeEditorApplication::swapMonoFont(j.at("fonts").at("useBigMonoFont").get<bool>());
282 }
283 }
284 if (j.contains("leftPane"))
285 {
286 j.at("leftPane").at("hide").get_to(gui::NodeEditorApplication::hideLeftPane);
287 j.at("leftPane").at("leftWidth").get_to(gui::NodeEditorApplication::leftPaneWidth);
288 j.at("leftPane").at("rightWidth").get_to(gui::NodeEditorApplication::rightPaneWidth);
289 }
290 if (j.contains("bottomViewHeight")) { j.at("bottomViewHeight").get_to(gui::NodeEditorApplication::bottomViewHeight); }
291 if (j.contains("hideFPS")) { j.at("hideFPS").get_to(gui::NodeEditorApplication::hideFPS); }
292 if (j.contains("lightMode"))
293 {
294 j.at("lightMode").get_to(gui::windows::nodeEditorLightMode);
296 }
297 if (j.contains("gridLinesEnabled")) { j.at("gridLinesEnabled").get_to(ed::GetStyle().Colors[ed::StyleColor_Grid].w); }
298 if (j.contains("transparentWindows")) { j.at("transparentWindows").get_to(ImGui::GetStyle().Colors[ImGuiCol_WindowBg].w); }
299 }
300
301 if (j.contains("nodes"))
302 {
303 for (const auto& nodeJson : j.at("nodes"))
304 {
305 if (!nodeJson.contains("type"))
306 {
307 LOG_ERROR("Node does not contain a type");
308 continue;
309 }
310 Node* node = nullptr;
311 for (const auto& registeredNode : NAV::NodeRegistry::RegisteredNodes())
312 {
313 for (const auto& nodeInfo : registeredNode.second)
314 {
315 if (nodeInfo.type == nodeJson.at("type").get<std::string>())
316 {
317 node = nodeInfo.constructor();
318 break;
319 }
320 }
321 if (node != nullptr)
322 {
323 break;
324 }
325 }
326 if (node == nullptr)
327 {
328 LOG_ERROR("Node type ({}) is not a valid type.", nodeJson.at("type").get<std::string>());
329 loadSuccessful = false;
330 continue;
331 }
332
333 nm::AddNode(node);
334 auto newNodeId = node->id;
335
336 nodeJson.get_to<Node>(*node);
337 if (nodeJson.contains("data"))
338 {
339 node->restore(nodeJson.at("data"));
340 }
341 // Load second time in case restore changed the amount of pins
342 nodeJson.get_to<Node>(*node);
343
344 if (requestNewIds)
345 {
346 node->id = newNodeId;
347 for (auto& pin : node->inputPins)
348 {
349 pin.id = nm::GetNextPinId();
350 }
351 for (auto& pin : node->outputPins)
352 {
353 pin.id = nm::GetNextPinId();
354 }
355 }
356
357 nm::UpdateNode(node);
358
359 if (!ConfigManager::Get<bool>("nogui"))
360 {
361 ed::SetNodePosition(node->id, nodeJson.at("pos").get<ImVec2>());
362
363 if (node->getSize().x > 0 && node->getSize().y > 0)
364 {
365 ed::SetGroupSize(node->id, node->getSize());
366 }
367 }
368 }
369 }
370
371 // Collect the node ids which get new links to call the restoreAfterLinks function on them
372 std::set<Node*> newlyLinkedNodes;
373
374 if (j.contains("links"))
375 {
376 for (size_t i = 0; i < 2; i++) // Run twice because pins can change type depending on other links
377 {
378 for (const auto& linkJson : j.at("links"))
379 {
380 auto linkId = linkJson.at("id").get<size_t>();
381 auto startPinId = linkJson.at("startPinId").get<size_t>();
382 auto endPinId = linkJson.at("endPinId").get<size_t>();
383
384 InputPin* endPin = nullptr;
385 OutputPin* startPin = nullptr;
386 for (auto* node : nm::m_Nodes())
387 {
388 if (!endPin)
389 {
390 for (auto& inputPin : node->inputPins)
391 {
392 if (endPinId == size_t(inputPin.id)) { endPin = &inputPin; }
393 }
394 }
395 if (!startPin)
396 {
397 for (auto& outputPin : node->outputPins)
398 {
399 if (startPinId == size_t(outputPin.id)) { startPin = &outputPin; }
400 }
401 }
402 if (startPin && endPin) { break; }
403 }
404 if (startPin && endPin)
405 {
406 if (!startPin->createLink(*endPin, linkId))
407 {
408 loadSuccessful = false;
409 continue;
410 }
411 newlyLinkedNodes.insert(startPin->parentNode);
412 newlyLinkedNodes.insert(endPin->parentNode);
413 }
414 }
415 }
416 }
417 if (j.contains("nodes"))
418 {
419 for (auto* node : newlyLinkedNodes)
420 {
421 if (j.at("nodes").contains("node-" + std::to_string(size_t(node->id))))
422 {
423 LOG_DEBUG("Calling restoreAtferLink() for new node '{}'", node->nameId());
424
425 const auto& nodeJson = j.at("nodes").at("node-" + std::to_string(size_t(node->id)));
426 if (nodeJson.contains("data"))
427 {
428 node->restoreAtferLink(nodeJson.at("data"));
429 }
430 }
431 }
432 }
433
434 return loadSuccessful;
435}
436
438{
439 return unsavedChanges;
440}
441
443{
444 // This prevents the newly loaded gui elements from triggering the unsaved changes
445 if (ImGui::GetCurrentContext() && ImGui::GetFrameCount() - loadingFrameCount >= loadingFramesToWait)
446 {
447 unsavedChanges = true;
448 if (saveLastActions)
449 {
451 }
452 }
453}
454
456{
457 unsavedChanges = false;
458}
459
461{
462 return currentFilename;
463}
464
465void NAV::flow::SetCurrentFilename(const std::string& newFilename)
466{
467 currentFilename = newFilename;
468}
469
470std::filesystem::path NAV::flow::GetProgramRootPath()
471{
472 return programRootPath;
473}
474
475void NAV::flow::SetProgramRootPath(const std::filesystem::path& newRootPath)
476{
477 LOG_DEBUG("Program root path set to {}", newRootPath);
478 programRootPath = newRootPath;
479}
480
481std::filesystem::path NAV::flow::GetOutputPath()
482{
483 std::filesystem::path filepath = flow::GetProgramRootPath();
484
485 if (std::filesystem::path outputPath{ ConfigManager::Get<std::string>("output-path") };
486 outputPath.is_relative())
487 {
488 filepath /= outputPath;
489 }
490 else
491 {
492 filepath = outputPath;
493 }
494
495 if (ConfigManager::Get<bool>("rotate-output"))
496 {
497 filepath /= fmt::format("{:04d}", currentRotatedParentFolderNumber);
498 }
499
500 return filepath;
501}
502
504{
505 currentRotatedParentFolderNumber = 0;
506 for (int i = 10000; i >= 0; --i)
507 {
508 std::filesystem::path outputDir{ programRootPath };
509
510 if (std::filesystem::path outputPath{ ConfigManager::Get<std::string>("output-path") };
511 outputPath.is_relative())
512 {
513 outputDir /= outputPath;
514 }
515 else
516 {
517 outputDir = outputPath;
518 }
519 outputDir /= fmt::format("{:04d}", i);
520 if (std::filesystem::exists(outputDir))
521 {
522 currentRotatedParentFolderNumber = static_cast<size_t>(i + 1); // NOLINT(bugprone-misplaced-widening-cast)
523 break;
524 }
525 }
526 LOG_DEBUG("Output directory set to {}", GetOutputPath());
527}
528
529std::filesystem::path NAV::flow::GetInputPath()
530{
531 std::filesystem::path filepath = flow::GetProgramRootPath();
532
533 if (std::filesystem::path inputPath{ ConfigManager::Get<std::string>("input-path") };
534 inputPath.is_relative())
535 {
536 filepath /= inputPath;
537 }
538 else
539 {
540 filepath = inputPath;
541 }
542
543 return filepath;
544}
545
546std::filesystem::path NAV::flow::GetFlowPath()
547{
548 std::filesystem::path filepath = flow::GetProgramRootPath();
549
550 if (std::filesystem::path inputPath{ ConfigManager::Get<std::string>("flow-path") };
551 inputPath.is_relative())
552 {
553 filepath /= inputPath;
554 }
555 else
556 {
557 filepath = inputPath;
558 }
559
560 return filepath;
561}
562
563std::filesystem::path NAV::flow::GetConfigPath()
564{
565 return flow::GetProgramRootPath() / "config";
566}
Colormap.
Common logging variables like time into run and local positions.
Config management for the Project.
Flow Executor Thread.
Save/Load the Nodes.
nlohmann::json json
json namespace
GlobalActions
Possible Global Actions to perform in the GUI.
@ SaveAs
Save the flow as filename.
ImPlot style editor window.
Defines how to save certain datatypes to json.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
Definition Logger.hpp:67
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
Definition Logger.hpp:73
#define LOG_INFO
Info to the user on the state of the program.
Definition Logger.hpp:69
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
Style Editor window.
Manages all Nodes.
Utility class which specifies available nodes.
Node Class.
Pin class.
static void restore(const json &j)
Read info from a json object.
Definition CommonLog.cpp:30
static json save()
Returns a json object of the common log.
Definition CommonLog.cpp:21
Input pins of nodes.
Definition Pin.hpp:491
Abstract parent class for all nodes.
Definition Node.hpp:92
virtual void restore(const json &j)
Restores the node from a json object.
Definition Node.cpp:62
const ImVec2 & getSize() const
Get the size of the node.
Definition Node.cpp:258
std::vector< OutputPin > outputPins
List of output pins.
Definition Node.hpp:399
std::vector< InputPin > inputPins
List of input pins.
Definition Node.hpp:397
ax::NodeEditor::NodeId id
Unique Id of the Node.
Definition Node.hpp:391
Output pins of nodes.
Definition Pin.hpp:338
bool createLink(InputPin &endPin, ax::NodeEditor::LinkId linkId=0)
Creates a link from this pin to another, calling all node specific callbacks.
Definition Pin.cpp:257
Node * parentNode
Reference to the parent node.
Definition Pin.hpp:307
static float bottomViewHeight
Height of the log viewer.
static float leftPaneWidth
Width of the left pane.
static std::vector< ImVec4 > m_colors
Color settings.
static bool hideLeftPane
Hide left pane.
static float rightPaneWidth
Width of the right pane.
static bool hideFPS
Hide FPS counter.
const T & Get(const std::string &key, const T &&defaultValue)
Retrieves the value of a corresponding key from the configuration, if one exists.
bool isRunning() noexcept
Checks if the thread is running.
void stop()
Stops the Thread.
void InitializeAllNodesAsync()
Initializes all nodes in a separate thread.
void UpdateNode(Node *node)
Update the provided node object.
bool InitializeAllNodes()
Initializes all nodes.
void DeleteAllNodes()
Delete all nodes.
const std::vector< Node * > & m_Nodes()
List of all registered Nodes.
void AddNode(Node *node)
Add the provided node object to the list of nodes.
ax::NodeEditor::PinId GetNextPinId()
Generates a new pin id.
const std::map< std::string, std::vector< NodeInfo > > & RegisteredNodes()
Reference to List of all registered Nodes.
bool saveLastActions
Whether actions should be saved to the last actions list.
void SetProgramRootPath(const std::filesystem::path &newRootPath)
Set the program root path.
bool LoadJson(const json &j, bool requestNewIds=false)
Loads the nodes and links from the specified json object.
void SetCurrentFilename(const std::string &newFilename)
Set the current filename of the open flow.
bool LoadFlow(const std::string &filepath)
Loads the flow from the specified file.
int loadingFrameCount
Frame Count when changes were loaded to prevent nodes moving from triggering unsaved changes.
std::filesystem::path GetConfigPath()
Get the path where config files are searched.
std::filesystem::path GetOutputPath()
Get the path where logs and outputs are stored.
void SaveFlowAs(const std::string &filepath)
Saves the current flow into the specified file.
std::filesystem::path GetInputPath()
Get the path where data files are searched.
void DiscardChanges()
Discards the unsaved changes flag. Does not really discard the changes.
std::string GetCurrentFilename()
Get the current filename of the open flow.
void ApplyChanges()
Signals that there have been changes to the flow.
std::filesystem::path GetFlowPath()
Get the path where flow files are searched.
std::filesystem::path GetProgramRootPath()
Get the program root path.
bool HasUnsavedChanges()
Checks if the currently open flow has unsaved changes.
void SaveFlow(GlobalActions &globalAction)
Saves the current flow into a file.
void SetOutputPath()
Set the path where logs and outputs are stored.
void ApplyDarkLightMode(std::vector< ImVec4 > &colors)
bool prefereFlowOverGlobal
If true, the ImPlot config from the flow file will be preferred over the global settings file.
bool nodeEditorLightMode
If true, light mode is selected.
bool saveConfigInFlow
If true, the ImPlot config will be saved into the flow file.
void clearLastActionList()
Clears the list of last actions.
void saveLastAction()
Saves the last action to the action list.
std::vector< Colormap > ColormapsFlow
Flow colormaps.
Definition Colormap.cpp:27