openframeworks / openFrameworks

openFrameworks is a community-developed cross platform toolkit for creative coding in C++.
http://openframeworks.cc
Other
9.86k stars 2.56k forks source link

ofParameter feature discussion. #1391

Closed openframeworks closed 11 years ago

openframeworks commented 12 years ago

This is a feature discussion issue for what ofParameter and its utility clases should be. What you see below is Work In Progress!! - discussion and comments are especially welcome! I will edit and add pseudo code below as the structure of ofParameter and its utility classes gets more flushed out.

Once we are all agreed - this issue will become the reference page for the code development.

Goal: The idea is that ofParameter essentially can act like a variable within your app. Unlike a typical variable an ofParam has a name, a type and a range.

ofParameter will have several advantages when used in your app. They are:

Current State:

ToDo's

elliotwoods commented 12 years ago

Requests

1. External parameters

ofParameter and ofBaseParameter you could perform all the operations on ofBaseParameter, but it doesn't actually contain any data whenever you get or set the data, it calls a function which you override for ofParameter, this gets/sets the internal variable an ofParameterGroup would then be a list of ofBaseParameter

in this way, we can override the parameter, e.g.

class translateParameter : ofBaseParameter<ofVec3f> {
public:
    translateParameter(ofNode & node) : node(node) {  }
protected:
    ofVec3f & get() const { return node.getPosition(); }
    void set(const ofVec3f & value) { node.setPosition(value); }
private:
    ofVec3f & node;
};

This kind of functionality is vital for when we want to make controls for variables which are naturally stored elsewhere.

2. Array parameters

The above example probably doesn't make sense since we're presuming standard numeric parameter types at the moment. Perhaps an ofVec3f parameter overrides a float[3] parameter somehow. Anyway, this would be very useful to have in general.

Perhaps ofBaseParemeter<T> inherits ofBaseArrayedParameter<T,1>

names here are for illustrative purposes only :)

3. Callback identifier

When the event callback is called, somehow it would be useful if the identifier of the parameter being changed was included in the callback, this could be a pointer to the variable, or in the case of arrays, the index of the value which was changed.

Questions

1. Endless parameters

How best to deal with parameters without meaningful bounds?

2. String parameters

Text parameters are important since you can't compile mathematical operators against them, implementing them would involving shaking up the class/interface/template tree a little

arturoc commented 12 years ago

some ideas about implementation and the current state:

you can load and save them to disk ( in a variety of file formats ). they can be easily sent over the network ( osc / udp / tcp / http ) they can be printed out in a more friendly way for debugging

in my opinion the best way to implement this is through the >> and << operator at the lowest level, then there can be utility functions to make things easier but basically anything that implenment those operators can be (de)serialized by ofLog, network utilities, ofFile...

they can be used to auto generate a gui to allow them to be changed.

this is already implemented:

