Comments (7)
Yes, absolutely agree! It was something i had for some time in the TODO list.
from mosaic.
Ok, great. I've been thinking about the pins, together with ofxObjectParameter; specifically on how they can link together. Can you tell me what you think and if it fits current implementation/objects ?
So basically the Pin class is the access door to a variable for other objects. It will also handle onConnected+onDisconnected callbacks and flag changed.
My question is about where the data is stored and how it's accessed.
I think that it matches your descriptions (1-2-3-4) in #22 , but need confirmation.
- Inlets do not store data. Instead they store a reference (or pointer) to the outlets they are connected to. In some cases, they can point to no valid data (disconnected).
- Outlets will always provide direct access to storage and are always valid.
Note: In the case of an outlet pointing to an inlet, the object creates a copy of the inlet value and feeds it to the outlet, ensuring the outlet remains valid when the inlet is disconnected. This will simplify and strengthen the data flow logic. Anyways, in such cases, the non-duplicate is accessible trough the original outlet.
Note: Inlets that override GUI variables will always be available (stored), otherwise the inlet will only be available when isConnected() is true.
This is a bit vague yet but I hope you get the idea. :)
from mosaic.
Yes, as you described, right now we have:
- inlets are void pointers in the PatchObject class
void *_inletParams[MAX_INLETS];
void *_outletParams[MAX_OUTLETS];
then, proper cast will be applied in every object:
// init
_inletParams[0] = new float();
//call
*(float *)&_inletParams[0] = 0.0f;
// init
_inletParams[1] = new string();
// call
*static_cast<string *>(_inletParams[1]) = "";
// init
_inletParams[2] = new vector<float>();
// call
static_cast<vector<float> *>(_inletParams[2])
// ETC.........
When connected, they just point to the same memory block of the related outlet, when disconnected, they just point to an empty memory block.
Outlets, on the other end, are always pointing to something, as they point to the result of some object internal operation.
Links are always created from outlet --> to inlet, and links data belong to the object having cables coming from his outlets, so from PatchObject class we manage the "sending" connections (outlets), and from ofxVIsualProgramming we manage the entire patch with the "receiving" (inlets) for all the objects (basically logical/graphical connect/disconnect from GUI user interaction).
From PatchObject class, the outlet "data sending" code:
void PatchObject::update(map<int,shared_ptr<PatchObject>> &patchObjects, ofxThreadedFileDialog &fd){
// update links positions
if(!willErase){
for(int out=0;out<getNumOutlets();out++){
for(int i=0;i<static_cast<int>(outPut.size());i++){
if(!outPut[i]->isDisabled && outPut[i]->fromOutletID == out && patchObjects[outPut[i]->toObjectID]!=nullptr && !patchObjects[outPut[i]->toObjectID]->getWillErase()){
outPut[i]->posFrom = getOutletPosition(out);
outPut[i]->posTo = patchObjects[outPut[i]->toObjectID]->getInletPosition(outPut[i]->toInletID);
// send data through links
patchObjects[outPut[i]->toObjectID]->_inletParams[outPut[i]->toInletID] = _outletParams[out];
}
}
}
updateObjectContent(patchObjects,fd);
}
}
From ofxVisualProgramming, the inlets "receiving data" code (connect and disconnect):
//--------------------------------------------------------------
bool ofxVisualProgramming::connect(int fromID, int fromOutlet, int toID,int toInlet, int linkType){
bool connected = false;
if((fromID != -1) && (patchObjects[fromID] != nullptr) && (toID != -1) && (patchObjects[toID] != nullptr) && (patchObjects[fromID]->getOutletType(fromOutlet) == patchObjects[toID]->getInletType(toInlet)) && !patchObjects[toID]->inletsConnected[toInlet]){
//cout << "Mosaic :: "<< "Connect object " << patchObjects[fromID]->getName().c_str() << ":" << ofToString(fromID) << " to object " << patchObjects[toID]->getName().c_str() << ":" << ofToString(toID) << endl;
shared_ptr<PatchLink> tempLink = shared_ptr<PatchLink>(new PatchLink());
string tmpID = ofToString(fromID)+ofToString(fromOutlet)+ofToString(toID)+ofToString(toInlet);
tempLink->id = stoi(tmpID);
tempLink->posFrom = patchObjects[fromID]->getOutletPosition(fromOutlet);
tempLink->posTo = patchObjects[toID]->getInletPosition(toInlet);
tempLink->type = patchObjects[toID]->getInletType(toInlet);
tempLink->fromOutletID = fromOutlet;
tempLink->toObjectID = toID;
tempLink->toInletID = toInlet;
tempLink->isDisabled = false;
tempLink->linkVertices.push_back(ofVec2f(tempLink->posFrom.x,tempLink->posFrom.y));
tempLink->linkVertices.push_back(ofVec2f(tempLink->posTo.x,tempLink->posTo.y));
patchObjects[fromID]->outPut.push_back(tempLink);
patchObjects[toID]->inletsConnected[toInlet] = true;
if(tempLink->type == VP_LINK_NUMERIC){
patchObjects[toID]->_inletParams[toInlet] = new float();
}else if(tempLink->type == VP_LINK_STRING){
patchObjects[toID]->_inletParams[toInlet] = new string();
}else if(tempLink->type == VP_LINK_ARRAY){
patchObjects[toID]->_inletParams[toInlet] = new vector<float>();
}else if(tempLink->type == VP_LINK_PIXELS){
patchObjects[toID]->_inletParams[toInlet] = new ofPixels();
}else if(tempLink->type == VP_LINK_TEXTURE){
patchObjects[toID]->_inletParams[toInlet] = new ofTexture();
}else if(tempLink->type == VP_LINK_AUDIO){
patchObjects[toID]->_inletParams[toInlet] = new ofSoundBuffer();
if(patchObjects[fromID]->getIsPDSPPatchableObject() && patchObjects[toID]->getIsPDSPPatchableObject()){
patchObjects[fromID]->pdspOut[fromOutlet] >> patchObjects[toID]->pdspIn[toInlet];
}else if(patchObjects[fromID]->getName() == "audio device" && patchObjects[toID]->getIsPDSPPatchableObject()){
patchObjects[fromID]->pdspOut[fromOutlet] >> patchObjects[toID]->pdspIn[toInlet];
}
}
checkSpecialConnection(fromID,toID,linkType);
connected = true;
}
return connected;
}
//--------------------------------------------------------------
void ofxVisualProgramming::disconnectSelected(int objID, int objLink){
for(map<int,shared_ptr<PatchObject>>::iterator it = patchObjects.begin(); it != patchObjects.end(); it++ ){
for(int j=0;j<static_cast<int>(it->second->outPut.size());j++){
if(it->second->outPut[j]->toObjectID == objID && it->second->outPut[j]->toInletID == objLink){
// remove link
vector<bool> tempEraseLinks;
for(int s=0;s<static_cast<int>(it->second->outPut.size());s++){
if(it->second->outPut[s]->toObjectID == objID && it->second->outPut[s]->toInletID == objLink){
tempEraseLinks.push_back(true);
}else{
tempEraseLinks.push_back(false);
}
}
vector<shared_ptr<PatchLink>> tempBuffer;
tempBuffer.reserve(it->second->outPut.size()-tempEraseLinks.size());
for(int s=0;s<static_cast<int>(it->second->outPut.size());s++){
if(!tempEraseLinks[s]){
tempBuffer.push_back(it->second->outPut[s]);
}else{
it->second->removeLinkFromConfig(it->second->outPut[s]->fromOutletID);
if(patchObjects[objID] != nullptr){
patchObjects[objID]->inletsConnected[objLink] = false;
if(patchObjects[objID]->getIsPDSPPatchableObject()){
patchObjects[objID]->pdspIn[objLink].disconnectIn();
}
}
}
}
it->second->outPut = tempBuffer;
break;
}
}
}
}
//--------------------------------------------------------------
void ofxVisualProgramming::disconnectLink(int linkID){
for(map<int,shared_ptr<PatchObject>>::iterator it = patchObjects.begin(); it != patchObjects.end(); it++ ){
for(int j=0;j<static_cast<int>(it->second->outPut.size());j++){
if(it->second->outPut[j]->id == linkID){
// remove link
vector<bool> tempEraseLinks;
for(int s=0;s<static_cast<int>(it->second->outPut.size());s++){
if(it->second->outPut[s]->id == linkID){
tempEraseLinks.push_back(true);
}else{
tempEraseLinks.push_back(false);
}
}
vector<shared_ptr<PatchLink>> tempBuffer;
tempBuffer.reserve(it->second->outPut.size()-tempEraseLinks.size());
for(int s=0;s<static_cast<int>(it->second->outPut.size());s++){
if(!tempEraseLinks[s]){
tempBuffer.push_back(it->second->outPut[s]);
}else{
it->second->removeLinkFromConfig(it->second->outPut[s]->fromOutletID);
if(patchObjects[it->second->outPut[j]->toObjectID] != nullptr){
patchObjects[it->second->outPut[j]->toObjectID]->inletsConnected[it->second->outPut[j]->toInletID] = false;
if(patchObjects[it->second->outPut[j]->toObjectID]->getIsPDSPPatchableObject()){
patchObjects[it->second->outPut[j]->toObjectID]->pdspIn[it->second->outPut[j]->toInletID].disconnectIn();
}
}
}
}
it->second->outPut = tempBuffer;
break;
}
}
}
}
Now, the first thing i had in the list was to change from void pointers:
void *_inletParams[MAX_INLETS];
to c++11 shared pointers
std::vector<std::shared_ptr> _inletParams;
I've made some tests, and definitely works faster and the memory is better managed.
About the outlet pointing to an inlet (inverting the wire dataflow direction), i think it can be a little confusing for the users, and actually it didn't add great possibilities. I prefer to not add this capability.
Let me know if you need more info, and if you have more suggestions.
from mosaic.
Thanks, that gives a good overview of the current implementation. We need to dig deeper here.
Totally agree for the void*.
About the outlet pointing to an inlet (inverting the wire dataflow direction), ...
I think we misunderstood on this, I was saying that feels weird too, but that's what happened in ConstantObject where inlets[0] = outlets[0] = new Float()
. But first let's clarify the biggest question.
As I understand, inlets are data pointers (allocated memory). Every update cycle, PatchObject::update(){_inletParams[i] = _outletParams[i];}
sets the inlet pointers to current (fresh) data. There's no value copy but pointers are re-assigned every update, ensuring the data remains valid when updateObjectContent() is called right after that.
On one side I like that approach for its simplicity of use and robustness : pointers and data allocation are hard to manage and this is an easy approach to it.
But it also feels like a lot of unnecessary computations too. On karmaMapper I chose to reduce computing power by ensuring data remains valid, and that's also what I've been trying to describe above with the question of data storage, without really knowing how it currently works.
So my proposal on the Pin class is to ensure data remains valid until the pin is destroyed. This will prevent the need to pinValue = curPinData
before every update; objects will directly use Pin->value which always points to valid data.
Gonna dig deeper in this current implementation, I misunderstood some parts of it, but I think there's some performance optimisation to discuss. This is also where things can get complicated and dark. And we should definitely split this issue into separate ones, once we define directives.
Side note, somehow related : I've also had a good experience moving from shared pointers to references (std::vector<std::shared_ptr<type> > value
-> type& value
), which (compared to Mosaic and old karmaMapper code), involves changing the setup chain (pass more references, like ofxVP->setup(&ImGuiInstance));
and relying more on constructor() and ~deconstructor() instead of setup(), newObject(), destroy(), exit(), shutdown(), etc. I don't know if this will be really needed, but it helps prevent (dummy?) allocation mistakes and regulating how data is instantiated (trough compile-time errors/restrictions). Once setup, code is more orgnanised (but maybe less accessible to novices). These were the last important ongoing changes on karmaMapper so I only have little experience with this, although the latest versions were stable, except for some old badly-ported objects/effects.
from mosaic.
First a clarification,
in the update of PatchObject class, we are looping on all the created links (output[i]
), a link give us the id of the node where is connected (toObjectID
) and the id of which inlet (toInletID
), so we get the object, his specific inlet, and copy the reference from the outlet the link came from (_outletParams[out]
)
patchObjects[outPut[i]->toObjectID]->_inletParams[outPut[i]->toInletID] = _outletParams[out];
Then, in some nodes, sometimes we have:
*(float *)&_outletParams[0] = *(float *)&_inletParams[0]
but that is a simple internal bypass.
Now, your main proposal, ensuring that data is always valid until pin is destroyed (most of the cases, when the related node is destroyed), is going to add robustness, without doubt, so i agree with you. Let's continue here to talk about how to implement this.
I agree about splitting the performance optimization issue into separate ones, and for now, let's wait for moving shared pointers to references, as you say, it will lead to less accessible code, and i was hoping to find more collaborations from the OF community, once we start to advertise Mosaic over the web.
Anyway, it's always a good choice to optimize performance and robustness, so we can dig deeper, and plan it for the future (shared pointers to references refactor)
from mosaic.
Please, add
- Pin.ID (now i'm using as pin ID the index, first pin index 0, etc...)
- Pin.objectID
to the list
from mosaic.
Closing this, as it's mostly integrated into issue 22 / ofxVPParameters.
from mosaic.
Related Issues (20)
- Linux - Internet not avaliable
- Ubuntu compile issues HOT 2
- Instant crash on launch HOT 3
- VP_LINK_FBO : A render pass for each object ? HOT 13
- Older GPUs and OpenGL 4.1 HOT 2
- No sound menu on Windows 10 ... HOT 1
- [WSL] bwrap: kernel does not support user namespaces HOT 1
- Mosaic patch in xCode HOT 4
- Settings or options for GUI / font sizes? HOT 4
- Mosaic still running after closing using window X in Ubuntu HOT 1
- inputs in glsl nodes HOT 2
- Corrade / Magnum HOT 4
- osx 10.12 support HOT 1
- Instant crash on macOS HOT 6
- Version 0.6.5_ofximgui_next does not render the UI properly. HOT 3
- Error in compile mosaic on Mint 20 HOT 1
- Error in compile on Mint: ofxFFmpegRecorder
- VideoGraber crash Mosaic on Mint with OF(Mosaic) HOT 5
- Flatpack update HOT 1
- Error compiling 0.6.5 on Mint 21.2 HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from mosaic.