ofxParameterCollection: Manage multiple ofParameters

Hey All,

Just posted a new addon: ofxParameterCollection

It manages an indefinite number of ofParameters of the same type while providing simple serialization, deserialization and notification. The class is useful in situations where you would like to work with ofParameters but you don’t know ahead of time how many parameterized items you will have. In essence, the class is like an std::vector for ofParameters, and its use cases are similar to that of std::vector, but it also provides deserialization and notifications of changes in the collection (both when items are added or removed and when a value in the collection changes).

Hope it is useful for others!

1 Like

Nice! I also recently made something like this; they’re abstractions of ofParameterGroup to act as the core of UI elements, so for example ofParameterSelect which will then contain:

ofParameterSelect
-> ofParameter<string> id; # current option selected
-> ofParameter<int> idx; # idx of option
-> ofParameterGroup opts;
-> -> { ofParameter<bool> ... }

(new ofParameterSelect())->set("mySelector", {"option a", "option b", "option c"});

with some internal logic to keep everything sync’d together when any internal param is updated

There’s also a rewritten ofSerialize and ofDeserialize which seem to miss out some param types in OF main, and some ofxZmq and ofxOsc stuff for synchronising across sketches

Here are the serialize / deserialize funcs for JSON (though I see you are using XML?):


bool ofxSync::Syncer::deserialize(const ofJson & js, string parent){

    string type = js["type"];
    string name = js["name"];

    bool isGroup = type == typeid(ofParameterGroup).name();
    bool isFloat = type == typeid(ofParameter<float>).name();
    bool isInt = type == typeid(ofParameter<int>).name();
    bool isBool = type == typeid(ofParameter<bool>).name();
    bool isColor = type == typeid(ofParameter<ofColor>).name();
    bool isVec2f = type == typeid(ofParameter<ofVec2f>).name();
    bool isPoint = type == typeid(ofParameter<ofVec2f>).name();
    bool isVec3f = type == typeid(ofParameter<ofVec3f>).name();
    bool isString = type == typeid(ofParameter<string>).name();
    
    if (parent == "") {
        ofLogError("ofxSync") << "deserialized parameter has no parent";
        return false;
    }

    if (isGroup) {
        ofParameterGroup p;
        p.setName(name);
        addToGroup( root, parent, p );
        for (auto & c : js["value"]) deserialize(c, name);
    } else if( isColor ) {
        ofParameter<ofColor> p;
        p.fromString( js["value"] );
        p.setName(name);
        ofParameter<ofColor> min;
        ofParameter<ofColor> max;
        min.fromString( js["min"].get<string>() );
        max.fromString( js["max"].get<string>() );
        p.setMin( min );
        p.setMax( max );
        addToGroup( root, parent, p );
    } else if( isVec2f ) {
        ofParameter<ofVec2f> p;
        p.fromString( js["value"] );
        p.setName(name);
        ofParameter<ofVec2f> min;
        ofParameter<ofVec2f> max;
        min.fromString( js["min"].get<string>() );
        max.fromString( js["max"].get<string>() );
        p.setMin( min );
        p.setMax( max );
        addToGroup( root, parent, p );
    } else if( isVec3f ) {
        ofParameter<ofVec3f> p;
        p.fromString( js["value"] );
        p.setName(name);
        ofParameter<ofVec3f> min;
        ofParameter<ofVec3f> max;
        min.fromString( js["min"].get<string>() );
        max.fromString( js["max"].get<string>() );
        p.setMin( min );
        p.setMax( max );
        addToGroup( root, parent, p );
    } else if( isPoint ) {
        ofParameter<ofPoint> p;
        p.fromString( js["value"] );
        p.setName(name);
        ofParameter<ofPoint> min;
        ofParameter<ofPoint> max;
        min.fromString( js["min"].get<string>() );
        max.fromString( js["max"].get<string>() );
        p.setMin( min );
        p.setMax( max );
        addToGroup( root, parent, p );
    } else if( isInt ) {
        ofParameter<int> p;
        p.fromString( js["value"] );
        p.setName(name);
        p.setMin( ofToInt( js["min"].get<string>() ) );
        p.setMax( ofToInt( js["max"].get<string>() ) );
        addToGroup( root, parent, p );
    } else if(isFloat ) {
        ofParameter<float> p;
        p.setName(name);
        p.fromString( js["value"] );
        p.setMin( ofToFloat( js["min"].get<string>() ) );
        p.setMax( ofToFloat( js["max"].get<string>() ) );
        addToGroup( root, parent, p );
    } else if(isBool ) {
        ofParameter<bool> p;
        p.fromString( js["value"] );
        p.setName(name);
        addToGroup( root, parent, p );
    } else {
        ofParameter<string> p;
        p.fromString( js["value"] );
        p.setName(name);
        addToGroup( root, parent, p );
    }
    
    return true;


}