gui.add(parameter.set("name",value,min,max);

about lists of parameters i think perhaps we don't need any special class, just a vector of ofParameters, then:

ofPanel gui;
gui,setup("name",myobject.parameters);

the only thing we need is a way of having a common base for all ofParameters since you can't add different versions of a templated class to a vector, but just by having an empty non templated base should be enough, something like ofBaseParameter of ofAbstractParameter

openframeworks commented 12 years ago

@elliotwoods Agreed on string params! I think external param and cb identifier shouldn't be a problem either.

@elliotwoods @arturoc Does it make sense to look at how OS X does things with NSDictionary? I know @memo was basing a lot of his stuff off of the NSDictionary model.

A crazy ( but maybe awesome idea? ) - ofParam is not templated, but just string based. That way we don't need to worry about how to store a vector of ofParms as they are all the same type. String would work for a lot of things and be quite powerful no?

@arturoc totally agreed on the << / >> for param io - that was what I was imagining. I also think ofParameterGroup might still be needed as it could allow for some powerful grouping capabilities - also the namespace system could be tied to this also.

markpitchless commented 12 years ago

+1 for string support. I would like this now for use in ofxLabel.

I do think it also needs a good number specialization, as numeric params will be the most common (especially if you count bool as numeric).

However it would also be nice if the min/max stuff was built on something a bit more generic. I'd like to able to create string params that can only be set to a given list of strings. Ints/floats that tweak them selves to always be odd or even (ie blur params on some of the cv functions). Roll around numbers ie when number hits x it wraps back aound to y again. Essentially by setting up a load of params I'm defining a control interface for the sketch.

elliotwoods commented 12 years ago

i think ofBaseHasParameters is really important, and that generally implies ofParameterGroup although for the time being, that could just be

ofParameterGroup : public vector<ofAbstractParameter> { };

@markpitchless : verification, restraints and other logic can be performed on the callback event (it gives you a reference to the value which you can change)

In fact, why doesn't this callback function give you a reference to the ofParameter, that way it's simple to act on the value (since the parameter can be operated on like a value) whilst also being able to identify the ofParameter

@openframeworks (theo?) a while back i was pitching for ofParameter to use a void* which would also allow for 'any' usage. the issues with c++ is that you would then need to template the access functions, making everything less trivial. I think we still need to do this through inheritance, e.g. (psuedo code):

class ofBaseParameter {
public:
   virtual stream operators = 0;
   string name, etc;
   get, set data as void*;
   virtual bool isNumeric() = 0;
   virtual string getParameterType() = 0;
protected:
   void* theData;
};

class ofBaseNumericParameter<T> : ofBaseParameter {
   virtual T getMin()  { use numeric limits };
   virtual T getMax() { use numeric limits };
   virtual T getStep() { return 0; }; ///< 0 for float, 1 for int
   stream operators;
   assignment operators;
};

class ofFloatParameter : ofBaseNumericParameter<T> { };
class ofIntParameter : ofBaseNumericParameter<T> { };
class ofIntParameter : ofBaseNumericParameter<T> { };

class ofStringParameter : ofBaseParameter {
...
};
markpitchless commented 12 years ago

@openframeworks

A crazy ( but maybe awesome idea? ) - ofParam is not templated, but just string based. That way we don't need to worry > about how to store a vector of ofParms as they are all the same type.

I think crazy (although I see where your coming from ;-). We will end up doing a lot of type casting. Most int, float params will get passed to functions that want those types which will need casting on every call (unless I'm missing something here?). I don't think we want to be starting a mini dynamic language inside of. Better to do all the conversion to and from strings at serialisation time ie the << and >> operators.

I don't have a good idea for the vector problem though.

I also think ofParameterGroup might still be needed as it could allow for some powerful grouping capabilities - also the namespace system could be tied to this also.

+1 for a container. I'd like a container that allows access to the params by name (like a hash map) as well as position (like a vector). If the container class also has a name field that can be concatenated with the param names to give a namespace. More specialised tools like xml settings could be given the list using its name for a tag enclosing tags for the parameters.

That raises the questions of whether the Groups can contain other Groups. Are they nestable?

elliotwoods commented 12 years ago

@markpitchless : if we have a hash table, e.g. std::map<string, ofBaseParameter*> then we can't have 2 parameters with the same name being stored (since the name becomes the index by which they're stored). Although useful for access, i think a for loop is sufficient to find what you want in this scenario without affecting system constraints.

markpitchless commented 12 years ago

@elliotwoods : whether the Group should allow duplicate names is an important question. I think I was assuming that it wouldn't given the talk of namespaces. I would expect them not to be. Makes life easier for serialisation etc. A gui with 3 sliders with the same name is not so useful. If you want a list for a given param name then make that explicit by using a list type. (If I was doing this in perl,python I'd be building a tree of hashes).

markpitchless commented 12 years ago

@elliotwoods

In fact, why doesn't this callback function give you a reference to the ofParameter, that way it's simple to act on the value (since the parameter can be operated on like a value) whilst also being able to identify the ofParameter

This is an excellent question/suggestion :)

markpitchless commented 12 years ago

verification, restraints and other logic can be performed on the callback event (it gives you a reference to the value which you can change)

