paceholder / nodeeditor

Qt Node Editor. Dataflow programming framework
BSD 3-Clause "New" or "Revised" License
2.92k stars 795 forks source link

Added propagation control functions and fix implicit string conversion in headers. #367

Open rayment opened 1 year ago

rayment commented 1 year ago

I've added the ability to explicitly control propagation of data in DataFlowGraphModel through the use of a new virtual function canPropagate(ConnectionId), and another function propagate(NodeId) to request a propagation without having to update connections or input/output. The user can also change onOutPortDataUpdated and propagateEmptyDataTo with deriviative functions to change the output of the propagation for each connection.

These functions are both virtual so that derivative dataflow models can specify their own control model, but this may go against the design of DataFlowGraphModel. Feel free to deny or change as you wish. As an example:

My project has nodes of text data ranging in the gigabytes. I use a global execution state to control when data is propagated and clone node outputs when there is more than one connection stemming from an output. This can be achieved with custom functions:

Controlled data propagation

PipedGraphModel::
PipedGraphModel(std::shared_ptr<NodeDelegateModelRegistry> registry) :
    DataFlowGraphModel(registry)
{
    executing = false;
}

PipedGraphModel::~PipedGraphModel(void)
{}

bool
PipedGraphModel::canPropagate(const ConnectionId conn)
{
    if (executing)
        return true;
    // data should only be passed on if the graph is being executed
    // or if the node is capable of performing the calculation in
    // such a negligible amount of time that it may as well calculate
    // immediately (think math or array nodes)
    Node *delegate = this->delegateModel<Node>(conn.inNodeId);
    return delegate->canInstantCompute();
}

void
PipedGraphModel::stopExecute(void)
{
    executing = false;
}

void
PipedGraphModel::execute(void)
{
    executing = true;
    for (NodeId id : this->allNodeIds())
    {
        // check each node for any inputs, and if it contains none, then
        // it must be an input node, from which we can propagate the rest of the
        // graph
        Node *delegate = this->delegateModel<Node>(id);
        if (delegate->numInputs() == 0 && delegate->numOutputs() > 0)
            this->propagate(id);
    }
    executing = false;
}

void
PipedGraphModel::onOutPortDataUpdated(NodeId const nodeId,
                                      PortIndex const portIndex)
{
    const std::unordered_set<ConnectionId> &connected =
        connections(nodeId, PortType::Out, portIndex);

    const QVariant portDataToPropagate =
        portData(nodeId, PortType::Out, portIndex, PortRole::Data);
    const std::shared_ptr<PipedNodeData> data =
        qvariant_cast<std::shared_ptr<PipedNodeData>>(portDataToPropagate);

    int con = 0;
    for (auto const &cn : connected) {
        if (canPropagate(cn)) {
            setPortData(cn.inNodeId,
                        PortType::In,
                        cn.inPortIndex,
                        con > 0 ? QVariant::fromValue(data->clone()) :
                                  QVariant::fromValue(data), // fork connections
                        PortRole::Data);
            ++con;
        }
    }
}
rayment commented 1 year ago

I've added another commit which surrounds strings that are present in headers with QLatin1String(...) because the library refuses to compile into my project if QT_NO_CAST_FROM_ASCII is defined. In a sense it optimises the code because strings are no longer being implicitly converted to QString at runtime, but I have not done the same for any of the source files (I would be happy to share my fix if it's worth it).

Just thought it would be useful to add here rather than open another PR...

rayment commented 1 year ago

Hi @paceholder, I understand you are probably busy with life at the moment (aren't we all?) but I was hoping to ask you to review this change when you have the chance (I'm mostly worried about 7f9d7ab as the project fails to build if we turn on QT_NO_CAST_FROM_ASCII).