GLib parameters



hi all,

as requested by owen, here are some details about the new parameter
system to be used in GLib, along with GLib's new type and
object system.
for the moment, lets say we have the following fundamentals,
and later concentrate on G_TYPE_PARAM:

typedef enum
{
  G_TYPE_INVALID,
  G_TYPE_NONE,
  G_TYPE_PARAM,
  G_TYPE_ENUM,
  G_TYPE_FLAGS,
  G_TYPE_OBJECT
};

G_TYPE_ENUM, G_TYPE_FLAGS and G_TYPE_OBJECT are all classesd types,
that means they have *one* class structure per type id, much like
things work in Gtk already, i.e.:

class structure (one instance per program):

     +----------------------------------------+
     | GtkWidgetClass (GTK_TYPE_WIDGET)       |
     | void (*delete_event) (GtkWidget*,...); |
     |                etc...                  |
     +----------------------------------------+
                           ^  ^
                           |  |
object structure           |  |
(multiple instances        |  |
per program):              |  |
                           |  |
+------------------------+ |  | +------------------------+
| GtkWidget  (Widget1)   | |  | | GtkWidget  (Widget2)   |
| GtkWidgetClass *class;-+-+  +-+-GtkWidgetClass *class; |
| GtkStateType state;    |      | GtkStateType state;    |
| etc...                 |      | etc...                 |
+------------------------+      +------------------------+


parameters, in contrast, will have multiple specification
structures per type id (thus the type system doesn't really
treat them as "classed" types):

typedef struct _GParamSpec        GParamSpec;
typedef struct _GParamSpec        GParamSpecInt;
typedef struct _GParamSpecRichInt GParamSpecRichInt;
typedef enum
{
  G_PARAM_READABLE            = 1 << 0,
  G_PARAM_WRITABLE            = 1 << 1,
} GParamFlags;
struct _GParamSpec	/* base specification */
{
  GType        gtype;
  const gchar *name;
  GParamFlags  flags;
};
struct _GParamSpecRichInt
{
  /* from GParamSpec */
  GType        gtype;
  const gchar *name;
  GParamFlags  flags;

  /* rich int specific members */
  gint minimum, maximum, default_value;
};


parameter specification structures (multiple instances per type id):

                                        +-------------------------------------+
                                        | GParamSpecRichInt (G_TYPE_RICH_INT) |
                                        | param_name="balance";               |
                                        | minumum=-127;                       |
                                        | maximum=127;                        |
                                        | default_value=0;                    |
                                        +-------------------------------------+
                                                                           ^ ^
 +----------------------------+  +-------------------------------------+   | |
 | GParamSpecInt (G_TYPE_INT) |  | GParamSpecRichInt (G_TYPE_RICH_INT) |   | |
 | param_name="pixel_size";   |  | param_name="person_age";            |   | |
 +----------------------------+  | minumum=1;                          |   | |
                       ^ ^       | maximum=120;                        |   | |
                       | |       | default_value=18;                   |   | |
                       | |       +-------------------------------------+   | |
                       | |                       ^ ^                       | |
parameter structures   | |                       | |                       | |
(multiple instances    | |                       | |                       | |
per GParamSpec):       | |                       | |                       | |
                       | |                       | |                       | |
 +-------------------+ | | +-------------------+ | | +-------------------+ | |
 | GParam  (Param1)  | | | | GParam  (Param3)  | | | | GParam  (Param5)  | | |
 | GParamSpec *spec;-+-+ | | GParamSpec *spec;-+-+ | | GParamSpec *spec;-+-+ |
 | value=999;        |   | | value=17;         |   | | value=-127;       |   |
 +-------------------+   | +-------------------+   | +-------------------+   |
                         |                         |                         |
 +-------------------+   | +-------------------+   | +-------------------+   |
 | GParam  (Param2)  |   | | GParam  (Param4)  |   | | GParam  (Param6)  |   |
 | GParamSpec *spec;-+---+ | GParamSpec *spec;-+---+ | GParamSpec *spec;-+---+
 | value=-34223;     |     | value=102;        |     | value=12;         |
 +-------------------+     +-------------------+     +-------------------+