Good point. It would be nice if as well as a method callback I could also pass a function obect as a listener. (http://www.cplusplus.com/reference/std/functional/). Then I could say things like:

ofxParameter<int> medianBlur;
medianBlur.set("Median Blur", 0, 0, 17);
struct onlyoddclass {
    operator()(int & v) { if (v % 2 == 0) v++; }
} onlyodd ;
medianBlur.addListener(onlyodd);

Or build up a library of useful classes for various types of validation.

elliotwoods commented 12 years ago

@markpitchless - you could remove 'onlyoddclass' from your example, i think it's a good idea, i presume you'd (again), take the ofParameter<int> & rather than an int & as the function argument also onlyoddclass would need to inherit ofBaseDoesStuffWithParameter<T> otherwise addListener couldn't be polymorphic i think. if we then use a different operator than operator(), e.g. parameterCallback(ofParameter<T> &) then we could also attach the calback easily to any class (which might otherwise have an operator(). The callback for ofParameter<T> & wouldn't be overriden so you could still use it exclusively, but maybe not as tidily.

But then we'd have 2 mechanisms for callbacks, which isn't good

ofTheo commented 12 years ago

@elliotwoods sorry - yeah its me ( I had to sign into the OF account to do some admin ), whoops!

There is some great discussion here.

Two quick points: -I think names should be unique within a namespace. -I think we could have groups nested within groups. I think NSDictionary has a system like this and I think in general a group of groups can be pretty common.

Here is a list of all the things I can think we might want to hold in an ofParam.

There are some other things that imo are not so much params but things you would want in a gui:

Not sure if we would want to have those as ofParams or keep that stuff seperate.

elliotwoods commented 12 years ago

i think 'String value: Read only / label' also falls into the list of things that aren't really params, but are gui-useful

regarding string list value, check back to http://dev.openframeworks.cc/pipermail/of-dev-openframeworks.cc/2012-February/003962.html

markpitchless commented 12 years ago

The id and serial numbers from a kinect object would seem to be valid read only string parameters to me.

elliotwoods commented 12 years ago

another totally seperate discussion to have is, what is the role of ofParameter in addon interoperability (note http://forum.openframeworks.cc/index.php?topic=7795.0 )

e.g. would we encourange ofxKinect to use a const-like ofParameter instead of getSerialNumber() ?

markpitchless commented 12 years ago

@elliotwoods : thanks for the pointers regards using a function object, I'll do some reading, coding. Not used them that much in cpp so far. This is just the place I would reach for an anon function (lambda, closure) in other languages, a func object seems to be the closest to that in the the current version of cpp (ie C++03).

markpitchless commented 12 years ago

e.g. would we encourange ofxKinect to use a const-like ofParameter instead of getSerialNumber() ?

Or to put that another way if an addon has a group of ofxParameters do those still need accessors? Is the listener hook and min/max enough encapsulation already? If that addon can return a ofParamGroup of its params then changes to those params via the group wont go though the accessors anyway, so that is maybe a strong argument for not having them.

ofTheo commented 12 years ago

@elliotwoods thanks for the forum link - I forgot about that discussion. ofEnum is pretty sweet! Or we could just typedef an vector of strings as an ofStringList ?? and pass that through as the templated type. ( would that work? )

I guess the big decision to make is how we structure the different types.

Do we:

Are there any other approaches I am missing which we could look at?

Even if we do the template approach we will need a isNumerical(), getNumDimensions() ( ie for a vec3 ) for gui developers. To know how to handle the parameter.

arturoc commented 12 years ago

about using strings for everything, i don't think is a good idea, as it is right now using templates it can hold anything and serialization is already solved for every primitive type and most of OF types through the << >> operators

about something like groups or dictionaries, a std map will solve it and for general use i think it's actually easier to use individual parameters than grouping them in a dictionary, you will need to pass them to a panel one by one but you'll need to do that for a dictionary also and to use them later is easier to directly use the parameter than calling it through a dictionary. creating the gui, is something you usually do just once while accessing the paramters is something you do several times in the code, so in my opinion having individual variables is in the end less verbose than using maps.

about things like isNumerical, getNumDimensions, i think it's also solved if you use individual variables instead of dictionaries, then you just create default controls for each type and when you call gui.add(parameter) it knows how to handle it from it's type as it is working now for float, int and bool which are rendered as sliders and checkbox.

for something like a list of checkboxes for a multiselection, for example, you can just pass a vector that contains several ofParameter

i've been using ofParameter a lot lately, the way i use it is something like:

class BlobDetector{
    ofParameter<int> maxNumBlobs;
    ofParameter<float> minArea,maxArea;
    ofParameter<bool> autoThreshold;

    void update(){
         for(int i=0;i<maxNumBlobs;i++)
         ...
    }
}

If you were using a dictionary you loose the declaration of the parameters in the .h as a reference of what parameters you have available for that class, you loose autocompletion and also you need to access them as parameters["maxNumBlobs"] which is way more verbose than accessing them as just a variable, like;

    void update(){
         for(int i=0;i<parameters["maxNumBlobs"];i++)
         ...
    }

then in testApp, or in a Gui class i create a panel for each class containing parameters and initialize the params in the class that contains them

The main problem i see with a base class for every parameter and dictionaries is to differentiate them after they are in a collection, since c++ doesn't have introspection this kind of problems are difficult to solve and usually involve ugly hacks that make the code super messy

arturoc commented 12 years ago

btw, just removed the default min and max using numeric limits so ofParameter allows any type

ofTheo commented 12 years ago

@arturoc I agree about not using strings for everything ( it was just a crazy idea :)

However I think the a grouping ability is really key. I don't want to have to add each individual element into my gui.

what I would be after is something as global even as the following.

gui.add( ofGetAppParams() ); 

elsewhere in my project I can either enable auto adding of ofParam to the global list / group, or say:

ofAddAppParam( myParam1 ); 
ofAddAppParam( myParam2 ); 

This is of course one way to do it. Another way would be to work with ofParameterGroups directly:

appGroup << ofFromFile("appSettings.json");

visionGroup.setName( "vision" );
visionGroup.add( threshold );
visionGroup.add( blur );
visionGroup.add( erode );

gui.add( appGroup );
gui.add( visionGroup );

ofToFile("settings.json") << gui.getParams(); 

Either way - I think having ofParam lists / groups is going to be super important. I agree having them as a variable is super useful - but being able to work with them as a group will be great too.

arturoc commented 12 years ago

mmh yes i was actually thinking now about serialization of whole panels for example and it could be useful there too. we can use:

http://www.appinf.com/docs/poco/Poco.Any.html#7157

which allows to cast back to the original type from an Any and will solve most of the problems of using ofAbstractParameter. actuallly @memo and i used it to do an ofDictionary class some time ago and it works quite well, it should be just a matter of making a wrapper to make it's use easier for people doing addons around ofParameter

elliotwoods commented 12 years ago

and then with ofBaseHasParameters we could do

ofxKinect kinect;
gui.add(kinect);

(which would work especially well presuming ofBaseDraws can act as a parameter. But perhaps it shouldn't)

Also to note, if string parameters can be read-only, perhaps we want to be able to make any parameter read-only. Read-only doesn't need to be strict like const, but does need to denote that a gui should not attempt to change this value, and it should not be loaded from disk (i.e. it's for feedback only)

bilderbuchi commented 12 years ago

Great points so far. A couple things which I'd like to add (mainly from a GUI perspective I guess):

I think it would be useful if ofParams would always have a "displayname/label". This could be the variable name by default, but can be set individually, and can be used e.g. to label parameters in a gui, and to circumvent problems with >1 ofParams having the same name - variable name is distinct (VolChannel1, VolChannel2), but the name used for display etc. can be the same (Volume)

I think it would be great if ofParams would be extendable, in that additional attributes can be easily added, where needed. I'm thinking e.g. MIDI or OSC information which is associated with an ofParam, but also thing we haven't thought about yet!

I want to reaffirm that it is important that we can distinguish between distinct ofParams which hold the same type of value - is an ofParameter<bool> a toggle or momentary button? Is an ofParameter<int> just a counter, an encoder/jogwheel giving +/-1, does it loop around?... I don't know which technical way is the best, if ofToggleParameter is better than a string ParamType...

+1 to some mechanism to group ofParams. This could also be useful e.g. for automatic construction of OSC address strings (/page1/channel3/gain 0.78).

elliotwoods commented 12 years ago

Looking at Poco::Any, we see:

const std::type_info & Poco::type() const;

Which surprisingly, actually works for non-std types: http://www.cplusplus.com/reference/std/typeinfo/type_info/

i had a crazy thought this morning ....

class ofBaseObject {
public:
    virtual string valueAsString() = 0;
    virtual string typeAsString() = 0;
    virtual friend ostream& operator<<(ostream& os) { os << this->valueAsString() };
    virtual friend istream& operator>>(istream& is);
    virtual size_t elementCount() = 0;
    virtual type_info elementType() = 0;
};

class ofVec3f : ofBaseObject {
...
public:
    string valueAsString();
    string typeAsString() { return "ofVec3f"; }
    size_t elementCount() { return 3; }
    type_info elementType() { return typeid(float); }
    etc..
};

class ofPixels_<T> : ofBaseObject {
public:
    size_t elementCount() { return this->getWidth() * this->getHeight() };
    type_info elementType() { return typeid(T); };
};

although, given type_info's magic powers, this is less necessary than i originally anticipated

roxlu commented 12 years ago

Hi OF!

I haven't got time to read all of the above posts, sorry for that. But it looks like you're aming for what I call a "any type". The crtmpserver library has a good example of an any type which I personally really like, it's called "Variant" there and can if you download the source (dir: sources/common/src/utils/misc/Variant.cpp). It has de-/serialization in xml and json.

Also messagepack seems relevant for this discussion: http://msgpack.org/

Sorry.. bit late aboard but I hope it's of some value.

roxlu

elliotwoods commented 12 years ago

Variant.cpp is visible here: https://gist.github.com/3082283 It is hardcoded for a set of types (generally standard numeric types). Using it would suggest hardcoding an ofVariant for all the types we wanted it to work with

arturoc commented 12 years ago

@elliotwoods about ofBaseHasParameters, i've been thinking about how to simplify the inheritance hierarchy lately and i think one of the main problems is all the ofBaseHas* classes. The Has in the name clearly implies aggregation: class A contains class B in it, and we are forcing it to be inheritance, adding several base classes that can probably be avoided. The same example that you post could be expressed as:

gui.add(kinect.parameters);

were parameters is a ofParameterGroup. the syntax is not so much verbose, it's easier to understand what's going on + doesn't require a new base class.

Inheritance should only be used for cases were class B is a class A and with the Has bases we are forcing something that is clearly aggregation into inheritance.

arturoc commented 12 years ago

@bilderbuchi ofParameter already has a name, that is later used when constructing a gui element from it.

about extendability, you can always use inheritance, like ofxMidiParameter or ofxOSCParameter, but i think it's not a good pattern, (i've tried it already :) instead you have a ofParameter that can be used by anything, a gui, a osc sender or receiver... then ofxOscSender can have a mechanism to convert an ofParameter into a osc message. if what you want, is to control a parameter through osc or midi then ofxOscReceiver or ofxMidi should have a way of binding a certain message type, note... to a parameter

and about different gui elements for parameters of the same type i think that's something that should happen in the gui: the parameter should be independent from it's graphical representation, that way 1 parameter can be used for different gui types:

ofParameter<int> p;
gui.add(p.set("param",0,0,100)); // creates a slider, the default for ints
gui.addJogDial(p); // Creates a jog dial
...

that way because of the event mechanism in ofParameter you can even bind the same parameter to several controls, network addons... and have all of them synchronized automatically

markpitchless commented 12 years ago

@elliotwoods:

Also to note, if string parameters can be read-only, perhaps we want to be able to make any parameter read-only. Read-only doesn't need to be strict like const, but does need to denote that a gui should not attempt to change this value, and it should not be loaded from disk (i.e. it's for feedback only)

