Giter Club home page Giter Club logo

Comments (7)

d3cod3 avatar d3cod3 commented on July 28, 2024

Yes, absolutely agree! It was something i had for some time in the TODO list.

from mosaic.

Daandelange avatar Daandelange commented on July 28, 2024

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.

d3cod3 avatar d3cod3 commented on July 28, 2024

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.

Daandelange avatar Daandelange commented on July 28, 2024

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.

d3cod3 avatar d3cod3 commented on July 28, 2024

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.

d3cod3 avatar d3cod3 commented on July 28, 2024

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.

Daandelange avatar Daandelange commented on July 28, 2024

Closing this, as it's mostly integrated into issue 22 / ofxVPParameters.

from mosaic.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.