Peer references



I've been tackling the issue of weak references in GTK+ recently,
and while thinking of this, came up with the following idea. (Not
really a GObject-2.0 idea, but wanted to write it down while I'm
thinking of it.)  

                                        Owen


There are several situations in which considering an object to be
either referenced or not referenced doesn't work well.

The two main ones are the following:

 * A group of objects that should be kept alive if any of the
   objects in the group is strongly referenced (peers). The
   objects could all be GTK+ objects, but this more commonly
   comes up when associating GTK+ objects with language bindings.

 * A cache that wants to treat objects that are referenced only
   by the cache differently than objects that are strongly
   referenced. (E.g., a cache that wants to keep around at most
   10 not-otherwise-referenced objects.)

An idea that had been floated for this is making the last
reference special in some way, but this is problematical because
there is no guarantee that there will only be one such reference.

So, my idea is to have multiple levels of references - and an
object is kept alive if it is referenced at any level. In the
simplest case there are two levels, strong references, and peer
references.

Then, there is notification when the strongest level of references
changes.

So, diagramatically, the way the first situation is handled looks
like.

  +-+ -------> +-+       
  |A|          |B|    Initial state. A->B link peer reference, B->A link
  +-+ <....... +-+    weak reference.
   ^
   | 

  +-+ -------> +-+
  |A|          |B|    B externally strongly referenced, A->B converted to 
  +-+ <------- +-+    peer reference.
   ^            ^
   |            |


  +-+ .......> +-+
  |A|          |B|    A only peer referenced, A->B converted to weak 
  +-+ <------- +-+    reference.
                ^
                |


  +-+ .......> +-+
  |A|          |B|    B only peer referenced, B->A converted to weak
  +-+ <....... +-+    reference.


  Poof                A and B finalized and freed.

The reason for having multiple levels is when you want to combine
the two above situations and have a group of peer objects refered
to from a cache, then you need to distinguish between:

 - Peer group only referenced itself and in the cache

 - Peer group only referenced by itself (and hence should be freed)

To be concrete about the interface, including multiple levels:

#define G_OBJECT_PEER_LEVEL_CACHE 10
#define G_OBJECT_PEER_LEVEL_PEER  20

typedef void (*GPeerWatchFunc) (GObject        *object,
				gint            new_level,
				gpointer        data);

void        g_object_peer_ref       (GObject        *object,
				     gint            level);
void        g_object_peer_unref     (GObject        *object,
				     gint            level);
GPeerWatch *g_object_add_peer_watch (GObject        *object,
				     GPeerWatchFunc  peer_watch,
				     gpointer        data);
void        g_peer_watch_remove     (GPeerWatch     *watch);


Using this interface, the way you would implement a "peer link"
facility as diagrammed above would look like:

typedef struct
{
  GObject *peer;
  GPeerWatch *watch;
  GWeakRef *self_weak_ref;
  GWeakRef *peer_weak_ref;
} GPeerLink;

GPeerLink * g_peer_link_new         (GObject        *object,
				     GObject        *peer);
void        g_peer_link_remove      (GPeerLink      *watch);

static void
g_peer_link_remove (GPeerLink *link)
{
  if (link->watch)
    g_peer_watch_remove (link->watch);
  if (link->self_weak_ref)
    g_weak_ref_remove (link->self_weak_ref);
  if (link->peer_weak_ref)
    g_weak_ref_remove (link->peer_weak_ref);

  g_free (link);
}

static void
peer_link_self_weak (GObject *object,
		     gpointer data)
{
  GPeerLink *link = data;

  link->watch = NULL;
  link->self_weak_ref = NULL;

  g_peer_link_remove (link);
}

static void
peer_link_other_weak (GObject *object,
		      gpointer data)
{
  GPeerLink *link = data;

  link->peer_weak_ref = NULL;

  g_peer_link_remove (object, link);  
}

static void
peer_link_watch (GObject *object,
		 gint     new_level,
		 gpointer data)
{
  GPeerLink *link = data;
  
  if (level < G_OBJECT_PEER_LEVEL_PEER)
    g_object_peer_ref (link->peer, G_OBJECT_PEER_LEVEL_PEER);
  else
    g_object_peer_unref (link->peer, G_OBJECT_PEER_LEVEL_PEER);
}

GPeerLink *
g_peer_link_new (GObject *object,
		 GObject *peer)
{
  GPeerLink *link = g_new (GPeerLink, 1);

  link->peer = peer;

  link->self_weak_ref = g_object_weak_ref (object,
					   g_object_peer_self_weak_link,
					   link);
  link->peer_weak_ref = g_object_weak_ref (peer,
					   g_object_peer_peer_weak_link,
					   link);
  link->watch = g_object_add_peer_watch (object,
					 g_object_peer_link_watch,
					 link);

  if (g_object_get_peer_level (object) < G_OBJECT_PEER_LEVEL_PEER)
    g_object_peer_ref (link->peer);

  return link;
}




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