I really like this interpatation of read only. Avoids the silly games you have to play when implementing proper readonly vars (they still need setting somewhere!), but gives us the effect we want.

markpitchless commented 12 years ago

@bilderbuchi

I think it would be useful if ofParams would always have a "displayname/label". This could be the variable name by default, but can be set individually, and can be used e.g. to label parameters in a gui, and to circumvent problems with >1 ofParams having the same name - variable name is distinct (VolChannel1, VolChannel2), but the name used for display etc. can be the same (Volume)

++ to this. Ideally I'd like to see limits on the valid chars for the name, with labels allowed pretty much anything. E.g. names can't have spaces and basically match ^\w+$, are a valid xml tag name, yaml/json var name. Makes life easier when doing serialization, avoiding those messy replace functions that convert the names. Will need a basic restriction on name anyway if we want to have namespace strings. Ie not allowing /. Handy for OSC that will want to create "$groupname/$paramname" type strings.

I'd also like a description (documentation) field in the params.

ofxParameter<float> threshold("Threshold", 0, 0, 255);
threshold.setDescription("Distance threshold applied to depth image.");

Guis can use it for tooltips, help boxes, settings mods could add them as comments to the xml, yaml, json they export.

Addons will then have a nice, self documented bit of code in their setup that describes the param interface to the addon.