so here we have two parameter types, G_TYPE_RICH_INT, and
G_TYPE_INT, both deriving from the type G_TYPE_PARAM.
comparing Param1, a "pixel_size" parameter, with a
"pixel_size" GtkArg (which is what we'd current have in gtk):

struct _GtkArg
{
  GtkType type;		=GTK_TYPE_INT
  gchar *name;          ="pixel_size"
  union {
    gint int_data;	=999
  } d;
};

struct _GParamSpecInt <------------------+
{                                        |
  GType        gtype;	=G_TYPE_INT      |
  const gchar *name;	="pixel_size"    |
};                                       |
struct _GParam                           |
{                                        |
  GParamSpec *spec;	-----------------+
  union {
    gint v_int;		=999
  } value;
};

we basically just moved the `type' and `name' parameters
into a seperate structure.

so basically GParam parameters are not identified by an inlined
type id and name, but by the parameter specification they point
to. this provides a number of benefits over the old system:

- memory savings; e.g. for all the properties that a GtkWidget
  currently exports, there will be *one* GParamSpec alike
  structure, held in the GtkObjectClass (GObjectClass?) structure.
  all parameters of such a type will point to this param spec
  and don't come with their own name strings.
- unification; GtkArgs currently may be named in different ways,
  e.g. "GtkWidget::visible" and "visible" refer to the same
  argument for a GtkFrame.
- complexity reduction over the current GtkArg system, basically
  the GtkArgInfo cruft will be absorbed by GParamSpec
* customizability; custom parameter types can be created, like
  the above GParamSpecRichInt which comes with maximum, minimum
  and default values - are often needed features for e.g.
  GUI builders. many variations are imaginable, e.g. BSE currently
  has a custom string type that defines character sets of which
  a string parameter may consist.
* scalability; parameter specifications can be as simple or complex as
  an application wants them to be. glib comes with the base types:
  ints, floats, strings...
  applications (library foundations) can extend parameter types to
  rightfully fit their needs while keeping the API reasonably simple:
  only two public functions have to be supported, one to create
  the parameter specification and one to set the parameter value
  (a getter can also be provided where convenient, to hide GParam
  details)
- benefits i forgot because i became too familiar with the concept already ;)

furthermore, since parameter types need to be "officially"
derived from the G_TYPE_PARAM fundamental type, appropriate
functions can be setup to support various _generic_ operations
on parameters. tentative API:

typedef gchar* (*GTypeParamCollector)   (GParam        *param,
                                         guint          n_bytes,
                                         guint8        *bytes);
struct _GTypeParamInfo
{
  guint      sizeof_param_spec;
  void     (*param_spec_free_fields) (BseParamSpec   *pspec);
  void     (*param_init)             (BseParam       *param,
                                      BseParamSpec   *pspec,
                                      gboolean        zeroed);
  void     (*param_free_value)       (BseParam       *param);
  void     (*param_copy_value)       (const BseParam *param_src,
                                      BseParam       *param_dest);

  /* optional, can be NULL for plain memcmp(3) */
  gint     (*param_values_cmp)       (const BseParam *param1,
                                      const BseParam *param2);

  /* optional, for rich types (e.g. for value = CLAMP (value, min, max);) */
  gboolean (*param_validate)   (BseParam *param);

  /* optional, required for vararg getters */
  void (*move_value)       (BseParam       *param,
                            gpointer        value_p);
  /* how to collect parameter values from vararg setters */
  guint               n_collect_bytes;	/* optional (0) */
  GTypeParamCollector param_collector;	/* optional (NULL) */
};


GType g_type_register_param_type (GType                 parent_type,
                                  const gchar          *type_name,
                                  const GTypeParamInfo *info);

typedef void (*GParamExchangeValueFunc) (BseParam *param1,
                                         BseParam *param2);
/* for possible parameter conversions (optional) */
void  g_param_type_register_exchange (GType                   param_type1,
                                      GType                   param_type2,
                                      GParamExchangeValueFunc func);


the standard set of parameters that glib should support out of the box
is probably:

  G_TYPE_PARAM_BOOL
  G_TYPE_PARAM_INT
  G_TYPE_PARAM_UINT
  G_TYPE_PARAM_FLOAT
  G_TYPE_PARAM_DOUBLE
  G_TYPE_PARAM_STRING
  G_TYPE_PARAM_POINTER
  G_TYPE_PARAM_GOBJECT

none of these should be rich types (i.e. come with min/max or default),
since as owen pointed out rightfully to me, glib should supply the
mechanism, not define semantics.
however, i think it'd be very convenient to supply rich types for Gtk,
i.e. floats and ints with min/max and default, since that's one of the
things that could easily be supplied by widgets and GUI builders are
desperatedly lacking.

so in parallel to the above GParamSpecRichInt example, we could have
such a type for gtk:

/* standard (generic) GLib parameter functions */
void            g_param_init            (GParam         *param,
                                         GParamSpec     *pspec);
void            g_param_init_default    (GParam         *param,
                                         GParamSpec     *pspec);
gboolean        g_param_validate        (GParam         *param);
gboolean        g_param_defaults        (const GParam   *param);
gint            g_param_values_cmp      (const GParam   *param1,
                                         const GParam   *param2);
void            g_param_copy_value      (const GParam   *param_src,
                                         GParam         *param_dest);
void            g_param_move_value      (GParam         *param,
                                         gpointer        value_p);
