24#include <imgui_node_editor.h>
27 #include <catch2/catch_test_macros.hpp>
99 if (!outputPin.isPinLinked()) {
return; }
101 for (
auto& link : outputPin.links)
103 auto* targetPin = link.getConnectedPin();
104 if (link.connectedNode->isInitialized() && !targetPin->queueBlocked)
106 outputPin.dataAccessCounter++;
107 link.dataChangeNotification =
true;
108 LOG_DATA(
"{}: Increasing data access counter on output pin '{}'. Value now {}.",
nameId(), outputPin.name, outputPin.dataAccessCounter);
115 auto data = std::make_shared<NodeData>();
116 data->insTime = insTime;
118 targetPin->queue.push_back(data);
121 for (
const auto& link : outputPin.links)
123 auto* targetPin = link.getConnectedPin();
124 if (link.connectedNode->isInitialized() && !targetPin->queueBlocked)
126 LOG_DATA(
"{}: Waking up worker of node '{}'. New data on pin '{}'",
nameId(), link.connectedNode->nameId(), targetPin->name);
127 link.connectedNode->wakeWorker();
137 std::unique_lock<std::mutex> lk(outputPin.dataAccessMutex);
138 if (outputPin.dataAccessCounter > 0)
140 LOG_DATA(
"{}: Requesting lock on output pin '{}', {} threads accessing still.",
nameId(), outputPin.name, outputPin.dataAccessCounter);
141 outputPin.dataAccessConditionVariable.wait(lk, [&outputPin]() {
return outputPin.dataAccessCounter == 0; });
142 LOG_DATA(
"{}: Lock on output pin '{}' acquired.",
nameId(), outputPin.name);
145 return std::scoped_lock(outputPin.dataAccessMutex);
152 std::scoped_lock<std::mutex> lk(outputPin->dataAccessMutex);
153 if (outputPin->dataAccessCounter > 0)
156 return link.connectedPinId == inputPins.at(portIndex).id;
158 if (outgoingLink != outputPin->links.end() && outgoingLink->dataChangeNotification)
160 outgoingLink->dataChangeNotification =
false;
161 outputPin->dataAccessCounter--;
163 if (outputPin->dataAccessCounter == 0)
165 LOG_DATA(
"{}: Notifying node '{}' connected to pin {} that all data is read.",
nameId(), outputPin->parentNode->nameId(), outputPin->name);
166 outputPin->dataAccessConditionVariable.notify_all();
186 LOG_DEBUG(
"{}: Tried to invokeCallbacks on pin {} with a nullptr, which is not allowed!!!",
nameId(), portIndex);
189 if (data->insTime.empty())
191 LOG_DATA(
"{}: Tried to invokeCallbacks on pin {} without a InsTime. The time is mandatory though!!! ",
nameId(), portIndex);
195 for (
const auto& link :
outputPins.at(portIndex).links)
197 auto* targetPin = link.getConnectedPin();
198 if (link.connectedNode->isInitialized() && !targetPin->queueBlocked)
205 targetPin->queue.push_back(data);
206 LOG_DATA(
"{}: Waking up worker of node {}. New data on pin '{}'",
nameId(),
size_t(link.connectedNode->id), targetPin->name);
207 link.connectedNode->wakeWorker();
217 if (pinId == inputPin.id) {
return inputPin; }
220 throw std::runtime_error(fmt::format(
"{}: The Pin {} is not on this node.",
nameId(),
size_t(pinId)).c_str());
227 if (pinId == outputPin.id) {
return outputPin; }
230 throw std::runtime_error(fmt::format(
"{}: The Pin {} is not on this node.",
nameId(),
size_t(pinId)).c_str());
235 for (
size_t i = 0; i <
inputPins.size(); i++)
237 if (pinId ==
inputPins.at(i).id) {
return i; }
240 throw std::runtime_error(fmt::format(
"{}: The Pin {} is not on this node.",
nameId(),
size_t(pinId)).c_str());
245 for (
size_t i = 0; i <
outputPins.size(); i++)
247 if (pinId ==
outputPins.at(i).id) {
return i; }
250 throw std::runtime_error(fmt::format(
"{}: The Pin {} is not on this node.",
nameId(),
size_t(pinId)).c_str());
270 return "Deinitialized";
272 return "DoInitialize";
274 return "Initializing";
276 return "Initialized";
278 return "DoDeinitialize";
280 return "Deinitializing";
538 std::unique_lock<std::mutex> lk(node->
_stateMutex);
541 if (lk.owns_lock()) { lk.unlock(); }
543 if (std::unique_lock<std::mutex> lock(node->
_stateMutex);
552 if (std::unique_lock<std::mutex> lock(node->
_stateMutex);
564 if (std::unique_lock<std::mutex> lock(node->
_stateMutex);
581 bool timeout =
false;
598 return inputPin.isPinLinked();
604 bool notifyTriggered =
false;
605 for (
size_t i = 0; i < node->
inputPins.size(); i++)
610 if (
auto callback = std::get<InputPin::DataChangedNotifyFunc>(inputPin.
callback))
612 LOG_DATA(
"{}: Invoking notify callback on input pin '{}'", node->
nameId(), inputPin.
name);
615 for (
const auto& watcherCallback : inputPin.watcherCallbacks)
617 if (
auto watcherCall = std::get<InputPin::DataChangedWatcherNotifyFunc>(watcherCallback))
619 std::invoke(watcherCall, node, insTime, i);
623 std::invoke(callback, node, insTime, i);
624 notifyTriggered =
true;
628 if (notifyTriggered) {
continue; }
638 bool allInputPinsHaveData = !node->
inputPins.empty();
639 for (
const auto& inputPin : node->
inputPins)
646 allInputPinsHaveData =
false;
651 if (!allInputPinsHaveData)
653 LOG_DATA(
"{}: Not all pins have data for temporal sorting", node->
nameId());
656 LOG_DATA(
"{}: All pins have data for temporal sorting", node->
nameId());
661 size_t earliestInputPinIdx = 0;
662 int earliestInputPinPriority = -1000;
663 for (
size_t i = 0; i < node->
inputPins.size(); i++)
667 && (earliestTime.
empty()
668 || inputPin.
queue.
front()->insTime < earliestTime
669 || (inputPin.
queue.
front()->insTime == earliestTime && inputPin.
priority > earliestInputPinPriority)))
671 earliestTime = inputPin.
queue.
front()->insTime;
672 earliestInputPinIdx = i;
673 earliestInputPinPriority = inputPin.
priority;
676 if (earliestInputPinPriority == -1000) {
break; }
678 auto& inputPin = node->
inputPins[earliestInputPinIdx];
681 if (
auto callback = std::get<InputPin::FlowFirableCallbackFunc>(inputPin.
callback))
685 for (
const auto& watcherCallback : inputPin.watcherCallbacks)
687 if (
auto watcherCall = std::get<InputPin::FlowFirableWatcherCallbackFunc>(watcherCallback))
689 std::invoke(watcherCall, node, inputPin.
queue, earliestInputPinIdx);
693 std::invoke(callback, node, inputPin.
queue, earliestInputPinIdx);
717 std::multimap<InsTime, std::pair<OutputPin*, size_t>>::iterator it;
721 size_t outputPinIdx = it->second.second;
724 if (std::holds_alternative<OutputPin::PollDataFunc>(outputPin->
data))
726 auto* callback = std::get_if<OutputPin::PollDataFunc>(&outputPin->
data);
727 if (callback !=
nullptr && *callback !=
nullptr)
730 if ((node->**callback)() ==
nullptr)
737 else if (std::holds_alternative<OutputPin::PeekPollDataFunc>(outputPin->
data))
739 auto* callback = std::get_if<OutputPin::PeekPollDataFunc>(&outputPin->
data);
740 if (callback !=
nullptr && *callback !=
nullptr)
742 if (!it->first.empty())
746 if ((node->**callback)(outputPinIdx,
false) ==
nullptr)
748 LOG_ERROR(
"{}: {} could not poll its observation despite being able to peek it.", node->
nameId(), outputPin->
name);
753 if (
auto obs = (node->**callback)(outputPinIdx,
true))
756 if (!obs->insTime.empty())
758 node->
pollEvents.insert(std::make_pair(obs->insTime, std::make_pair(outputPin, outputPinIdx)));
762 (node->**callback)(outputPinIdx,
false);
770 for (
auto& link : outputPin->
links)
772 link.connectedNode->wakeWorker();
778 LOG_ERROR(
"{} - {}: Callback is not valid anymore", node->
nameId(),
size_t(outputPin->
id));
791 if (!outputPin.noMoreDataAvailable)
793 LOG_TRACE(
"{}: Output Pin finished: {}", node->
nameId(), outputPin.name);
794 outputPin.noMoreDataAvailable =
true;
795 for (
auto& link : outputPin.links)
797 link.connectedNode->wakeWorker();
811 return inputPin.type != Pin::Type::Flow || !inputPin.isPinLinked() || inputPin.link.connectedNode->isDisabled()
812 || !inputPin.link.getConnectedPin()->blocksConnectedNodeFromFinishing
813 || (inputPin.queue.empty() && inputPin.link.getConnectedPin()->noMoreDataAvailable);
820 LOG_TRACE(
"{}: Output Pin finished: {}", node->
nameId(), outputPin.name);
821 outputPin.noMoreDataAvailable =
true;
822 for (
auto& link : outputPin.links)
824 link.connectedNode->wakeWorker();
854 if (
Node* connectedNode = inputPin.link.connectedNode)
856 if (!connectedNode->isInitialized())
858 LOG_DEBUG(
"{}: Initializing connected Node '{}' on input Pin {}",
nameId(), connectedNode->nameId(),
size_t(inputPin.id));
859 if (!connectedNode->doInitialize(
true))
861 LOG_ERROR(
"{}: Could not initialize connected node {}",
nameId(), connectedNode->nameId());
878 bool initSucceeded =
false;
889 inputPin.queue.clear();
890 inputPin.queueBlocked =
false;
902 outputPin.noMoreDataAvailable =
true;
903 for (
auto& link : outputPin.links)
905 LOG_TRACE(
"{}: Waking connected node '{}'",
nameId(), link.connectedNode->nameId());
906 link.connectedNode->wakeWorker();
946 for (
const auto& link : outputPin.links)
948 if (link.connectedNode->isInitialized())
951 _reinitialize ?
"Reinitializing" :
"Deinitializing", link.connectedNode->nameId(),
size_t(outputPin.id));
953 else { link.connectedNode->doDeinitialize(); }
992 ImVec2 realSize = ed::GetNodeSize(node.
id);
996 {
"id", size_t(node.
id) },
997 {
"type", node.
type() },
998 {
"kind", std::string(node.
kind) },
999 {
"name", node.
name },
1000 {
"size", node.
_size.x == 0 && node.
_size.y == 0 ? node.
_size : realSize },
1001 {
"pos", ed::GetNodePosition(node.
id) },
1009 node.
id = j.at(
"id").get<
size_t>();
1010 if (j.contains(
"kind"))
1014 if (j.contains(
"name"))
1016 j.at(
"name").get_to(node.
name);
1018 if (j.contains(
"size"))
1020 j.at(
"size").get_to(node.
_size);
1026 if (j.contains(
"enabled"))
1028 bool enabled = j.at(
"enabled").get<
bool>();
1036 if (j.contains(
"inputPins"))
1038 auto inputPins = j.at(
"inputPins").get<std::vector<InputPin>>();
1039 for (
size_t i = 0; i < inputPins.size(); ++i)
1045 j.at(
"inputPins").at(i).get_to(node.
inputPins.at(i));
1049 if (j.contains(
"outputPins"))
1051 auto outputPins = j.at(
"outputPins").get<std::vector<OutputPin>>();
1052 for (
size_t i = 0; i < outputPins.size(); ++i)
1058 j.at(
"outputPins").at(i).get_to(node.
outputPins.at(i));
#define INS_ASSERT_USER_ERROR(_EXP, _MSG)
Assert function with message.
nlohmann::json json
json namespace
Defines how to save certain datatypes to json.
Utility class for logging to console and file.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
#define LOG_DATA
All output which occurs repeatedly every time observations are received.
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Utility functions for working with std::strings.
The class is responsible for all time-related tasks.
constexpr bool empty() const
Checks if the Time object has a value.
Abstract parent class for all nodes.
bool _workerWakeup
Variable to prevent the worker from sleeping.
bool isDisabled() const
Checks if the node is disabled.
bool isOnlyRealtime() const
Checks if the node is only working in real time (sensors, network interfaces, ...)
bool isInitialized() const
Checks if the node is initialized.
bool doDeinitialize(bool wait=false)
Asks the node worker to deinitialize the node.
static void workerThread(Node *node)
Worker thread.
virtual void restore(const json &j)
Restores the node from a json object.
void releaseInputValue(size_t portIndex)
Unblocks the connected node. Has to be called when the input value should be released and getInputVal...
bool _reinitialize
Flag if the node should be reinitialize after deinitializing.
virtual void workerTimeoutHandler()
Handler which gets triggered if the worker runs into a periodic timeout.
bool _disable
Flag if the node should be disabled after deinitializing.
bool doDisable(bool wait=false)
Asks the node worker to disable the node.
State
Possible states of the node.
@ DoInitialize
Node should be initialized.
@ Shutdown
Node is shutting down.
@ DoShutdown
Node should shut down.
@ Initializing
Node is currently initializing.
@ Initialized
Node is initialized (green)
@ DoDeinitialize
Node should be deinitialized.
@ Disabled
Node is disabled and won't be initialized.
@ Deinitializing
Node is currently deinitializing.
@ Deinitialized
Node is deinitialized (red)
void wakeWorker()
Wakes the worker thread.
ImVec2 _size
Size of the node in pixels.
std::mutex _configWindowMutex
Mutex to show the config window (prevents initialization to modify values within the config window)
State getState() const
Get the current state of the node.
bool workerInitializeNode()
Called by the worker to initialize the node.
void notifyOutputValueChanged(size_t pinIdx, const InsTime &insTime, const std::scoped_lock< std::mutex > &&guard)
Notifies connected nodes about the change.
const ImVec2 & getSize() const
Get the size of the node.
bool hasInputPinWithSameTime(const InsTime &insTime) const
Checks wether there is an input pin with the same time.
virtual void flush()
Function called by the flow executer after finishing to flush out remaining data.
std::vector< OutputPin > outputPins
List of output pins.
Node(std::string name)
Constructor.
bool workerDeinitializeNode()
Called by the worker to deinitialize the node.
std::multimap< InsTime, std::pair< OutputPin *, size_t > > pollEvents
Map with callback events (sorted by time)
Kind kind
Kind of the Node.
size_t outputPinIndexFromId(ax::NodeEditor::PinId pinId) const
Returns the index of the pin.
std::vector< InputPin > inputPins
List of input pins.
OutputPin & outputPinFromId(ax::NodeEditor::PinId pinId)
Returns the pin with the given id.
virtual void deinitialize()
Deinitialize the Node.
bool doInitialize(bool wait=false)
Asks the node worker to initialize the node.
State _state
Current state of the node.
Mode
Different Modes the Node can work in.
@ POST_PROCESSING
Node running in post-processing mode.
@ REAL_TIME
Node running in real-time mode.
bool callbacksEnabled
Enables the callbacks.
std::atomic< Mode > _mode
Mode the node is currently running in.
virtual bool resetNode()
Resets the node. It is guaranteed that the node is initialized when this is called.
std::string nameId() const
Node name and id.
virtual void afterCreateLink(OutputPin &startPin, InputPin &endPin)
Called when a new link was established.
std::thread _worker
Worker handling initialization and processing of data.
size_t inputPinIndexFromId(ax::NodeEditor::PinId pinId) const
Returns the index of the pin.
virtual void onDeleteLink(OutputPin &startPin, InputPin &endPin)
Called when a link is to be deleted.
virtual ~Node()
Destructor.
Mode getMode() const
Get the current mode of the node.
std::mutex _workerMutex
Mutex to interact with the worker condition variable.
virtual void guiConfig()
ImGui config window which is shown on double click.
std::string name
Name of the Node.
bool _onlyRealTime
Whether the node can run in post-processing or only real-time.
bool doEnable()
Enable the node.
bool doReinitialize(bool wait=false)
Asks the node worker to reinitialize the node.
std::chrono::duration< int64_t > _workerTimeout
Periodic timeout of the worker to check if new data available.
std::condition_variable _workerConditionVariable
Condition variable to signal the worker thread to do something.
virtual void restoreAtferLink(const json &j)
Restores link related properties of the node from a json object.
virtual void afterDeleteLink(OutputPin &startPin, InputPin &endPin)
Called when a link was deleted.
InputPin & inputPinFromId(ax::NodeEditor::PinId pinId)
Returns the pin with the given id.
std::scoped_lock< std::mutex > requestOutputValueLock(size_t pinIdx)
Blocks the thread till the output values was read by all connected nodes.
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
virtual json save() const
Saves the node into a json object.
static std::string toString(State state)
Converts the state into a printable text.
static bool _autostartWorker
Flag which prevents the worker to be autostarted if false.
ax::NodeEditor::NodeId id
Unique Id of the Node.
virtual bool onCreateLink(OutputPin &startPin, InputPin &endPin)
Called when a new link is to be established.
virtual std::string type() const =0
String representation of the Class Type.
std::mutex _stateMutex
Mutex to interact with the worker state variable.
bool isTransient() const
Checks if the node is changing its state currently.
virtual bool initialize()
Initialize the Node.
PinData data
Pointer to data (owned by this node) which is transferred over this pin.
std::vector< OutgoingLink > links
Info to identify the linked pins.
std::atomic< bool > noMoreDataAvailable
Flag set, when no more data is available on this pin.
Node * parentNode
Reference to the parent node.
std::string name
Name of the Pin.
ax::NodeEditor::PinId id
Unique Id of the Pin.
Type type
Type of the Pin.
auto & front()
Returns a reference to the first element in the container.
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
bool empty() const noexcept
Checks if the container has no elements, i.e. whether 'begin() == end()'.
void pop_front()
Removes the first element of the container. If there are no elements in the container,...
void Add(ax::NodeEditor::LinkId id, ax::NodeEditor::FlowDirection direction=ax::NodeEditor::FlowDirection::Forward)
Thread safe flow animaton of links.
void deregisterNode(const Node *node)
Called by nodes when they finished with sending data.
bool showFlowWhenNotifyingValueChange
Flag if notifyOutputValueChanged & notifyInputValueChanged triggers a GUI Flow event.
static std::string replaceAll_copy(std::string str, const std::string &from, const std::string &to, CaseSensitivity cs)
Replaces all occurrence of a search pattern with another sequence.
void to_json(json &j, const Node &node)
Converts the provided node into a json object.
void move(std::vector< T > &v, size_t sourceIdx, size_t targetIdx)
Moves an element within a vector to a new position.
void from_json(const json &j, Node &node)
Converts the provided json object into a node object.
@ GroupBox
Group box which can group other nodes and drag them together.
Collection of information about the connected node and pin.