markpitchless commented 12 years ago

@arturoc : I would vote for kinect.parameters as well.

Alos agree regards extendability, I dont think we want to sprout lots of ofxMidiParameter type classes or encourage ofxParameter to be used that way. The type comes from the type given to the param, ofxParameter. It looks to me too much like a parallel class hiearcy for types, which can't be good. We should think of ofxParam and Group more like a datatypes.

This also applies to widget selection, it is based on the type given to the param. People create widgets that work with an int, float, list of floats, ofPoint, etc which is held in an ofParam given to the widget at setup time.

Regards:

gui.addJogDial(p); // Creates a jog dial

Can we please not do that, instead it is done this way (the way ofxGui currently works):

ofxJogDial p;
gui.add(p);

I think it is a messy proliferation of methods and makes the gui look at first sight like it has a limited set of widgets. Also means there are two ways to add, say, a toggle (gui.add( toggle ) and gui.addToggle). Users will need to learn the more general pass a widget object version at some stage and all those add* methods will be calling it anyway.

I do think this is very nice:

gui.add(p.set("param",0,0,100)); // creates a slider, the default for ints

Having add overloaded for a common set of data types is really nice. Will cover the majority of the examples.

elliotwoods commented 12 years ago

agreed, +1 for kinect.parameters rather than ofBaseHasParameters. very true. i'm not sure adding more properties to a parameter (e.g. a second name, and a description) are going to be popular. It doesn't seem very oF-like.

