Closed eminor1988 closed 4 weeks ago
It's certainly an interesting concept, and I'd like to explore this further. Could you provide an example where a std::function filter would come into play?
Sorry for my poor English skill, I'm not so familiar in ImNodeFlow. So below of code is just trying to explain the concepts(might be not so smart).
In ImNodeFlow.inl: Enum as filter (original):
template<class T>
void InPin<T>::createLink(Pin *other)
{
...
if (!((m_filter & other->getFilter()) != 0 || m_filter == ConnectionFilter_None || other->getFilter() == ConnectionFilter_None)) // Check Filter
return;
...
}
Using std::function as filer (new):
using ConnectionFilter = std::function<bool(OutPin* outPin, InPin* inPin)>;
template<class T>
void InPin<T>::createLink(Pin *other)
{
...
ConnectionFilter currFilterFunc = m_filter;
ConnectionFilter otherFilterFunc = other->getFilter();
if (currFilterFunc) {
if (!currFilterFunc(other, this)) {
return;
}
}
if (otherFilterFunc) {
if (!otherFilterFunc(other, this)) {
return;
}
}
...
}
That means we need to call the two ConnectionFilter functions for the pins between the link. It cannot be connected if ANY ConnectionFilter of the pin rejects it.
For those original enum values like ConnectionFilter_Integer and ConnectionFilter_Numbers, we can add some default ConnectionFilters written in std::function to check whether the "typeid(T) of InPin
Sorry, I meant to ask for a practical use case where such a filter could be useful. Knowing how it will be used will make implementation easier. Just a simple example of nodes and pins interaction to show the use of function filters
I have a GPU sampling function node (perhaps written in GLSL, where the value is merely a cache in the graphics card), the OutPin\<Float32> of GPU should not be able to connect to InPin\<Float32> of a CPU node (because the value of OutPin\<Float32> is in cache of Graphic Card).
And I have others similar cases like it. Some nodes can be used in certain virtual device or context only. For download/upload data must to use certain node.
The virtual device/context can be CPU/GPU, different processes, remote computer, etc.
So I need to isolate the nodes by different scope/context except the certain upload/download nodes)
Ok, if you had function filters, ideally what would you check to validate a connection in your use case?
I'm not a native English speaker, so sorry if my expression is not fluent.
Background: I am trying to re-design my old game engine. The main purpose is make it more programmable by user and make it can hot-reload when runtime. I've just started working on it; perhaps I haven't considered it thoroughly enough yet.
If I have function filters, I will check those condition in most of cases: The context(parent) of node must be the same with other pin. The data type of pins must be same or convertable(need to determine by other function).
But I carefully considered various scenarios that I might encounter next, there are some special cases...
Sometimes, I will allow "Circular reference" for time-based node that works in frame by frame: Allowing "Circular reference" can save many nodes and feel more intuitive in this kind of frame swapping cases. But in most of time I need to reject "Circular reference", so I will use an additional function to check it.
Sometimes, I need to reject the two cases because I want to limit the number of type C nodes because some soft/hard limit (maybe limit by hardware, EX: number of texture samplers in shader): A,B,C,D are four types of node. I need to reject them because user can use only 4 C nodes in context: 1: A -> B -> C -> D -> C -> D -> C -> D A -> B -> C -> D -> C -> D 2: A -> B -> C -> D -> C -> D A -> B -> C -> D -> C -> D B -> C -> D
So I hope constraint the connectability by certain limitation in different scenarios.
Thank you for your patient reading.
Ok ok, thanks for the detailed explanation
The concept seems to be working
https://github.com/Fattorino/ImNodeFlow/assets/90210751/66bf2575-889c-482d-80f1-e5db25ee1a46
class PickyDisplay : public BaseNode
{
public:
explicit PickyDisplay()
{
setTitle("PickyDisplay");
addIN<float>("Val", 0.f, [](Pin* out, Pin* in) { return out->getDataType() == in->getDataType() && dynamic_cast<ImFlow::OutPin<float>*>(out)->val() > 4.f; });
setStyle(NodeStyle::red());
}
void draw() override
{
ImGui::Text("Value = %.2f;", getInVal<float>("Val"));
if (getInVal<float>("Val") <= 4.f)
inPin("Val")->deleteLink();
}
private:
};
I tried it and it works great! Thanks!!
That's how I check for circular references. (It hasn't been well tested, no copyright, welcome to use or modify.)
namespace ImFlowEx
{
using namespace ImFlow;
using ConnFilterFunc = std::function<bool(Pin*, Pin*)>;
static auto IsOutNodeInPinsCircularRef(BaseNode* checkingNode, BaseNode* rejectNode) -> bool
{
if (rejectNode == checkingNode) {
return true;
}
for (const std::shared_ptr<Pin>& inPin : checkingNode->getIns()) {
auto link = inPin->getLink().lock();
if (link) {
Pin* outPin = link->left();
if (outPin && IsOutNodeInPinsCircularRef(outPin->getParent(), rejectNode)) {
return true;
}
}
}
return false;
}
static auto IsInNodeOutPinsCircularRef(BaseNode* checkingNode, BaseNode* rejectNode) -> bool
{
if (rejectNode == checkingNode) {
return true;
}
for (const std::shared_ptr<Pin>& outPin : checkingNode->getOuts()) {
auto link = outPin->getLink().lock();
if (link) {
Pin* inPin = link->right();
if (inPin && IsInNodeOutPinsCircularRef(inPin->getParent(), rejectNode)) {
return true;
}
}
}
return false;
}
static auto ConnFilter_NoCircularRef() -> ConnFilterFunc
{
return [](Pin* out, Pin* in) {
return
!IsOutNodeInPinsCircularRef(out->getParent(), in->getParent()) &&
!IsInNodeOutPinsCircularRef(in->getParent(), out->getParent());
};
}
static auto CascadeConnFilters(std::vector<ConnFilterFunc>&& connFilters) -> ConnFilterFunc
{
return [vec = std::move(connFilters)](Pin* out, Pin* in) {
for (const ConnFilterFunc& connFilterFunc : vec) {
if (!connFilterFunc(out, in)) {
return false;
}
}
return true;
};
}
static ConnFilterFunc connFilterSameTypeNoCircularRef = CascadeConnFilters({ ConnectionFilter::SameType(), ConnFilter_NoCircularRef() });
}
// Use it on the node.
class NodePipelineUpdate final : public ImFlow::BaseNode
{
public:
NodePipelineUpdate()
{
setTitle("Pipeline - Update");
addIN<bool>("Prev.", false, ImFlowEx::connFilterSameTypeNoCircularRef, ImFlow::PinStyle::red());
}
};
Finished implementation with commit aaaab72
Thanks for this amazing library. ImNodeFlow is very easy for me to use.
I have some ideas regarding the enum "ConnectionFilter". I need various types of nodes for complex pipeline design. My nodes might have nested relationships (parent/children). I hope the ConnectionFilter can be a std::function that determines whether the nodes are connectable or not (based on port type or parent type), rather than just using an enum.
Not sure if anyone else has any other ideas?