bool ofxSync::Syncer::serialize(ofJson & js, ofAbstractParameter & param_){

    if(!param_.isSerializable()) return false;

    bool isGroup = param_.type() == typeid(ofParameterGroup).name();
    bool isFloat = param_.type() == typeid(ofParameter<float>).name();
    bool isInt = param_.type() == typeid(ofParameter<int>).name();
    bool isBool = param_.type() == typeid(ofParameter<bool>).name();
    bool isColor = param_.type() == typeid(ofParameter<ofColor>).name();
    bool isVec2f = param_.type() == typeid(ofParameter<ofVec2f>).name();
    bool isPoint = param_.type() == typeid(ofParameter<ofVec2f>).name();
    bool isVec3f = param_.type() == typeid(ofParameter<ofVec3f>).name();
    bool isString = param_.type() == typeid(ofParameter<string>).name();

    string n = param_.getName();
    if (parents.find(n) != parents.end()) {
        js["parent"] = parents[n];
    } else {
        ofLogError("ofxSync") << "no parent found for" << param_.getName();
        return false;
    }
    js["type"] = param_.type();
    js["name"] = n;
    js["props"] = (props.find(n) != props.end()) ? props[n] : "";

    if ( isGroup ) {
        ofJson jsonGroup;
        for(auto & p: param_.castGroup()) {
            ofJson c;
            serialize(c, *p);
            js["value"].push_back(c);
        }
    } else {
        js["value"] = param_.toString();
        if (isColor) {
            ofParameter<ofColor> p = static_cast <const ofParameter<ofColor> &>(param_);
            js["min"] = static_cast <const ofParameter<ofColor> &>(p.getMin()).toString();
            js["max"] = static_cast <const ofParameter<ofColor> &>(p.getMax()).toString();
        } else if (isVec2f) {
            ofParameter<ofVec2f> p = static_cast <const ofParameter<ofVec2f> &>(param_);
            js["min"] = static_cast <const ofParameter<ofVec2f> &>(p.getMin()).toString();
            js["max"] = static_cast <const ofParameter<ofVec2f> &>(p.getMax()).toString();
        } else if (isVec3f) {
            ofParameter<ofVec3f> p = static_cast <const ofParameter<ofVec3f> &>(param_);
            js["min"] = static_cast <const ofParameter<ofVec3f> &>(p.getMin()).toString();
            js["max"] = static_cast <const ofParameter<ofVec3f> &>(p.getMax()).toString();
        } else if (isPoint) {
            ofParameter<ofPoint> p = static_cast <const ofParameter<ofPoint> &>(param_);
            js["min"] = static_cast <const ofParameter<ofPoint> &>(p.getMin()).toString();
            js["max"] = static_cast <const ofParameter<ofPoint> &>(p.getMax()).toString();
        } else if (isFloat) {
            ofParameter<float> p = static_cast <const ofParameter<float> &>(param_);
            js["min"] = static_cast <const ofParameter<float> &>(p.getMin()).toString();
            js["max"] = static_cast <const ofParameter<float> &>(p.getMax()).toString();
        } else if (isInt) {
            ofParameter<int> p = static_cast <const ofParameter<int> &>(param_);
            js["min"] = static_cast <const ofParameter<int> &>(p.getMin()).toString();
            js["max"] = static_cast <const ofParameter<int> &>(p.getMax()).toString();
        }
    }
    
    return true;

}

Yes, it is XML only right now. Thanks for posting your code!

By the way, to serialize and deserialize ofParameters with custom types there’s no need to rewrite any built-in functions. All you have to do is create the following, in a header most likely, and in the global namespace:

To serialize:

inline std::ostream& operator<<(std::ostream& os, const MyCustomType& myObject)
{
	// Function that takes the object and returns an std::string representation of it:
	os << myCustomTypeToString(myObject);
	return os;
}

To deserialize:

inline std::istream& operator>>(std::istream& is, MyCustomType& myObject)
{
	std::string inString;
	std::getline(is, inString);
	// Function that takes a string and returns the object:
	myObject = myCustomTypeFromString(inString);
	return is;
}

oF (de)serialization relies on ofParameter.toString and fromString, which in turn will call these functions.

Neat, I didn’t know about this, thanks!

c.