<EDIT ignore this bit about ofxJogDial>

I really don't like ofxJogDial p; As it means:

  1. There's 2 lines of code for every widget that you want to add (this was really unpopular in my first gui - ofxCvGui, and i think was laborious and messy myself)
  2. ofxGui::add(...) is already be overloaded for the different parameter types, without the need for specifying the type of control that needs to be offered. So ofxGui::addJogDial makes sense as an alternative method to the de-facto 'add a slider' behaviour.
  3. Wait, isn't using a jog dial with a mouse a really bad idea anyway? :)
  4. ofxJogDial is cramping my namespace, man....

</EDIT>

also void add(ofxBaseGui * element); ... shouldn't that be void add(ofxBaseGui & element); ? for beginners, and for any advanced users who learnt to code c++ in the 202nd decade

Perhaps I'm getting to bogged down on ofxGui (non core) on a thread that's about ofParameter (core)

I'm going to separate out the next thing i want to say...

elliotwoods commented 12 years ago

If only 1 thing i say gets taken away from this discussion: I want (as aforementioned) the possibility of creating a parameter where the data is stored elsewhere, (aka External Parameters above) e.g.

myTranslateParameter : ofSomewhereInTheInheritanceHeirarchyOfOfParamater {
public:
    myParameter(ofNode &);
//something to do with overriding getting and setting
protected:
   ofNode thatNodeImGoingToTranslate;
};
markpitchless commented 12 years ago

@elliotwoods:

  1. You also save a line as the widget is the variable (wrapped in a parameter). You use it direct so it is the same thing. See the ofxGui example. openFrameworks / apps / devApps / guiExample For addons that provide params they have been decalred for me.
  2. So how do I add new widget types? Seems I need to add a method to ofxGui. Which is going to be a pain if I want to distribute widgets in separate addons.
  3. Probably :)
  4. Dude, was just following the current ofxGui naming. Also there is a thread on dev list about gui naming.

I think some gui discussion is valid here given ofxParamater is planned to be a key component of ofxGui.

markpitchless commented 12 years ago

@elliotwoods : Not sure I understand your 'creating a parameter where the data is stored elsewhere' example, even after reading back over the thread. Could this be done using a ref type or pointer e.g. ofxParameter<ofNode*> ? Could you elaborate a little please?

