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.