void            g_param_free_value      (GParam         *param);
void            g_param_reset_value     (GParam         *param);
gboolean        g_param_value_convert   (const BseParam *param_src,
                                         BseParam       *param_dest);
gboolean        g_param_values_exchange (BseParam       *param1,
                                         BseParam       *param2);

[gtk, upon startup, registers GTK_TYPE_PARAM_INT_RANGE and conversion
 functions like range_int<->int, range_int<->float]

typedef struct _GtkParamSpecRangeInt GtkParamSpecRangeInt;
struct _GtkParamSpecRangeInt
{
  /* from GParamSpec */
  GType        gtype;
  const gchar *name;
  GParamFlags  flags;
  
  /* standard GtkParamSpec* members */
  gchar *nick;
  gchar *blurb;
  
  /* RangeInt specifics */
  gint minimum;
  gint maximum;
  gint stepping_rate;
  gint default_value;
};

/* create a parameter specification for a range_int */
GParamSpec*     gtk_param_spec_range_int (const gchar  *name,
                                          const gchar  *nick,
                                          const gchar  *blurb,
                                          gint          minimum,
                                          gint          maximum,
                                          gint          stepping_rate,
                                          gint          default_value,
                                          GtkParamFlags flags);
/* returns whether v_int is valid within range or had to be CLAMP()ed */
gboolean        gtk_param_set_range_int  (GParam       *param,
                                          gint          v_int);

with that, the current GtkContainer::border_width argument would
e.g. be implemented as (in gtk_container_class_init):

gtk_object_class_add_param (container_class,
                            ARG_BORDER_WIDTH,
                            gtk_param_spec_range_int ("border_width",
                                                      "Border Width",
                                                      "Padding border in pixels"
                                                      " around the container",
                                                      0, /* minimum */
                                                      1024, /* maximum */
                                                      5, /* stepping_rate */
                                                      0, /* default_value */
                                                      GTK_PARAM_READWRITE));

example use:

gint border_width = 0;

gtk_object_set (frame,
                "border_width", 15,
                NULL);
gtk_object_get (frame,
                "border_width", &border_width,
                NULL);
/* border_width == 15 at this point */


a GUI builder can present this property with a spin button
that steps upwards/downwards by 5 pixels and a tooltip:

Border Width:        |   15| X
                      /
                    /
   +-----------------------------------------------+
   | Padding border in pixels around the container |
   +-----------------------------------------------+

and the builder could omit saving the border_width property to
its GUI description file, if border_width==0 (since that's the default).


ok i guess its time for some rationale on this lengthy mail, GtkArg
has some severe limitations (and also an awkwardly complex implementation
to be found in gtkarg.c, gtkargcollector.c and various places in
gtksignal.c and gtkobject.c), most of which mentioned above already.
GParam is meant to solve that by providing the flexibility required in
some places and be extensible beyond the needs of a toolkit object system.
the above suggested g_param_*() API is not something i invented while
composing this mail, rather, it evolved in BSE and is basically already
implemented there (what's missing is the type registration part; in BSE
the different parameters are still fundamental types).

a complete set of rich parameters can be found in the module beast on
GNOME CVS under beast/bse/bseparam.[hc], and the various objects in
beast/bse/ and beast/plugins/ make exclusive use of this
mechansim to export properties (much like GnomeCanvasItems provide a
GtkArg API only). some older textual description is also available
in beast/docs/bse-typesystem.txt.
90% of BEASTs GUI is generated from the parameter specifications, so i'm
pretty sure this mechanism will be feasible and extensible enough for other
parameterized applications as well.

the parameter types are not only usefull to export object properties, in fact,
this is what GSignal (and for the ones that've heared of it: GClosure) will
build upon as well, and within BSE they also serve as arguments to
procedure types and build the foundation to serialize objects (read/write
objects to/from a text stream).


DISCLAIMER:
since i haven't actually performed the transition of the BSE parameter
system to GLib yet, the above outline is still subject to change, but
the general mechanism is pretty clear already.
it's pretty late already, so please be prepared to forgive minor and/or
major mistakes i made ;)

things that are as of yet not completely resolved:

* since objects, object classes, enum/flags classes, interfaces etc. are
  going to be runtime destructable, so they can be implemented from within
  dynamic modules, i wonder whether this makes sense for parameter types as
  well. on the API side this is just:
   GType g_type_register_param_type (GType                 parent_type,
                                     const gchar          *type_name,
  -                                  const GTypeParamInfo *info);
  +                                  GPlugin              *plugin);
  on the other hand, parameter types provide the most fundamental means
  of comunication and should therefore probably be provided by static
  program components.
* GtkArg/GtkObject have some general-use problems with constructor arguments,
  though this is independant of the GtkArg->GParam transition, a satisfying
  solution still has to be worked out.


---
ciaoTJ



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]