elliotwoods commented 12 years ago

You also save a line as the widget is the variable (wrapped in a parameter)

to confirm, you mean ofxJogDial would inherit ofParameter rather than ofxBaseGui? EDIT : yes i see how the ofxGui example works. You're right, we're adding ofxBaseGui's in that example not ofParameter's

I think some gui discussion is valid here given ofxParamater is planned to be a key component of ofxGui.

Ah right, well (and someone let me know if i'm getting this wrong at all), ofParameter is going to be in the core, whilst ofxGui is going to remain as a core addon. Therefore their fates are not so intertwined.

i.e. ofxParameter will become ofParameter.

ofParameter therefore has to remain as slim as possible, and be generally agnostic of ofxGui (hence why ofxBaseGui and ofxParameter are seperate classes).

We want people to use ofParameter's within their own gui (not necessarily ofxGui), without having to depend on ofxGui. Other gui's also include things like ofxTimeline.

creating a parameter where the data is stored elsewhere

Another example might be a toggle parameter for whether an ofVideoPlayer is playing or not. ofParameter<ofVideoPlayer> doesn't make any sense as the video player isn't really the parameter, it's more of a ofParameter<bool>. However, there is no boolean anywhere which describes the playing state, however, the playing state can be set and get as a bool. This implies a bool parameter which gets and sets elsewhere, without there being an actual bool anywhere. Other example:

These are all things that can be set and get as a simple variable, but we cannot naturally use a reference to a variable in these instances as no exposed variable exists.

N.B. Optimistically: in some cases we can in fact replace the get/set paradigm with an ofParameter, but it seems unlikely and somewhat unnatural.

Sorry for jerking you on the naming. I know this is all hypothetical and we'll sort that out later. And it's great to get good discussion on these points, the more questions we throw in, the more confident we can be when all the decisions are made.

arturoc commented 12 years ago

@elliotwoods for the cases you describe

Enable capture on a kinect (bool)
Translate on an ofNode (ofVec3f)
Lens offset on an ofxRay::Projector (ofVec2f)
Fov on an ofCamera (float)
Volume on a sound (float)

i think the events in ofParameter are enough:

class testApp{
   ofParameter<bool> paused;
   ofVideoGrabber grabber;
   ofxPanel gui;
   void setup(){
        gui.add(paused.set("paused",false);
        paused.addListener(this,&testApp::pausedPressed);
   }
   void pausedPressed(bool & paused){
        grabber.setPaused(paused);
   }
   void update(){
       if(someCondition) paused = true;
   }

}

N.B. Optimistically: in some cases we can in fact replace the get/set paradigm with an ofParameter, but it seems unlikely and somewhat unnatural.

actually i've been using this pattern in my projects and it works really well although it seems too radical to use it in OF by default. It remembers me a little to the c# properties:

http://msdn.microsoft.com/en-us/library/x9fsa0sw.aspx

elliotwoods commented 12 years ago

Hypothetical addon inter-op

Using the update paradigm suggested above by @arturoc

Outline

A, B, C share an ofParameter P, (presume A, B and C are addons)

A ties the parameter to the x position of an ofNode. B is a gui C is a timeline

If A receives a notice that the parameter has been changed, it should move the ofNode Whenever the ofNode is moved, A will notify the ofParameter to update

User makes change in gui B to P

User moves node in A

Without the cached value, we would call an event every time the nodes position is set to reassign the ofParameter, therefore causing a feedback loop.

Feedback loops

I'm still a little worried about feedback loops, e.g. if we're parameterising rotation, then to test changes in the ofNode's we have to decompose the rotation from the matrix, which can have rounding errors (e.g. if you set it to 1 value, if you get it back it will likely be marginally different, causing a continuous update of the parameter, but this would only happen every frame).

Could this be worked around?

We don't cache, we just compare the get result vs the parameter:

The rotation was set with one value, and returns a rounding error'd different value, the 2 will not generally be equal

We cache the get value whenever we set it [1]

They should remain equal in following frames until the transform matrix is changed. No parameter update will occur. The user is responsible in general for creating a cache to compare values.

We set the parameter with the get value

We cause P to trigger C and A, causing A to set the rotation again, resulting in a feedback loop (stack overflow).

We implement an epsilon value

Messy, difficult to get right.

 Conclusion

Using an update loop it's possible to synchronise a parameter with an external variable without using the External Parameters suggestion above.

Using an update loop is what I was trying to avoid, but it's likely that the inheritance tree of ofParameter will need space for other features, and so perhaps it's best to rescind the External Parameters request.

(If this is final, then i can start working with this paradigm straight away)

arturoc commented 12 years ago

i've been thinking about feedback loops but i think that's unavoidable completely when dealing with something like this so you have to be careful to not have them for each case. but in the case you explain i don't see why you need an update loop though, you can just make A update the variable if it comes from the ofParameter event without reupdating the parameter, and any other change will change the parameter too:

class NodeUpdater{
    void setup(){
         rotation.addListener(this,&NodeUpdater::rotationChanged);
    }
    void anyMethod(){
        rotation = ...;
    }
    void rotationChanged(float & rotation){
       node.setRotation(rotation);
    }
    ofParameter<float> rotation;
}

but perhaps i'm not understanding your example well, can you write down some code?

elliotwoods commented 12 years ago

You need to be able to rotate the node by any of the other rotation possibilities (applying an ofMatrix4x4 or ofQuaternion, rotating around an arbitrary axis). Therefore the user shouldn't have to set the parameter directly, A should set the parameter when it's necessary to update it. The real data is stored in the ofNode's transform, the ofParameter is mirroring it and updating it when necessary

arturoc commented 12 years ago

i see, actually i'm having the same problem trying to make synchronization through osc in both directions. don't know what the possible solution could be. perhaps the event should signal where the change is coming from in some way and if the sender is the same as the receiver then the receiver doesn't update again

markpitchless commented 12 years ago

Could that be handled by ignoring sets to a parameter that occur in its listeners? Not sure I like this but would protect against infinite loops. Debugging the missing sets might be worse than tracing the segfault!

template<typename ParameterType>
void ofxParameter<ParameterType>::setValue(ParameterType v){
    if (bInNotify) return;
    bInNotify = true;
    obj->value = v;
    ofNotifyEvent(obj->changedE,v,this);
    bInNotify = false;
}

Or still update the value but don't send another event.

arturoc commented 12 years ago

mmh, will try it but it seems like a good solution

ofTheo commented 12 years ago

the osc in both directions seems tricky no? are you talking about a situation where a change could go back and forth over osc endlessly?

@markpitchless ha thats awesome - sort of like a mutex for events :)

arturoc commented 12 years ago

yes the idea is that you can have an application with some parameters, then the same parameters in a gui in a different application, if you change the parameters in the gui they notify the oscSender which updates the remote parameters. i have that working, the idea now is that if the application updates itself the parameters in the gui have to be updated too.

i think using a similar pattern to mark's but in the oscReceiver it can be implemented without a feedback loop but it would need some kind of comunication in between the oscReceiver and the oscSender. i think i'm just going to add the capabilities of sending a parameter or parameter group to the normal ofxOsc addon, then another higher level addon can have a receiver and a sender and do a more automatic synchronization avoiding the feedback loop

arturoc commented 12 years ago

yay it works! have just pushed some changes to the preview PR with an ofxOscParameterSync class that can synchronize a group of parameters over the network in both directions without feedback loops

markpitchless commented 12 years ago

@ofTheo nice, an event muxex, though probably not thread safe in this case :) Which does raise the question of should be thinking about thread safety for params? If addons communicate with the app via a param interface, and those params deal with threading that should make addons that run in another thread easier to code? (I've not done any threading in C++ yet, and my coding experience is mainly multi process rather than threaded).

markpitchless commented 12 years ago

@arturoc : excellent work on the OSC sync. I really want a setup where the GUI gets mirrored over OSC so I can control installations from my tablet/phone using TouchOSC, Control or similar. Sweet. No more crouching under the screen at a stupid angle with a keyboard balanced on one knee and a mouse on the other!

arturoc commented 12 years ago

@markpitchless thanks :)

about making ofParameter thread safe, i don't think it's necessary, if you are working with threads you should make your own code thread safe, and putting locks in there can make other application using mutexes suffer of dead locks