Re: Introducing "toggle references"



On Sat, 2005-04-30 at 13:56 +0200, Tim Janik wrote:
> On Thu, 28 Apr 2005, Owen Taylor wrote:
> 
> > On Wed, 2005-04-27 at 20:07 -0400, Owen Taylor wrote:
> >
> >> I'll do a new version tomorrow.
> >
> > Here's a new version. I think it addresses all the points that
> > were discussed.
> >
> > Two other changes in the new version:
> >
> > - in gdatasetprivate.h gsize instead of gulong is used for
> >   pointer => int casts. Really we'd want uinptr_t, but
> >   gsize works on all platforms I know. (gulong apparently
> >   won't work on win64)
> >
> > - The docs for g_object_add_weak_ref() are extended to
> >   describe the use case in some detail.
> 
> the patch only contained the gobject/ part of your changes.
> i'm fine with those but i'd also like to see the final dataset.*
> changeset ;)

Let's try that again ...
					Owen

Index: glib/Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/glib/Makefile.am,v
retrieving revision 1.131
diff -u -p -u -r1.131 Makefile.am
--- glib/Makefile.am	14 Mar 2005 04:26:57 -0000	1.131
+++ glib/Makefile.am	30 Apr 2005 12:36:51 -0000
@@ -70,6 +70,7 @@ libglib_2_0_la_SOURCES = 	\
 	gcompletion.c		\
 	gconvert.c		\
 	gdataset.c		\
+	gdatasetprivate.h	\
 	gdate.c         	\
 	gdir.c			\
 	gerror.c		\
Index: glib/gdataset.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gdataset.c,v
retrieving revision 1.22
diff -u -p -u -r1.22 gdataset.c
--- glib/gdataset.c	14 Mar 2005 05:30:08 -0000	1.22
+++ glib/gdataset.c	30 Apr 2005 12:36:51 -0000
@@ -37,6 +37,7 @@
 
 #include "glib.h"
 #include "galias.h"
+#include "gdatasetprivate.h"
 
 
 /* --- defines --- */
@@ -91,7 +92,6 @@ static GHashTable   *g_quark_ht = NULL;
 static gchar       **g_quarks = NULL;
 static GQuark        g_quark_seq_id = 0;
 
-
 /* --- functions --- */
 
 /* HOLDS: g_dataset_global_lock */
@@ -102,8 +102,8 @@ g_datalist_clear_i (GData **datalist)
   
   /* unlink *all* items before walking their destructors
    */
-  list = *datalist;
-  *datalist = NULL;
+  list = G_DATALIST_GET_POINTER (datalist);
+  G_DATALIST_SET_POINTER (datalist, NULL);
   
   while (list)
     {
@@ -139,7 +139,7 @@ g_datalist_clear (GData **datalist)
   if (!g_dataset_location_ht)
     g_data_initialize ();
 
-  while (*datalist)
+  while (G_DATALIST_GET_POINTER (datalist))
     g_datalist_clear_i (datalist);
   G_UNLOCK (g_dataset_global);
 }
@@ -210,7 +210,7 @@ g_data_set_internal (GData	  **datalist,
 {
   register GData *list;
   
-  list = *datalist;
+  list = G_DATALIST_GET_POINTER (datalist);
   if (!data)
     {
       register GData *prev;
@@ -226,12 +226,12 @@ g_data_set_internal (GData	  **datalist,
 		prev->next = list->next;
 	      else
 		{
-		  *datalist = list->next;
+		  G_DATALIST_SET_POINTER (datalist, list->next);
 		  
 		  /* the dataset destruction *must* be done
 		   * prior to invokation of the data destroy function
 		   */
-		  if (!*datalist && dataset)
+		  if (!list->next && dataset)
 		    g_dataset_destroy_internal (dataset);
 		}
 	      
@@ -309,11 +309,11 @@ g_data_set_internal (GData	  **datalist,
 	}
       else
 	list = g_chunk_new (GData, g_data_mem_chunk);
-      list->next = *datalist;
+      list->next = G_DATALIST_GET_POINTER (datalist);
       list->id = key_id;
       list->data = data;
       list->destroy_func = destroy_func;
-      *datalist = list;
+      G_DATALIST_SET_POINTER (datalist, list);
     }
 
   return NULL;
@@ -459,7 +459,7 @@ g_datalist_id_get_data (GData	 **datalis
     {
       register GData *list;
       
-      for (list = *datalist; list; list = list->next)
+      for (list = G_DATALIST_GET_POINTER (datalist); list; list = list->next)
 	if (list->id == key_id)
 	  return list->data;
     }
@@ -509,7 +509,7 @@ g_datalist_foreach (GData	   **datalist,
   g_return_if_fail (datalist != NULL);
   g_return_if_fail (func != NULL);
   
-  for (list = *datalist; list; list = next)
+  for (list = G_DATALIST_GET_POINTER (datalist); list; list = next)
     {
       next = list->next;
       func (list->id, list->data, user_data);
@@ -522,6 +522,74 @@ g_datalist_init (GData **datalist)
   g_return_if_fail (datalist != NULL);
   
   *datalist = NULL;
+}
+
+/**
+ * g_datalist_set_flags:
+ * @datalist: pointer to the location that holds a list
+ * @flags: the flags to turn on. The values of the flags are
+ *   restricted by %G_DATALIST_FLAGS_MASK (currently
+ *   3; giving two possible boolean flags).
+ *   A value for @flags that doesn't fit within the mask is
+ *   an error.
+ * 
+ * Turns on flag values for a data list. This function is used
+ * to keep a small number of boolean flags in an object with
+ * a data list without using any additional space. It is
+ * not generally useful except in circumstances where space
+ * is very tight. (It is used in the base #GObject type, for
+ * example.)
+ * 
+ * Return value: the flags of the datalist
+ **/
+void
+g_datalist_set_flags (GData **datalist,
+		      guint   flags)
+{
+  g_return_if_fail (datalist != NULL);
+  g_return_if_fail ((flags & ~G_DATALIST_FLAGS_MASK) == 0);
+
+  G_DATALIST_SET_FLAGS (datalist, flags);
+}
+
+/**
+ * g_datalist_unset_flags:
+ * @datalist: pointer to the location that holds a list
+ * @flags: the flags to turn off. The values of the flags are
+ *   restricted by %G_DATALIST_FLAGS_MASK (currently
+ *   3: giving two possible boolean flags).
+ *   A value for @flags that doesn't fit within the mask is
+ *   an error.
+ * 
+ * Turns off flag values for a data list. See g_datalist_unset_flags()
+ * 
+ * Return value: the flags of the datalist
+ **/
+void
+g_datalist_unset_flags (GData **datalist,
+			guint   flags)
+{
+  g_return_if_fail (datalist != NULL);
+  g_return_if_fail ((flags & ~G_DATALIST_FLAGS_MASK) == 0);
+
+  G_DATALIST_UNSET_FLAGS (datalist, flags);
+}
+
+/**
+ * g_datalist_get_flags:
+ * @datalist: pointer to the location that holds a list
+ * 
+ * Gets flags values packed in together with the datalist.
+ * See g_datalist_set_flags().
+ * 
+ * Return value: the flags of the datalist
+ **/
+guint
+g_datalist_get_flags (GData **datalist)
+{
+  g_return_val_if_fail (datalist != NULL, 0);
+
+  return G_DATALIST_GET_FLAGS (datalist);
 }
 
 /* HOLDS: g_dataset_global_lock */
Index: glib/gdataset.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gdataset.h,v
retrieving revision 1.2
diff -u -p -u -r1.2 gdataset.h
--- glib/gdataset.h	26 Jun 2001 16:01:14 -0000	1.2
+++ glib/gdataset.h	30 Apr 2005 12:36:51 -0000
@@ -39,19 +39,27 @@ typedef void            (*GDataForeachFu
 
 /* Keyed Data List
  */
-void      g_datalist_init                (GData          **datalist);
-void      g_datalist_clear               (GData          **datalist);
-gpointer  g_datalist_id_get_data         (GData          **datalist,
-                                          GQuark           key_id);
-void      g_datalist_id_set_data_full    (GData          **datalist,
-                                          GQuark           key_id,
-                                          gpointer         data,
-                                          GDestroyNotify   destroy_func);
-gpointer  g_datalist_id_remove_no_notify (GData          **datalist,
-                                          GQuark           key_id);
-void      g_datalist_foreach             (GData          **datalist,
-                                          GDataForeachFunc func,
-                                          gpointer         user_data);
+#define G_DATALIST_FLAGS_MASK 0x3
+
+void     g_datalist_init                (GData            **datalist);
+void     g_datalist_clear               (GData            **datalist);
+gpointer g_datalist_id_get_data         (GData            **datalist,
+					 GQuark             key_id);
+void     g_datalist_id_set_data_full    (GData            **datalist,
+					 GQuark             key_id,
+					 gpointer           data,
+					 GDestroyNotify     destroy_func);
+gpointer g_datalist_id_remove_no_notify (GData            **datalist,
+					 GQuark             key_id);
+void     g_datalist_foreach             (GData            **datalist,
+					 GDataForeachFunc   func,
+					 gpointer           user_data);
+void     g_datalist_set_flags           (GData            **datalist,
+					 guint              flags);
+void     g_datalist_unset_flags         (GData            **datalist,
+					 guint              flags);
+guint    g_datalist_get_flags           (GData            **datalist);
+
 #define   g_datalist_id_set_data(dl, q, d)      \
      g_datalist_id_set_data_full ((dl), (q), (d), NULL)
 #define   g_datalist_id_remove_data(dl, q)      \
Index: glib/gdatasetprivate.h
===================================================================
RCS file: glib/gdatasetprivate.h
diff -N glib/gdatasetprivate.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ glib/gdatasetprivate.h	30 Apr 2005 12:36:51 -0000
@@ -0,0 +1,57 @@
+/* GLIB - Library of useful routines for C programming
+ * gdataset-private.h: Internal macros for accessing dataset values
+ * Copyright (C) 2005  Red Hat
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/*
+ * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
+ * file for a list of people on the GLib Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GLib at ftp://ftp.gtk.org/pub/gtk/. 
+ */
+
+#ifndef __G_DATASETPRIVATE_H__
+#define __G_DATASETPRIVATE_H__
+
+#include <glib/gdataset.h>
+
+G_BEGIN_DECLS
+
+#define G_DATALIST_GET_FLAGS(datalist)							\
+  ((gsize)*(datalist) & G_DATALIST_FLAGS_MASK)
+#define G_DATALIST_SET_FLAGS(datalist, flags) G_STMT_START {				\
+  *datalist = (GData *)(G_DATALIST_GET_FLAGS(datalist) |				\
+			(flags) |							\
+			((gsize)*(datalist) & ~(gsize)G_DATALIST_FLAGS_MASK));		\
+} G_STMT_END
+#define G_DATALIST_UNSET_FLAGS(datalist, flags) G_STMT_START {				\
+  *datalist = (GData *)((G_DATALIST_GET_FLAGS(datalist) & ~(flags)) |			\
+			(flags) |							\
+			((gsize)*(datalist) & ~(gsize)G_DATALIST_FLAGS_MASK));	        \
+} G_STMT_END
+
+#define G_DATALIST_GET_POINTER(datalist)						\
+  ((GData *)((gsize)*(datalist) & ~(gsize)G_DATALIST_FLAGS_MASK))
+#define G_DATALIST_SET_POINTER(datalist,pointer) G_STMT_START {				\
+  *(datalist) = (GData *)(G_DATALIST_GET_FLAGS (datalist) |				\
+			  (gsize)pointer);						\
+} G_STMT_END
+
+G_END_DECLS
+
+#endif /* __G_DATASETPRIVATE_H__ */
Index: gobject/gobject.c
===================================================================
RCS file: /cvs/gnome/glib/gobject/gobject.c,v
retrieving revision 1.66
diff -u -p -u -r1.66 gobject.c
--- gobject/gobject.c	14 Mar 2005 06:47:51 -0000	1.66
+++ gobject/gobject.c	30 Apr 2005 12:36:51 -0000
@@ -18,6 +18,7 @@
  */
 #include	"gobject.h"
 #include	"gobjectalias.h"
+#include        <glib/gdatasetprivate.h>
 
 /*
  * MT safe
@@ -39,6 +40,10 @@
 #define PARAM_SPEC_PARAM_ID(pspec)		((pspec)->param_id)
 #define	PARAM_SPEC_SET_PARAM_ID(pspec, id)	((pspec)->param_id = (id))
 
+#define OBJECT_HAS_TOGGLE_REF_FLAG 0x1
+#define OBJECT_HAS_TOGGLE_REF(object) \
+    ((G_DATALIST_GET_FLAGS(&(object)->qdata) & OBJECT_HAS_TOGGLE_REF_FLAG) != 0)
+
 
 /* --- signals --- */
 enum {
@@ -105,6 +110,7 @@ static void object_interface_check_prope
 /* --- variables --- */
 static GQuark	            quark_closure_array = 0;
 static GQuark	            quark_weak_refs = 0;
+static GQuark	            quark_toggle_refs = 0;
 static GParamSpecPool      *pspec_pool = NULL;
 static GObjectNotifyContext property_notify_context = { 0, };
 static gulong	            gobject_signals[LAST_SIGNAL] = { 0, };
@@ -241,6 +247,7 @@ g_object_do_class_init (GObjectClass *cl
   quark_closure_array = g_quark_from_static_string ("GObject-closure-array");
 
   quark_weak_refs = g_quark_from_static_string ("GObject-weak-references");
+  quark_toggle_refs = g_quark_from_static_string ("GObject-toggle-references");
   pspec_pool = g_param_spec_pool_new (TRUE);
   property_notify_context.quark_notify_queue = g_quark_from_static_string ("GObject-notify-queue");
   property_notify_context.dispatcher = g_object_notify_dispatcher;
@@ -1519,10 +1526,8 @@ g_object_weak_unref (GObject    *object,
 	    found_one = TRUE;
 	    wstack->n_weak_refs -= 1;
 	    if (i != wstack->n_weak_refs)
-	      {
-		wstack->weak_refs[i].notify = wstack->weak_refs[wstack->n_weak_refs].notify;
-		wstack->weak_refs[i].data = wstack->weak_refs[wstack->n_weak_refs].data;
-	      }
+	      wstack->weak_refs[i] = wstack->weak_refs[wstack->n_weak_refs];
+
 	    break;
 	  }
     }
@@ -1554,6 +1559,154 @@ g_object_remove_weak_pointer (GObject  *
                        weak_pointer_location);
 }
 
+typedef struct {
+  GObject *object;
+  guint n_toggle_refs;
+  struct {
+    GToggleNotify notify;
+    gpointer    data;
+  } toggle_refs[1];  /* flexible array */
+} ToggleRefStack;
+
+static void
+toggle_refs_notify (GObject *object,
+		    gboolean is_last_ref)
+{
+  ToggleRefStack *tstack = g_datalist_id_get_data (&object->qdata, quark_toggle_refs);
+
+  /* Reentrancy here is not as tricky as it seems, because a toggle reference
+   * will only be notified when there is exactly one of them.
+   */
+  g_assert (tstack->n_toggle_refs == 1);
+  tstack->toggle_refs[0].notify (tstack->toggle_refs[0].data, tstack->object, is_last_ref);
+}
+
+/**
+ * g_object_add_toggle_ref:
+ * @object: a #GObject
+ * @notify: a function to call when this reference is the
+ *          last reference to the object, or is no longer
+ *          the last reference.
+ * @data:   data to pass to @notify
+ *
+ * Increases the reference count of the object by one and sets a
+ * callback to be called when all other references to the object are
+ * dropped, or when this is already the last reference to the object
+ * and another reference is established.
+ *
+ * This functionality is intended for binding @object to a proxy
+ * object managed by another memory manager. This is done with two
+ * paired references: the strong reference added by
+ * g_object_add_toggle_ref() and a reverse reference to the proxy
+ * object which is either a strong reference or weak reference.
+ * 
+ * The setup is that when there are no other references to @object,
+ * only a weak reference is held in the reverse direction from @object
+ * to the proxy object, but when there are other references held to
+ * @object, a strong reference is held. The @notify callback is called
+ * when the reference from @object to the proxy object should be
+ * <firstterm>toggled</firstterm> from strong to weak (@is_last_ref
+ * true) or weak to strong (@is_last_ref false).
+ *
+ * Since a (normal) reference must be held to the object before
+ * calling g_object_toggle_ref(), the initial state of the reverse
+ * link is always strong.
+ *
+ * Multiple toggle references may be added to the same gobject,
+ * however if there are multiple toggle references to an object, none
+ * of them will ever be notified until all but one are removed.  For
+ * this reason, you should only ever use a toggle reference if there
+ * is important state in the proxy object.
+ */
+void
+g_object_add_toggle_ref (GObject       *object,
+			 GToggleNotify  notify,
+			 gpointer       data)
+{
+  ToggleRefStack *tstack;
+  guint i;
+  
+  g_return_if_fail (G_IS_OBJECT (object));
+  g_return_if_fail (notify != NULL);
+  g_return_if_fail (object->ref_count >= 1);
+
+  g_object_ref (object);
+
+  tstack = g_datalist_id_remove_no_notify (&object->qdata, quark_toggle_refs);
+  if (tstack)
+    {
+      i = tstack->n_toggle_refs++;
+      /* allocate i = tstate->n_toggle_refs - 1 positions beyond the 1 declared
+       * in tstate->toggle_refs */
+      tstack = g_realloc (tstack, sizeof (*tstack) + sizeof (tstack->toggle_refs[0]) * i);
+    }
+  else
+    {
+      tstack = g_renew (ToggleRefStack, NULL, 1);
+      tstack->object = object;
+      tstack->n_toggle_refs = 1;
+      i = 0;
+    }
+
+  /* Set a flag for fast lookup after adding the first toggle reference */
+  if (tstack->n_toggle_refs == 1)
+    G_DATALIST_SET_FLAGS (&object->qdata, OBJECT_HAS_TOGGLE_REF_FLAG);
+  
+  tstack->toggle_refs[i].notify = notify;
+  tstack->toggle_refs[i].data = data;
+  g_datalist_id_set_data_full (&object->qdata, quark_toggle_refs, tstack,
+			       (GDestroyNotify)g_free);
+}
+ 
+/**
+ * g_object_remove_toggle_ref:
+ * @object: a #GObject
+ * @notify: a function to call when this reference is the
+ *          last reference to the object, or is no longer
+ *          the last reference.
+ * @data:   data to pass to @notify
+ *
+ * Removes a reference added with g_object_add_toggle_ref(). The
+ * reference count of the object is decreased by one.
+ */
+void
+g_object_remove_toggle_ref (GObject       *object,
+			    GToggleNotify  notify,
+			    gpointer       data)
+{
+  ToggleRefStack *tstack;
+  gboolean found_one = FALSE;
+
+  g_return_if_fail (G_IS_OBJECT (object));
+  g_return_if_fail (notify != NULL);
+
+  tstack = g_datalist_id_get_data (&object->qdata, quark_toggle_refs);
+  if (tstack)
+    {
+      guint i;
+
+      for (i = 0; i < tstack->n_toggle_refs; i++)
+	if (tstack->toggle_refs[i].notify == notify &&
+	    tstack->toggle_refs[i].data == data)
+	  {
+	    found_one = TRUE;
+	    tstack->n_toggle_refs -= 1;
+	    if (i != tstack->n_toggle_refs)
+	      tstack->toggle_refs[i] = tstack->toggle_refs[tstack->n_toggle_refs];
+
+	    if (tstack->n_toggle_refs == 0)
+	      G_DATALIST_UNSET_FLAGS (&object->qdata, OBJECT_HAS_TOGGLE_REF_FLAG);
+
+	    g_object_unref (object);
+	    
+	    break;
+	  }
+    }
+  
+  if (!found_one)
+    g_warning ("%s: couldn't find toggle ref %p(%p)", G_STRFUNC, notify, data);
+}
+
 gpointer
 g_object_ref (gpointer _object)
 {
@@ -1568,6 +1721,8 @@ g_object_ref (gpointer _object)
 #endif  /* G_ENABLE_DEBUG */
 
   object->ref_count += 1;
+  if (object->ref_count == 2 && OBJECT_HAS_TOGGLE_REF (object))
+    toggle_refs_notify (object, FALSE);
   
   return object;
 }
@@ -1586,7 +1741,11 @@ g_object_unref (gpointer _object)
 #endif  /* G_ENABLE_DEBUG */
 
   if (object->ref_count > 1)
-    object->ref_count -= 1;
+    {
+      object->ref_count -= 1;
+      if (object->ref_count == 1 && OBJECT_HAS_TOGGLE_REF (object))
+	toggle_refs_notify (object, TRUE);
+    }
   else
     g_object_last_unref (object);
 }
Index: gobject/gobject.h
===================================================================
RCS file: /cvs/gnome/glib/gobject/gobject.h,v
retrieving revision 1.30
diff -u -p -u -r1.30 gobject.h
--- gobject/gobject.h	8 Mar 2005 05:41:42 -0000	1.30
+++ gobject/gobject.h	30 Apr 2005 12:36:51 -0000
@@ -178,6 +178,31 @@ void        g_object_add_weak_pointer   
                                                gpointer       *weak_pointer_location);
 void        g_object_remove_weak_pointer      (GObject        *object, 
                                                gpointer       *weak_pointer_location);
+
+/**
+ * GToggleNotify:
+ * @data: Callback data passed to g_object_add_toggle_ref()
+ * @object: The object on which g_object_add_toggle_ref() was
+ *          called.
+ * @is_last_ref: %TRUE if the toggle reference is now the
+ *    last reference to the object. %FALSE if the toggle
+ *    reference was the last reference and there are now other
+ *    references.
+ *
+ * A callback function used for notification when the state
+ * of a toggle reference changes. See g_object_add_toggle_ref().
+ */
+typedef void (*GToggleNotify) (gpointer      data,
+			       GObject      *object,
+			       gboolean      is_last_ref);
+
+void g_object_add_toggle_ref    (GObject       *object,
+				 GToggleNotify  notify,
+				 gpointer       data);
+void g_object_remove_toggle_ref (GObject       *object,
+				 GToggleNotify  notify,
+				 gpointer       data);
+
 gpointer    g_object_get_qdata                (GObject        *object,
 					       GQuark          quark);
 void        g_object_set_qdata                (GObject        *object,
Index: tests/gobject/Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/tests/gobject/Makefile.am,v
retrieving revision 1.7
diff -u -p -u -r1.7 Makefile.am
--- tests/gobject/Makefile.am	24 Oct 2003 03:41:22 -0000	1.7
+++ tests/gobject/Makefile.am	30 Apr 2005 12:36:51 -0000
@@ -52,7 +52,8 @@ test_programs =					\
 	ifaceinit				\
 	ifaceinherit				\
 	ifaceproperties				\
-	override
+	override				\
+	references
 
 check_PROGRAMS = $(test_programs)
 
Index: tests/gobject/references.c
===================================================================
RCS file: tests/gobject/references.c
diff -N tests/gobject/references.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ tests/gobject/references.c	30 Apr 2005 12:36:51 -0000
@@ -0,0 +1,281 @@
+/* GObject - GLib Type, Object, Parameter and Signal Library
+ * Copyright (C) 2005 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#undef	G_LOG_DOMAIN
+#define	G_LOG_DOMAIN "TestReferences"
+
+#undef G_DISABLE_ASSERT
+#undef G_DISABLE_CHECKS
+#undef G_DISABLE_CAST_CHECKS
+
+#include	<glib-object.h>
+
+/* This test tests weak and toggle references
+ */
+
+static GObject *global_object;
+
+static gboolean object_destroyed;
+static gboolean weak_ref1_notified;
+static gboolean weak_ref2_notified;
+static gboolean toggle_ref1_weakened;
+static gboolean toggle_ref1_strengthened;
+static gboolean toggle_ref2_weakened;
+static gboolean toggle_ref2_strengthened;
+static gboolean toggle_ref3_weakened;
+static gboolean toggle_ref3_strengthened;
+
+/*
+ * TestObject, a parent class for TestObject
+ */
+#define TEST_TYPE_OBJECT          (test_object_get_type ())
+typedef struct _TestObject        TestObject;
+typedef struct _TestObjectClass   TestObjectClass;
+
+struct _TestObject
+{
+  GObject parent_instance;
+};
+struct _TestObjectClass
+{
+  GObjectClass parent_class;
+};
+
+G_DEFINE_TYPE (TestObject, test_object, G_TYPE_OBJECT);
+
+static void
+test_object_finalize (GObject *object)
+{
+  object_destroyed = TRUE;
+  
+  G_OBJECT_CLASS (test_object_parent_class)->finalize (object);
+}
+
+static void
+test_object_class_init (TestObjectClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = test_object_finalize;
+}
+
+static void
+test_object_init (TestObject *test_object)
+{
+}
+
+static void
+clear_flags (void)
+{
+  object_destroyed = FALSE;
+  weak_ref1_notified = FALSE;
+  weak_ref2_notified = FALSE;
+  toggle_ref1_weakened = FALSE;
+  toggle_ref1_strengthened = FALSE;
+  toggle_ref2_weakened = FALSE;
+  toggle_ref2_strengthened = FALSE;
+  toggle_ref3_weakened = FALSE;
+  toggle_ref3_strengthened = FALSE;
+}
+
+static void
+weak_ref1 (gpointer data,
+	   GObject *object)
+{
+  g_assert (object == global_object);
+  g_assert (data == GUINT_TO_POINTER (42));
+
+  weak_ref1_notified = TRUE;
+}
+
+static void
+weak_ref2 (gpointer data,
+	   GObject *object)
+{
+  g_assert (object == global_object);
+  g_assert (data == GUINT_TO_POINTER (24));
+
+  weak_ref2_notified = TRUE;
+}
+
+static void
+toggle_ref1 (gpointer data,
+	     GObject *object,
+	     gboolean is_last_ref)
+{
+  g_assert (object == global_object);
+  g_assert (data == GUINT_TO_POINTER (42));
+
+  if (is_last_ref)
+    toggle_ref1_weakened = TRUE;
+  else
+    toggle_ref1_strengthened = TRUE;
+}
+
+static void
+toggle_ref2 (gpointer data,
+	     GObject *object,
+	     gboolean is_last_ref)
+{
+  g_assert (object == global_object);
+  g_assert (data == GUINT_TO_POINTER (24));
+
+  if (is_last_ref)
+    toggle_ref2_weakened = TRUE;
+  else
+    toggle_ref2_strengthened = TRUE;
+}
+
+static void
+toggle_ref3 (gpointer data,
+	     GObject *object,
+	     gboolean is_last_ref)
+{
+  g_assert (object == global_object);
+  g_assert (data == GUINT_TO_POINTER (34));
+
+  if (is_last_ref)
+    {
+      toggle_ref3_weakened = TRUE;
+      g_object_remove_toggle_ref (object, toggle_ref3, GUINT_TO_POINTER (34));
+    }
+  else
+    toggle_ref3_strengthened = TRUE;
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  GObject *object;
+	
+  g_log_set_always_fatal (g_log_set_always_fatal (G_LOG_FATAL_MASK) |
+			  G_LOG_LEVEL_WARNING |
+			  G_LOG_LEVEL_CRITICAL);
+  g_type_init ();
+
+  /* Test basic weak reference operation
+   */
+  global_object = object = g_object_new (TEST_TYPE_OBJECT, NULL);
+  
+  g_object_weak_ref (object, weak_ref1, GUINT_TO_POINTER (42));
+
+  clear_flags ();
+  g_object_unref (object);
+  g_assert (weak_ref1_notified == TRUE);
+  g_assert (object_destroyed == TRUE);
+
+  /* Test two weak references at once
+   */
+  global_object = object = g_object_new (TEST_TYPE_OBJECT, NULL);
+  
+  g_object_weak_ref (object, weak_ref1, GUINT_TO_POINTER (42));
+  g_object_weak_ref (object, weak_ref2, GUINT_TO_POINTER (24));
+
+  clear_flags ();
+  g_object_unref (object);
+  g_assert (weak_ref1_notified == TRUE);
+  g_assert (weak_ref2_notified == TRUE);
+  g_assert (object_destroyed == TRUE);
+
+  /* Test remove weak references
+   */
+  global_object = object = g_object_new (TEST_TYPE_OBJECT, NULL);
+  
+  g_object_weak_ref (object, weak_ref1, GUINT_TO_POINTER (42));
+  g_object_weak_ref (object, weak_ref2, GUINT_TO_POINTER (24));
+  g_object_weak_unref (object, weak_ref1, GUINT_TO_POINTER (42));
+
+  clear_flags ();
+  g_object_unref (object);
+  g_assert (weak_ref1_notified == FALSE);
+  g_assert (weak_ref2_notified == TRUE);
+  g_assert (object_destroyed == TRUE);
+
+  /* Test basic toggle reference operation
+   */
+  global_object = object = g_object_new (TEST_TYPE_OBJECT, NULL);
+  
+  g_object_add_toggle_ref (object, toggle_ref1, GUINT_TO_POINTER (42));
+
+  clear_flags ();
+  g_object_unref (object);
+  g_assert (toggle_ref1_weakened == TRUE);
+  g_assert (toggle_ref1_strengthened == FALSE);
+  g_assert (object_destroyed == FALSE);
+
+  clear_flags ();
+  g_object_ref (object);
+  g_assert (toggle_ref1_weakened == FALSE);
+  g_assert (toggle_ref1_strengthened == TRUE);
+  g_assert (object_destroyed == FALSE);
+
+  g_object_unref (object);
+
+  clear_flags ();
+  g_object_remove_toggle_ref (object, toggle_ref1, GUINT_TO_POINTER (42));
+  g_assert (toggle_ref1_weakened == FALSE);
+  g_assert (toggle_ref1_strengthened == FALSE);
+  g_assert (object_destroyed == TRUE);
+
+  global_object = object = g_object_new (TEST_TYPE_OBJECT, NULL);
+
+  /* Test two toggle references at once
+   */
+  g_object_add_toggle_ref (object, toggle_ref1, GUINT_TO_POINTER (42));
+  g_object_add_toggle_ref (object, toggle_ref2, GUINT_TO_POINTER (24));
+
+  clear_flags ();
+  g_object_unref (object);
+  g_assert (toggle_ref1_weakened == FALSE);
+  g_assert (toggle_ref1_strengthened == FALSE);
+  g_assert (toggle_ref2_weakened == FALSE);
+  g_assert (toggle_ref2_strengthened == FALSE);
+  g_assert (object_destroyed == FALSE);
+
+  clear_flags ();
+  g_object_remove_toggle_ref (object, toggle_ref1, GUINT_TO_POINTER (42));
+  g_assert (toggle_ref1_weakened == FALSE);
+  g_assert (toggle_ref1_strengthened == FALSE);
+  g_assert (toggle_ref2_weakened == TRUE);
+  g_assert (toggle_ref2_strengthened == FALSE);
+  g_assert (object_destroyed == FALSE);
+
+  clear_flags ();
+  g_object_remove_toggle_ref (object, toggle_ref2, GUINT_TO_POINTER (24));
+  g_assert (toggle_ref1_weakened == FALSE);
+  g_assert (toggle_ref1_strengthened == FALSE);
+  g_assert (toggle_ref2_weakened == FALSE);
+  g_assert (toggle_ref2_strengthened == FALSE);
+  g_assert (object_destroyed == TRUE);
+  
+  /* Test a toggle reference that removes itself
+   */
+  global_object = object = g_object_new (TEST_TYPE_OBJECT, NULL);
+  
+  g_object_add_toggle_ref (object, toggle_ref3, GUINT_TO_POINTER (34));
+
+  clear_flags ();
+  g_object_unref (object);
+  g_assert (toggle_ref3_weakened == TRUE);
+  g_assert (toggle_ref3_strengthened == FALSE);
+  g_assert (object_destroyed == TRUE);
+
+  return 0;
+}

Attachment: signature.asc
Description: This is a digitally signed message part



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