Make a GTree persistent.
authorDana Jansens <danakj@orodu.net>
Fri, 13 Nov 2009 16:20:47 +0000 (11:20 -0500)
committerDana Jansens <danakj@orodu.net>
Wed, 25 Nov 2009 17:01:11 +0000 (12:01 -0500)
* Change the GTreeNode structure so that it can keep multiple versions of its
navigation pointers.

* Remove left_child and right_child from GTreeNode in favor of parent. For
persistence to work, a node needs to know where its incoming pointers are.
If node->right/left can point to the next/previous node in the tree, then we
might have an incoming pointer from our previous node, but don't have a
pointer to it.  Finding the next/previous node takes up to log(n) time, which
isn't cool to do every time we change a node.  So, if we want next/previous
pointers, we need to always have the pointer, so that it can be recipricated,
and thus would require two extra pointer spots beyond left/right to store that.

Because a node points to its children, they also need to be able to find their
incoming pointer in their parent.  We add a pointer for this, which allows us
to still navigate the tree, and thus we don't need to add next/previous
pointers also.

* Reflect that g_tree_nnodes() now returns the number of nodes for the current
version of the tree.

* Add g_tree_current_version() and g_tree_next_version() functions which
report the current version, and allow you to move the tree into the next
version.  Inserts/deletes after that will occur in the new version only.

* Add g_tree_root_next_version() and g_tree_node_next_version() functions that
move a node or a root pointer into the tree up to the current version of the
tree so that it can be modified (for insert/delete).

Make sure nodes have pointers for the current version when changing them, and
that we always modify the current root pointer into the tree.

When moving a node to the current version of the tree, it's pointer table
may become full, and a new node needs to be created.  Then any nodes with
pointers to the old one need to be updated.

* Add functions g_tree_root_find_version() and g_tree_node_find_version()
that take a version number, and find the pointers which match for
that version (the largest ones <= the version).

* Fix g_tree_remove() and g_tree_steal() for a persistent tree. Free a node's
data and return TRUE for these functions only if it's the last
version of the node in the persistent tree.  Make the functions return FALSE
if the node is found but cannot be removed because it is in an older version
of the tree.

This should help people from freeing things accidentally based on the return
result.

* Make g_tree_node_search() and g_tree_node_find() search for a key within a
given version of the GTree, rather than always in the latest version.

* Make g_tree_destroy_all() delete all versions of everything in the tree.

* Make the new g_tree_lookup_related() function take an argument "version".
This function now searches in the requested version of the tree, rather than
only in the current/latest version.

* Update the debug testing code to check all versions of a persistent tree.

* Add units tests for consistency in all versions of a persistent/versioned
GTree using g_tree_lookup_related().

glib/gtree.c
glib/gtree.h
tests/tree-test.c

index 8870a8f5165ef8b7e66281081e208fc717eeac5c..34946f80813bf68a429e8e8e99ffa5fa59fb97aa 100644 (file)
 
 #undef G_TREE_DEBUG
 
-typedef struct _GTreeNode  GTreeNode;
+#define MAX_IN_DEGREE 3
+#define TABLE_SIZE (MAX_IN_DEGREE+1) /* This is the minimum size required so
+                                        that you only have a constant number of
+                                        nodes fill up their tables and
+                                        require a new node to be created at
+                                        a time. */
+
+typedef struct _GTreeRootVersion   GTreeRootVersion;
+typedef struct _GTreeNodeVersion   GTreeNodeVersion;
+typedef struct _GTreeNodeData      GTreeNodeData;
+typedef struct _GTreeNode          GTreeNode;
+
+struct _GTreeRootVersion
+{
+  GTreeNode *root;
+  guint      version;
+};
 
 struct _GTree
 {
-  GTreeNode        *root;
+  GTreeRootVersion *r; /* versioned root nodes of the tree.  r[0] is
+                          the highest (latest) version.  then r[1]..r[nr-1] are
+                          older versions in ascending order.  Use first_v(),
+                          next_v() and prev_v() to traverse the list. */
+  guint             nr;
   GCompareDataFunc  key_compare;
   GDestroyNotify    key_destroy_func;
   GDestroyNotify    value_destroy_func;
   gpointer          key_compare_data;
   guint             nnodes;
   gint              ref_count;
+  guint             version;
 };
 
-struct _GTreeNode
+struct _GTreeNodeVersion
 {
-  gpointer   key;         /* key for this node */
-  gpointer   value;       /* value stored at this node */
+  guint      version;
   GTreeNode *left;        /* left subtree */
   GTreeNode *right;       /* right subtree */
   GTreeNode *parent;      /* parent node */
-  guint8     left_child;  /* the node has a left child (else left points to the
-                             previous node in the tree) */
-  guint8     right_child; /* the node has a right child (else right points to
-                             the next node in the tree) */
 };
 
+struct _GTreeNodeData
+{
+  gpointer   key;         /* key for this node */
+  gpointer   value;       /* value stored at this node */
+  gint       ref_count;
+  guint8     stolen;      /* true if the node is stolen instead of removed */
+};
+
+struct _GTreeNode
+{
+  GTreeNodeData    *data; /* the node's permanent data */
+  GTreeNodeVersion *v;    /* versions of pointers for the node.  v[0] is the
+                             highest (latest) version.  then v[1]..v[nv-1] are
+                             older versions in ascending order.  Use first_v(),
+                             next_v() and prev_v() to traverse the list. */
+  guint             nv;   /* number of versions stored in this node */
+};
 
-static GTreeNode* g_tree_node_new                   (gpointer         key,
+static GTreeNode* g_tree_node_new                   (GTree           *tree,
+                                                     gpointer         key,
                                                     gpointer         value);
 static guint      g_tree_priority                   (GTreeNode       *node);
 static void       g_tree_insert_internal            (GTree           *tree,
@@ -74,7 +108,8 @@ static gboolean   g_tree_remove_internal            (GTree           *tree,
                                                     gboolean         steal);
 static GTreeNode *g_tree_find_node                  (GTree           *tree,
                                                     gconstpointer    key,
-                                                     GTreeSearchType  search_type);
+                                                     GTreeSearchType  search_type,
+                                                     guint            version);
 static gint       g_tree_node_pre_order             (GTreeNode       *node,
                                                     GTraverseFunc    traverse_func,
                                                     gpointer         data);
@@ -87,31 +122,89 @@ static gint       g_tree_node_post_order            (GTreeNode       *node,
 static gpointer   g_tree_node_search                (GTreeNode       *node,
                                                     GCompareFunc     search_func,
                                                     gconstpointer    data,
-                                                     GTreeSearchType  search_type);
+                                                     GTreeSearchType  search_type,
+                                                     guint            version);
 static gint       g_tree_node_height                (GTreeNode       *node);
-static GTreeNode* g_tree_node_rotate_left           (GTreeNode       *node);
-static GTreeNode* g_tree_node_rotate_right          (GTreeNode       *node);
+static GTreeNode* g_tree_node_rotate_left           (GTree           *tree,
+                                                     GTreeNode       *node);
+static GTreeNode* g_tree_node_rotate_right          (GTree           *tree,
+                                                     GTreeNode       *node);
 #ifdef G_TREE_DEBUG
-static void       g_tree_node_check                 (GTreeNode       *node);
+static void       g_tree_node_check                 (GTreeNode       *node,
+                                                     guint            version);
 #endif
 
 static GTreeNode*
-g_tree_node_new (gpointer key,
+g_tree_node_new (GTree   *tree,
+                 gpointer key,
                 gpointer value)
 {
   GTreeNode *node = g_slice_new (GTreeNode);
 
-  node->left = NULL;
-  node->right = NULL;
-  node->parent = NULL;
-  node->left_child = FALSE;
-  node->right_child = FALSE;
-  node->key = key;
-  node->value = value;
+  node->data = g_slice_new(GTreeNodeData);
+  node->data->key = key;
+  node->data->value = value;
+  node->data->ref_count = 1;
+  node->data->stolen = FALSE;
+
+  /* for the version 0, only allocate one set of pointers for a node,
+     so that we don't use a lot more memory when persistence isn't even
+     being used */
+  node->v = g_slice_alloc(sizeof(GTreeNodeVersion) *
+                          (tree->version == 0 ? 1 : TABLE_SIZE));
+  node->nv = 1;
+
+  node->v[0].version = tree->version;
+  node->v[0].left = NULL;
+  node->v[0].right = NULL;
+  node->v[0].parent = NULL;
 
   return node;
 }
 
+static gboolean
+g_tree_node_data_unref (GTree *tree,
+                        GTreeNode *node)
+{
+  /* free the node's data if it's the last node which is using it. just
+     because the version of the node was created in the latest version
+     of the tree, doesn't mean there aren't older versions around still */
+  if (g_atomic_int_dec_and_test (&node->data->ref_count))
+    {
+      if (!node->data->stolen)
+        {
+          if (tree->key_destroy_func)
+            tree->key_destroy_func (node->data->key);
+          if (tree->value_destroy_func)
+            tree->value_destroy_func (node->data->value);
+        }
+      g_slice_free (GTreeNodeData, node->data);
+      return TRUE;
+    }
+  return FALSE;
+}
+
+static gboolean
+g_tree_node_free (GTree     *tree,
+                  GTreeNode *node)
+{
+  gboolean data_free;
+
+  if (node->data)
+    data_free = g_tree_node_data_unref (tree, node);
+  else
+    data_free = FALSE;
+
+  g_slice_free1 (sizeof(GTreeNodeVersion) *
+                 (node->v[0].version == 0 ? 1 : TABLE_SIZE),
+                 node->v);
+  node->v = NULL;
+  node->data = NULL;
+  g_slice_free (GTreeNode, node);
+
+  return data_free;
+}
+
 /**
  * g_tree_new:
  * @key_compare_func: the function used to order the nodes in the #GTree.
@@ -181,85 +274,218 @@ g_tree_new_full (GCompareDataFunc key_compare_func,
   g_return_val_if_fail (key_compare_func != NULL, NULL);
   
   tree = g_slice_new (GTree);
-  tree->root               = NULL;
+  tree->r                  = g_new(GTreeRootVersion, 1);
+  tree->nr                 = 1;
   tree->key_compare        = key_compare_func;
   tree->key_destroy_func   = key_destroy_func;
   tree->value_destroy_func = value_destroy_func;
   tree->key_compare_data   = key_compare_data;
   tree->nnodes             = 0;
   tree->ref_count          = 1;
+  tree->version            = 0;
+
+  tree->r[0].root          = NULL;
+  tree->r[0].version       = 0;
   
   return tree;
 }
 
+/* finds the largest version in the array, that is <= the query version */
+static inline GTreeRootVersion *
+g_tree_root_find_version (GTree *tree,
+                          guint  version)
+{
+  GTreeRootVersion *const v = tree->r;
+  const guint nv = tree->nr;
+  GTreeRootVersion *l, *r;
+
+  /* note: we never search for a version smaller than the smallest
+     version in the array */
+
+  if (v[0].version <= version)
+    return v;
+
+  l = v+1;
+  r = v+(nv-1);
+  while (l <= r) /* binary search */
+    {
+      GTreeRootVersion *const m = l + (r - l) / 2;
+      if (version == m->version)
+        return m;
+      else if (version < m->version)
+        r = m-1;
+      else
+        l = m+1;
+    }
+  g_assert (r->version < version);
+  /* When searching for the second last root (the last position in the array),
+     l will be off the end of the list */
+  g_assert (l == v+nv || l->version > version);
+  return r;
+}
+
+static inline GTreeNodeVersion *
+g_tree_node_find_version(GTreeNode *node,
+                         guint      version)
+{
+  GTreeNodeVersion *const v = node->v;
+  const guint nv = node->nv;
+  GTreeNodeVersion *n;
+
+  /* note: we never search for a version smaller than the smallest
+     version in the array */
+
+  if (v[0].version <= version)
+    return v;
+
+  /* there are at most TABLE_SIZE things to look through, which is small,
+     so just scan through them from largest version to smallest */
+  for (n = v+(nv-1); n != v; --n)
+    if (n->version <= version)
+      break;
+  g_assert (n != v);
+  return n;
+}
+
 static inline GTreeNode *
-g_tree_first_node (GTree *tree)
+g_tree_first_node (GTree *tree,
+                   guint  version)
 {
   GTreeNode *tmp;
+  GTreeNodeVersion *tmpv;
+  GTreeRootVersion *rv;
 
-  if (!tree->root)
+  rv = g_tree_root_find_version (tree, version);
+  if (!rv->root)
     return NULL;
 
-  tmp = tree->root;
+  tmpv = g_tree_node_find_version (tmp = rv->root, version);
 
-  while (tmp->left_child)
-    tmp = tmp->left;
+  while (tmpv->left)
+    tmpv = g_tree_node_find_version (tmp = tmpv->left, version);
 
   return tmp;
 } 
 
 static inline GTreeNode *
-g_tree_node_previous (GTreeNode *node)
+g_tree_node_previous (GTreeNode *node,
+                      guint      version)
 {
   GTreeNode *tmp;
+  GTreeNodeVersion *nodev, *tmpv;
 
-  tmp = node->left;
-
-  if (node->left_child)
-    while (tmp->right_child)
-      tmp = tmp->right;
+  nodev = g_tree_node_find_version (node, version);
 
+  if (nodev->left)
+    {
+      tmpv = g_tree_node_find_version (tmp = nodev->left, version);
+      while (tmpv->right)
+        tmpv = g_tree_node_find_version (tmp = tmpv->right, version);
+    }
+  else
+    {
+      tmp = nodev->parent;
+      while (tmp)
+        {
+          tmpv = g_tree_node_find_version (tmp, version);
+          if (tmpv->right == node)
+              break;
+          node = tmp;
+          tmp = tmpv->parent;
+        }
+    }
   return tmp;
 }
                  
 static inline GTreeNode *
-g_tree_node_next (GTreeNode *node)
+g_tree_node_next (GTreeNode *node,
+                  guint      version)
 {
   GTreeNode *tmp;
+  GTreeNodeVersion *nodev, *tmpv;
 
-  tmp = node->right;
-
-  if (node->right_child)
-    while (tmp->left_child)
-      tmp = tmp->left;
+  nodev = g_tree_node_find_version (node, version);
 
+  if (nodev->right)
+    {
+      tmpv = g_tree_node_find_version (tmp = nodev->right, version);
+      while (tmpv->left)
+        tmpv = g_tree_node_find_version (tmp = tmpv->left, version);
+    }
+  else
+    {
+      tmp = nodev->parent;
+      while (tmp)
+        {
+          tmpv = g_tree_node_find_version (tmp, version);
+          if (tmpv->left == node)
+              break;
+          node = tmp;
+          tmp = tmpv->parent;
+        }
+    }
   return tmp;
 }
 
-static void
-g_tree_remove_all (GTree *tree)
+static inline void
+g_tree_node_free_all (GTree *tree, GTreeNode *node)
 {
-  GTreeNode *node;
-  GTreeNode *next;
-
-  g_return_if_fail (tree != NULL);
+  guint i;
 
-  node = g_tree_first_node (tree);
+  if (!node)
+    return;
 
-  while (node)
+  for (i = 0; i < node->nv; ++i)
     {
-      next = g_tree_node_next (node);
+      g_tree_node_free_all (tree, node->v[i].parent);
+      g_tree_node_free_all (tree, node->v[i].left);
+      g_tree_node_free_all (tree, node->v[i].right);
+    }
+
+  g_tree_node_free (tree, node);
+}
 
-      if (tree->key_destroy_func)
-       tree->key_destroy_func (node->key);
-      if (tree->value_destroy_func)
-       tree->value_destroy_func (node->value);
-      g_slice_free (GTreeNode, node);
+static inline GTreeNode *
+g_tree_node_decompose (GTree     *tree,
+                       GTreeNode *node)
+{
+  guint i;
 
-      node = next;
+  if (!node || !node->data)
+    return NULL;
+
+  g_tree_node_data_unref (tree, node);
+  node->data = NULL;
+
+  for (i = 0; i < node->nv; ++i)
+    {
+      node->v[i].parent = g_tree_node_decompose (tree, node->v[i].parent);
+      node->v[i].left = g_tree_node_decompose (tree, node->v[i].left);
+      node->v[i].right = g_tree_node_decompose (tree, node->v[i].right);
     }
 
-  tree->root = NULL;
+  return node;
+}
+
+static void
+g_tree_remove_all (GTree *tree)
+{
+  guint i;
+
+  g_return_if_fail (tree != NULL);
+
+  /* decompose the graph into individual trees rooted at the various
+     root pointers in the graph, so that there are no cycles while we are
+     freeing them */
+  for (i = 0; i < tree->nr; ++i)
+    tree->r[i].root = g_tree_node_decompose (tree, tree->r[i].root);
+
+  /* free everything */
+  for (i = 0; i < tree->nr; ++i)
+    g_tree_node_free_all (tree, tree->r[i].root);
+
+  tree->r[0].root = NULL;
+  tree->r[0].version = tree->version = 0;
   tree->nnodes = 0;
 }
 
@@ -305,6 +531,7 @@ g_tree_unref (GTree *tree)
   if (g_atomic_int_dec_and_test (&tree->ref_count))
     {
       g_tree_remove_all (tree);
+      g_free (tree->r);
       g_slice_free (GTree, tree);
     }
 }
@@ -329,6 +556,180 @@ g_tree_destroy (GTree *tree)
   g_tree_unref (tree);
 }
 
+/**
+ * g_tree_current_version:
+ * @tree: a #GTree
+ *
+ * Returns the current version number of the #GTree.  Inserting or deleting
+ * keys will affect the current version, but not change earlier versions.
+ *
+ * Returns: the current version number of the #GTree.
+ **/
+guint
+g_tree_current_version (GTree *tree)
+{
+  return tree->version;
+}
+
+/**
+ * g_tree_next_version:
+ * @tree: a #GTree
+ *
+ * Increments the version number of the tree.  Inserting or deleting keys will
+ * affect the new version of the tree, but not change earlier versions.
+ *
+ * Returns: the new current version number of the #GTree.
+ **/
+guint
+g_tree_next_version (GTree *tree)
+{
+  return ++tree->version;
+}
+
+/* Make a new root pointer if the current one is not for the current version
+   of the tree */
+static void
+g_tree_root_next_version (GTree *tree)
+{
+  g_assert(tree->r[0].version <= tree->version);
+
+  if (tree->r[0].version < tree->version)
+    {
+      /* add a new version of the root */
+      tree->nr++;
+      tree->r = g_renew(GTreeRootVersion, tree->r, tree->nr);
+      /* copy the latest version from r[0] */
+      tree->r[tree->nr-1] = tree->r[0];
+      tree->r[0].version = tree->version;
+    }
+}
+
+/* add a new version of pointers to a node.  return TRUE if ok, and FALSE
+   if there is no room to do so. */
+static inline GTreeNode *
+g_tree_node_add_version (GTree     *tree,
+                         GTreeNode *node)
+{
+  if (!node)
+    return NULL;
+
+  /* node already has the current version */
+  if (node->v[0].version == tree->version)
+    return node;
+  /* if we filled the node's pointer table and need to make a new GTreeNode */
+  else if (node->v[0].version == 0 || node->nv >= TABLE_SIZE)
+    {
+      GTreeNode *newnode = g_slice_new(GTreeNode);
+      newnode->v = g_slice_alloc(sizeof(GTreeNodeVersion) * TABLE_SIZE);
+      newnode->data = node->data;
+      g_atomic_int_inc (&newnode->data->ref_count);
+      newnode->v[0] = node->v[0]; /* copy the latest version to here */
+      newnode->v[0].version = tree->version;
+      newnode->nv = 1;
+      return newnode;
+    }
+  /* there is space left in the node's pointer table for a new version */
+  else
+    {
+      node->nv++;
+      /* copy the latest version from v[0] */
+      node->v[node->nv-1] = node->v[0];
+      node->v[0].version = tree->version;
+      return node;
+    }
+}
+
+static inline void
+g_tree_node_fix_incoming (GTree     *tree,
+                          GTreeNode *oldnode,
+                          GTreeNode *newnode)
+{
+  GTreeNode *oldparent, *oldleft, *oldright;
+  GTreeNode *newparent, *newleft, *newright;
+
+  if (oldnode == newnode) return;
+
+  g_assert (oldnode != NULL);
+  g_assert (newnode != NULL);
+
+  /* find incoming edges to the old version of the node */
+  oldparent = newnode->v[0].parent;
+  if (oldparent && oldparent->v[0].left != oldnode &&
+      oldparent->v[0].right != oldnode)
+    oldparent = NULL;
+  oldleft = newnode->v[0].left;
+  if (oldleft && oldleft->v[0].parent != oldnode)
+    oldleft = NULL;
+  oldright = newnode->v[0].right;
+  if (oldright && oldright->v[0].parent != oldnode)
+    oldright = NULL;
+
+  /* add new pointers for nodes that point to me, possibly creating new
+     versions of the nodes */
+  newparent = g_tree_node_add_version (tree, oldparent);
+  newleft = g_tree_node_add_version (tree, oldleft);
+  newright = g_tree_node_add_version (tree, oldright);
+
+  /* 1. update my pointers to point to the new versions (if a prev node
+     points to me, then i'm its next node, and its my parent or
+     it is the rightmost node in my left subtree. case a, i update my
+     pointer to it already.  case b, if it's my left child i update
+     my pointer to it already, otherwise i don't have a pointer to it,
+     because i do have a left subtree.
+  */
+  if (newparent != oldparent)
+    newnode->v[0].parent = newparent;
+  if (newleft != oldleft)
+    newnode->v[0].left = newleft;
+  if (newright != oldright)
+    newnode->v[0].right = newright;
+
+  /* 2. update my incoming nodes to point back to the new version of me */
+  if (newparent)
+    {
+      if (newparent->v[0].left == oldnode)
+        newparent->v[0].left = newnode;
+      else
+        newparent->v[0].right = newnode;
+    }
+  if (newleft)
+    newleft->v[0].parent = newnode;
+  if (newright)
+    newright->v[0].parent = newnode;
+
+  /* 3. recurse on each of the incoming nodes if they had to create a new
+     version */
+  g_tree_node_fix_incoming (tree, oldparent, newparent);
+  g_tree_node_fix_incoming (tree, oldleft, newleft);
+  g_tree_node_fix_incoming (tree, oldright, newright);
+
+  /* 4. if this was the root node, then the root pointer into the tree needs
+     be updated */
+  if (tree->r[0].root == oldnode)
+    {
+      g_tree_root_next_version (tree);
+      tree->r[0].root = newnode;
+    }
+}
+
+/* You must call this on a node before you start changing its pointers,
+   or you might be changing an old (permanent) version of the node */
+static GTreeNode*
+g_tree_node_next_version (GTree     *tree,
+                          GTreeNode *oldnode)
+{
+  GTreeNode *newnode;
+
+  if (!oldnode)
+    return NULL;
+
+  g_assert(oldnode->v[0].version <= tree->version);
+
+  newnode = g_tree_node_add_version (tree, oldnode);
+  g_tree_node_fix_incoming (tree, oldnode, newnode);
+  return newnode;
+}
+
 /**
  * g_tree_insert:
  * @tree: a #GTree.
@@ -354,7 +755,11 @@ g_tree_insert (GTree    *tree,
   g_tree_insert_internal (tree, key, value, FALSE);
 
 #ifdef G_TREE_DEBUG
-  g_tree_node_check (tree->root);
+  {
+    guint i;
+    for (i = 0; i <= tree->version; ++i)
+      g_tree_node_check (g_tree_root_find_version (tree, i)->root, i);
+  }
 #endif
 }
 
@@ -384,7 +789,11 @@ g_tree_replace (GTree    *tree,
   g_tree_insert_internal (tree, key, value, TRUE);
 
 #ifdef G_TREE_DEBUG
-  g_tree_node_check (tree->root);
+  {
+    guint i;
+    for (i = 0; i <= tree->version; ++i)
+      g_tree_node_check (g_tree_root_find_version (tree, i)->root, i);
+  }
 #endif
 }
 
@@ -396,7 +805,7 @@ g_tree_replace (GTree    *tree,
 static guint
 g_tree_priority (GTreeNode *node)
 {
-  guint key = GPOINTER_TO_UINT (node);
+  guint key = GPOINTER_TO_UINT (node->data);
 
   /* This hash function is based on one found on Thomas Wang's
    * web page at
@@ -415,7 +824,8 @@ g_tree_priority (GTreeNode *node)
   return key? key : 1;
 }
 
-/* internal insert routine */
+/* Internal insert routine.  Always inserts into the current version of the
+   tree. */
 static void
 g_tree_insert_internal (GTree    *tree,
                         gpointer  key,
@@ -426,32 +836,34 @@ g_tree_insert_internal (GTree    *tree,
 
   g_return_if_fail (tree != NULL);
 
-  if (!tree->root)
+  if (!tree->r[0].root)
     {
-      tree->root = g_tree_node_new (key, value);
+      g_tree_root_next_version (tree);
+      tree->r[0].root = g_tree_node_new (tree, key, value);
       tree->nnodes++;
       return;
     }
 
-  node = tree->root;
+  node = tree->r[0].root;
 
   while (1)
     {
-      gint cmp = tree->key_compare (key, node->key, tree->key_compare_data);
+      gint cmp = tree->key_compare (key, node->data->key,
+                                    tree->key_compare_data);
       
       if (cmp == 0)
         {
           if (tree->value_destroy_func)
-            tree->value_destroy_func (node->value);
+            tree->value_destroy_func (node->data->value);
 
-          node->value = value;
+          node->data->value = value;
 
           if (replace)
             {
               if (tree->key_destroy_func)
-                tree->key_destroy_func (node->key);
+                tree->key_destroy_func (node->data->key);
 
-              node->key = key;
+              node->data->key = key;
             }
           else
             {
@@ -464,18 +876,15 @@ g_tree_insert_internal (GTree    *tree,
         }
       else if (cmp < 0)
         {
-          if (node->left_child)
-              node = node->left;
+          if (node->v[0].left)
+              node = node->v[0].left;
           else
             {
-              child = g_tree_node_new (key, value);
+              child = g_tree_node_new (tree, key, value);
+              node = g_tree_node_next_version (tree, node);
 
-              child->left = node->left;
-              child->right = node;
-              child->parent = node;
-
-              node->left = child;
-              node->left_child = TRUE;
+              child->v[0].parent = node;
+              node->v[0].left = child;
 
              tree->nnodes++;
 
@@ -484,18 +893,15 @@ g_tree_insert_internal (GTree    *tree,
         }
       else
         {
-          if (node->right_child)
-              node = node->right;
+          if (node->v[0].right)
+              node = node->v[0].right;
           else
             {
-              child = g_tree_node_new (key, value);
-
-              child->right = node->right;
-              child->left = node;
-              child->parent = node;
+              child = g_tree_node_new (tree, key, value);
+              node = g_tree_node_next_version (tree, node);
 
-              node->right = child;
-              node->right_child = TRUE;
+              child->v[0].parent = node;
+              node->v[0].right = child;
 
              tree->nnodes++;
 
@@ -506,22 +912,29 @@ g_tree_insert_internal (GTree    *tree,
 
   /* rotate the new node up until the heap property is restored */
   node = child;
-  while (node->parent &&
-         g_tree_priority (node) < g_tree_priority (node->parent))
+  while (node->v[0].parent &&
+         g_tree_priority (node) < g_tree_priority (node->v[0].parent))
     {
-      GTreeNode *const sp = node->parent->parent;
-      GTreeNode *const p = node->parent;
-      if (p->left == node)
-        g_tree_node_rotate_right (p);
+      GTreeNode *sp, *p;
+
+      /* we'll be changing both of these nodes */
+      p = g_tree_node_next_version (tree, node->v[0].parent);
+      sp = g_tree_node_next_version (tree, p->v[0].parent);
+
+      if (p->v[0].left == node)
+        node = g_tree_node_rotate_right (tree, p);
       else
-        g_tree_node_rotate_left (p);
+        node = g_tree_node_rotate_left (tree, p);
 
       if (!sp)
-        tree->root = node;
-      else if (sp->left == p)
-        sp->left = node;
+        {
+          g_tree_root_next_version (tree);
+          tree->r[0].root = node;
+        }
+      else if (sp->v[0].left == p)
+        sp->v[0].left = node;
       else
-        sp->right = node;
+        sp->v[0].right = node;
     }
 }
 
@@ -535,10 +948,13 @@ g_tree_insert_internal (GTree    *tree,
  * If the #GTree was created using g_tree_new_full(), the key and value 
  * are freed using the supplied destroy functions, otherwise you have to 
  * make sure that any dynamically allocated values are freed yourself.
+ * Note that if the key existed in
+ * earlier versions of the tree (g_tree_next_version() has been called since
+ * it was inserted), then it cannot be removed from the tree. *
  * If the key does not exist in the #GTree, the function does nothing.
  *
- * Returns: %TRUE if the key was found (prior to 2.8, this function returned 
- *   nothing)
+ * Returns: %TRUE if the key was found and able to be removed
+ *   (prior to 2.8, this function returned nothing)
  **/
 gboolean
 g_tree_remove (GTree         *tree,
@@ -551,7 +967,11 @@ g_tree_remove (GTree         *tree,
   removed = g_tree_remove_internal (tree, key, FALSE);
 
 #ifdef G_TREE_DEBUG
-  g_tree_node_check (tree->root);
+  {
+    guint i;
+    for (i = 0; i <= tree->version; ++i)
+      g_tree_node_check (g_tree_root_find_version (tree, i)->root, i);
+  }
 #endif
 
   return removed;
@@ -563,12 +983,13 @@ g_tree_remove (GTree         *tree,
  * @key: the key to remove.
  * 
  * Removes a key and its associated value from a #GTree without calling 
- * the key and value destroy functions.
- *
+ * the key and value destroy functions.  Note that if the key existed in
+ * earlier versions of the tree (g_tree_next_version() has been called since
+ * it was inserted), then it cannot be removed from the tree. *
  * If the key does not exist in the #GTree, the function does nothing.
  *
- * Returns: %TRUE if the key was found (prior to 2.8, this function returned 
- *    nothing)
+ * Returns: %TRUE if the key was found and able to be removed
+ *   (prior to 2.8, this function returned nothing)
  **/
 gboolean
 g_tree_steal (GTree         *tree,
@@ -581,7 +1002,11 @@ g_tree_steal (GTree         *tree,
   removed = g_tree_remove_internal (tree, key, TRUE);
 
 #ifdef G_TREE_DEBUG
-  g_tree_node_check (tree->root);
+  {
+    guint i;
+    for (i = 0; i <= tree->version; ++i)
+      g_tree_node_check (g_tree_root_find_version (tree, i)->root, i);
+  }
 #endif
 
   return removed;
@@ -595,98 +1020,144 @@ g_tree_remove_internal (GTree         *tree,
 {
   GTreeNode *node, *parent;
   gboolean is_leftchild;
+  gboolean data_free;
 
   g_return_val_if_fail (tree != NULL, FALSE);
 
-  if (!tree->root)
+  if (!tree->r[0].root)
     return FALSE;
 
-  node = tree->root;
+  node = tree->r[0].root;
   parent = NULL;
   is_leftchild = FALSE;
 
   while (1)
     {
-      gint cmp = tree->key_compare (key, node->key, tree->key_compare_data);
+      gint cmp = tree->key_compare (key, node->data->key,
+                                    tree->key_compare_data);
       
       if (cmp == 0)
         break;
       else if (cmp < 0)
         {
-          if (!node->left_child)
+          if (!node->v[0].left)
             return FALSE;
 
          parent = node;
           is_leftchild = TRUE;
-         node = node->left;
+         node = node->v[0].left;
         }
       else
         {
-          if (!node->right_child)
+          if (!node->v[0].right)
             return FALSE;
 
          parent = node;
           is_leftchild = FALSE;
-         node = node->right;
+         node = node->v[0].right;
         }
     }
 
   /* rotate the node down the tree, maintaining the heap property */
-  while (node->left_child || node->right_child)
+  while (node->v[0].left || node->v[0].right)
     {
-      if (!node->left_child ||
-          (node->right_child &&
-           g_tree_priority (node->left) > g_tree_priority (node->right)))
+      /* we're changing this node, make sure our pointer will stay valid
+         when we rotate it */
+      node = g_tree_node_next_version (tree, node);
+      /* getting the next version for the node may change where its parent
+         lies, so get a new pointer for it, from the node */
+      parent = node->v[0].parent;
+
+      if (!node->v[0].left ||
+          (node->v[0].right &&
+           g_tree_priority (node->v[0].left) >
+           g_tree_priority (node->v[0].right)))
         {
           /* rotate the right child up */
           if (!parent)
-            parent = tree->root = g_tree_node_rotate_left (node);
-          else if (is_leftchild)
-            parent = parent->left = g_tree_node_rotate_left (node);
+            {
+              g_tree_root_next_version (tree);
+              parent = tree->r[0].root =
+                g_tree_node_rotate_left (tree, node);
+            }
           else
-            parent = parent->right = g_tree_node_rotate_left (node);
+            {
+              parent = g_tree_node_next_version (tree, parent);
+              if (is_leftchild)
+                parent = parent->v[0].left =
+                  g_tree_node_rotate_left (tree, node);
+              else
+                parent = parent->v[0].right =
+                  g_tree_node_rotate_left (tree, node);
+            }
           is_leftchild = TRUE;
         }
       else
         {
           /* rotate the left child up */
           if (!parent)
-            parent = tree->root = g_tree_node_rotate_right (node);
-          else if (is_leftchild)
-            parent = parent->left = g_tree_node_rotate_right (node);
+            {
+              g_tree_root_next_version (tree);
+              parent = tree->r[0].root =
+                g_tree_node_rotate_right (tree, node);
+            }
           else
-            parent = parent->right = g_tree_node_rotate_right (node);
+            {
+              parent = g_tree_node_next_version (tree, parent);
+              if (is_leftchild)
+                parent = parent->v[0].left =
+                  g_tree_node_rotate_right (tree, node);
+              else
+                parent = parent->v[0].right =
+                  g_tree_node_rotate_right (tree, node);
+            }
           is_leftchild = FALSE;
         }
     }
 
   /* remove any pointers to the node in the treap */
   if (!parent)
-    tree->root = NULL;
-  else if (is_leftchild)
     {
-      parent->left_child = FALSE;
-      parent->left = node->left;
+      g_tree_root_next_version (tree);
+      tree->r[0].root = NULL;
     }
   else
     {
-      parent->right_child = FALSE;
-      parent->right = node->right;
+      /* make a new version of the parent to cut the child off.
+         making a new version of the parent may make a new version of
+         the node being deleted, which is the one we'd want to free then
+         instead, so update the node pointer */
+      parent = g_tree_node_next_version (tree, parent);
+      if (is_leftchild)
+        {
+          node = parent->v[0].left;
+          parent->v[0].left = NULL;
+        }
+      else
+        {
+          node = parent->v[0].right;
+          parent->v[0].right = NULL;
+        }
     }
-  
-  if (!steal)
+
+  tree->nnodes--;
+
+  if (node->v[0].version == tree->version)
     {
-      if (tree->key_destroy_func)
-        tree->key_destroy_func (node->key);
-      if (tree->value_destroy_func)
-        tree->value_destroy_func (node->value);
+      /* remove the entry from the node's pointer table */
+      node->v[0] = node->v[node->nv-1];
+      --node->nv;
     }
 
-  g_slice_free (GTreeNode, node);
+  node->data->stolen = steal;
+  data_free = FALSE;
 
-  tree->nnodes--;
+  /* only really delete the node if it was only in the current version,
+     otherwise it needs to be remembered */
+  if (node->nv == 0)
+    data_free = g_tree_node_free (tree, node);
 
-  return TRUE;
+  return data_free;
 }
 
 /**
@@ -705,7 +1176,7 @@ gpointer
 g_tree_lookup (GTree         *tree,
               gconstpointer  key)
 {
-  return g_tree_lookup_related (tree, key, G_TREE_SEARCH_EXACT);
+  return g_tree_lookup_related (tree, key, G_TREE_SEARCH_EXACT, tree->version);
 }
 
 /**
@@ -715,6 +1186,8 @@ g_tree_lookup (GTree         *tree,
  * @search_type: the search behavior if the @key is not present in the #GTree,
  *   one of %G_TREE_SEARCH_EXACT, %G_TREE_SEARCH_SUCCESSOR, and
  *   %G_TREE_SEARCH_PREDECESSOR.
+ * @version: the version of the tree within which to search.  If
+ *   g_tree_next_version() has not been used, then this is 0.
  *
  * Gets a value corresponding to the given key.
  *
@@ -738,15 +1211,17 @@ g_tree_lookup (GTree         *tree,
 gpointer
 g_tree_lookup_related  (GTree          *tree,
                         gconstpointer   key,
-                        GTreeSearchType search_type)
+                        GTreeSearchType search_type,
+                        guint           version)
 {
   GTreeNode *node;
 
   g_return_val_if_fail (tree != NULL, NULL);
+  g_return_val_if_fail (version <= tree->version, NULL);
 
-  node = g_tree_find_node (tree, key, search_type);
+  node = g_tree_find_node (tree, key, search_type, version);
   
-  return node ? node->value : NULL;
+  return node ? node->data->value : NULL;
 }
 
 /**
@@ -773,14 +1248,14 @@ g_tree_lookup_extended (GTree         *tree,
   
   g_return_val_if_fail (tree != NULL, FALSE);
   
-  node = g_tree_find_node (tree, lookup_key, G_TREE_SEARCH_EXACT);
+  node = g_tree_find_node (tree, lookup_key, G_TREE_SEARCH_EXACT, tree->version);
   
   if (node)
     {
       if (orig_key)
-        *orig_key = node->key;
+        *orig_key = node->data->key;
       if (value)
-        *value = node->value;
+        *value = node->data->value;
       return TRUE;
     }
   else
@@ -812,17 +1287,17 @@ g_tree_foreach (GTree         *tree,
 
   g_return_if_fail (tree != NULL);
   
-  if (!tree->root)
+  if (!tree->r[0].root)
     return;
 
-  node = g_tree_first_node (tree);
+  node = g_tree_first_node (tree, tree->version);
   
   while (node)
     {
-      if ((*func) (node->key, node->value, user_data))
+      if ((*func) (node->data->key, node->data->value, user_data))
        break;
       
-      node = g_tree_node_next (node);
+      node = g_tree_node_next (node, tree->version);
     }
 }
 
@@ -850,21 +1325,21 @@ g_tree_traverse (GTree         *tree,
 {
   g_return_if_fail (tree != NULL);
 
-  if (!tree->root)
+  if (!tree->r[0].root)
     return;
 
   switch (traverse_type)
     {
     case G_PRE_ORDER:
-      g_tree_node_pre_order (tree->root, traverse_func, user_data);
+      g_tree_node_pre_order (tree->r[0].root, traverse_func, user_data);
       break;
 
     case G_IN_ORDER:
-      g_tree_node_in_order (tree->root, traverse_func, user_data);
+      g_tree_node_in_order (tree->r[0].root, traverse_func, user_data);
       break;
 
     case G_POST_ORDER:
-      g_tree_node_post_order (tree->root, traverse_func, user_data);
+      g_tree_node_post_order (tree->r[0].root, traverse_func, user_data);
       break;
     
     case G_LEVEL_ORDER:
@@ -940,7 +1415,9 @@ g_tree_search_related (GTree          *tree,
 {
   g_return_val_if_fail (tree != NULL, NULL);
 
-  return g_tree_node_search (tree->root, search_func, user_data, search_type);
+  return g_tree_node_search (tree->r[0].root,
+                             search_func, user_data, search_type,
+                             tree->version);
 }
 
 static gint
@@ -948,8 +1425,8 @@ g_tree_node_height(GTreeNode *node)
 {
   gint l = 0, r = 0;
   if (node == NULL) return 0;
-  if (node->left_child) l = g_tree_node_height (node->left);
-  if (node->right_child) r = g_tree_node_height (node->right);
+  if (node->v[0].left) l = g_tree_node_height (node->v[0].left);
+  if (node->v[0].right) r = g_tree_node_height (node->v[0].right);
   return 1 + MAX(l, r);
 }
 
@@ -970,16 +1447,16 @@ g_tree_height (GTree *tree)
 {
   g_return_val_if_fail (tree != NULL, 0);
 
-  return g_tree_node_height (tree->root);
+  return g_tree_node_height (tree->r[0].root);
 }
 
 /**
  * g_tree_nnodes:
  * @tree: a #GTree.
  * 
- * Gets the number of nodes in a #GTree.
+ * Gets the number of nodes in the current version of a #GTree.
  * 
- * Return value: the number of nodes in the #GTree.
+ * Return value: the number of nodes in the latest version of the #GTree.
  **/
 gint
 g_tree_nnodes (GTree *tree)
@@ -992,38 +1469,43 @@ g_tree_nnodes (GTree *tree)
 static GTreeNode *
 g_tree_find_node (GTree           *tree,
                   gconstpointer    key,
-                  GTreeSearchType  search_type)
+                  GTreeSearchType  search_type,
+                  guint            version)
 {
   GTreeNode *node, *remember;
+  GTreeRootVersion *rv;
   gint cmp;
 
-  node = tree->root;
+  rv = g_tree_root_find_version (tree, version);
+  node = rv->root;
   if (!node)
     return NULL;
 
   remember = NULL;
   while (1)
     {
-      cmp = tree->key_compare (key, node->key, tree->key_compare_data);
+      cmp = tree->key_compare (key, node->data->key, tree->key_compare_data);
       if (cmp == 0)
        return node;
       else if (cmp < 0)
        {
+          GTreeNodeVersion *nodev = g_tree_node_find_version (node, version);
           if (search_type == G_TREE_SEARCH_SUCCESSOR)
             remember = node;
-         if (!node->left_child)
+         if (!nodev->left)
            return remember;
 
-         node = node->left;
+         node = nodev->left;
        }
       else
        {
+          GTreeNodeVersion *nodev = g_tree_node_find_version (node, version);
           if (search_type == G_TREE_SEARCH_PREDECESSOR)
             remember = node;
-         if (!node->right_child)
+         if (!nodev->right)
            return remember;
 
-         node = node->right;
+         node = nodev->right;
        }
     }
 }
@@ -1033,18 +1515,18 @@ g_tree_node_pre_order (GTreeNode     *node,
                       GTraverseFunc  traverse_func,
                       gpointer       data)
 {
-  if ((*traverse_func) (node->key, node->value, data))
+  if ((*traverse_func) (node->data->key, node->data->value, data))
     return TRUE;
 
-  if (node->left_child)
+  if (node->v[0].left)
     {
-      if (g_tree_node_pre_order (node->left, traverse_func, data))
+      if (g_tree_node_pre_order (node->v[0].left, traverse_func, data))
        return TRUE;
     }
 
-  if (node->right_child)
+  if (node->v[0].right)
     {
-      if (g_tree_node_pre_order (node->right, traverse_func, data))
+      if (g_tree_node_pre_order (node->v[0].right, traverse_func, data))
        return TRUE;
     }
 
@@ -1056,18 +1538,18 @@ g_tree_node_in_order (GTreeNode     *node,
                      GTraverseFunc  traverse_func,
                      gpointer       data)
 {
-  if (node->left_child)
+  if (node->v[0].left)
     {
-      if (g_tree_node_in_order (node->left, traverse_func, data))
+      if (g_tree_node_in_order (node->v[0].left, traverse_func, data))
        return TRUE;
     }
 
-  if ((*traverse_func) (node->key, node->value, data))
+  if ((*traverse_func) (node->data->key, node->data->value, data))
     return TRUE;
 
-  if (node->right_child)
+  if (node->v[0].right)
     {
-      if (g_tree_node_in_order (node->right, traverse_func, data))
+      if (g_tree_node_in_order (node->v[0].right, traverse_func, data))
        return TRUE;
     }
   
@@ -1079,19 +1561,19 @@ g_tree_node_post_order (GTreeNode     *node,
                        GTraverseFunc  traverse_func,
                        gpointer       data)
 {
-  if (node->left_child)
+  if (node->v[0].left)
     {
-      if (g_tree_node_post_order (node->left, traverse_func, data))
+      if (g_tree_node_post_order (node->v[0].left, traverse_func, data))
        return TRUE;
     }
 
-  if (node->right_child)
+  if (node->v[0].right)
     {
-      if (g_tree_node_post_order (node->right, traverse_func, data))
+      if (g_tree_node_post_order (node->v[0].right, traverse_func, data))
        return TRUE;
     }
 
-  if ((*traverse_func) (node->key, node->value, data))
+  if ((*traverse_func) (node->data->key, node->data->value, data))
     return TRUE;
 
   return FALSE;
@@ -1101,7 +1583,8 @@ static gpointer
 g_tree_node_search (GTreeNode       *node,
                    GCompareFunc     search_func,
                    gconstpointer    data,
-                    GTreeSearchType  search_type)
+                    GTreeSearchType  search_type,
+                    guint            version)
 {
   gint dir;
   GTreeNode *remember;
@@ -1112,140 +1595,146 @@ g_tree_node_search (GTreeNode       *node,
   remember = NULL;
   while (1) 
     {
-      dir = (* search_func) (node->key, data);
+      dir = (* search_func) (node->data->key, data);
       if (dir == 0)
-       return node->value;
+       return node->data->value;
       else if (dir < 0) 
        { 
+          GTreeNodeVersion *nodev = g_tree_node_find_version (node, version);
           if (search_type == G_TREE_SEARCH_SUCCESSOR)
             remember = node;
-         if (!node->left_child)
+         if (!nodev->left)
            return remember;
 
-         node = node->left;
+         node = nodev->left;
        }
       else
        {
+          GTreeNodeVersion *nodev = g_tree_node_find_version (node, version);
           if (search_type == G_TREE_SEARCH_PREDECESSOR)
             remember = node;
-         if (!node->right_child)
+         if (!nodev->right)
            return remember;
          
-         node = node->right;
+         node = nodev->right;
        }
     }
 }
 
 static GTreeNode*
-g_tree_node_rotate_left (GTreeNode *node)
+g_tree_node_rotate_left (GTree     *tree,
+                         GTreeNode *node)
 {
   GTreeNode *right;
 
-  right = node->right;
+  g_assert (node->v[0].version == tree->version);
+
+  right = g_tree_node_next_version (tree, node->v[0].right);
 
-  if (right->left_child)
+  if (right->v[0].left)
     {
-      node->right = right->left;
-      node->right->parent = node;
+      node->v[0].right = g_tree_node_next_version (tree, right->v[0].left);
+      node->v[0].right->v[0].parent = node;
     }
   else
-    {
-      node->right_child = FALSE;
-      node->right = right;
-      right->left_child = TRUE;
-    }
-  right->left = node;
-  right->parent = node->parent;
-  node->parent = right;
+      node->v[0].right = NULL;
+  right->v[0].left = node;
+  right->v[0].parent = node->v[0].parent;
+  node->v[0].parent = right;
 
   return right;
 }
 
 static GTreeNode*
-g_tree_node_rotate_right (GTreeNode *node)
+g_tree_node_rotate_right (GTree     *tree,
+                          GTreeNode *node)
 {
   GTreeNode *left;
 
-  left = node->left;
+  g_assert (node->v[0].version == tree->version);
+
+  left = g_tree_node_next_version (tree, node->v[0].left);
 
-  if (left->right_child)
+  if (left->v[0].right)
     {
-      node->left = left->right;
-      node->left->parent = node;
+      node->v[0].left = g_tree_node_next_version (tree, left->v[0].right);
+      node->v[0].left->v[0].parent = node;
     }
   else
-    {
-      node->left_child = FALSE;
-      node->left = left;
-      left->right_child = TRUE;
-    }
-  left->right = node;
-  left->parent = node->parent;
-  node->parent = left;
+    node->v[0].left = NULL;
+  left->v[0].right = node;
+  left->v[0].parent = node->v[0].parent;
+  node->v[0].parent = left;
 
   return left;
 }
 
 #ifdef G_TREE_DEBUG
 static void
-g_tree_node_check (GTreeNode *node)
+g_tree_node_check (GTreeNode *node,
+                   guint      version)
 {
-  gint left_height;
-  gint right_height;
-  GTreeNode *tmp;
+  GTreeNodeVersion *nv, *tmpv;
 
   if (node)
     {
-      if (node->left_child)
+      nv = g_tree_node_find_version (node, version);
+
+      g_assert (nv->left == NULL || nv->left != nv->right);
+
+      if (nv->left)
        {
-         tmp = g_tree_node_previous (node);
-         g_assert (tmp->right == node);
+          tmpv = g_tree_node_find_version (nv->left, version);
+          g_assert (tmpv->parent == node);
        }
 
-      if (node->right_child)
+      if (nv->right)
        {
-         tmp = g_tree_node_next (node);
-         g_assert (tmp->left == node);
+          tmpv = g_tree_node_find_version (nv->right, version);
+          g_assert (tmpv->parent == node);
        }
 
-      left_height = 0;
-      right_height = 0;
-      
-      if (node->left_child)
-       left_height = g_tree_node_height (node->left);
-      if (node->right_child)
-       right_height = g_tree_node_height (node->right);
-      
-      if (node->left_child)
-       g_tree_node_check (node->left);
-      if (node->right_child)
-       g_tree_node_check (node->right);
+      if (nv->parent)
+        {
+          tmpv = g_tree_node_find_version (nv->parent, version);
+          g_assert (tmpv->left == node || tmpv->right == node);
+        }
+
+      if (nv->left)
+       g_tree_node_check (nv->left, version);
+      if (nv->right)
+       g_tree_node_check (nv->right, version);
     }
 }
 
 static void
-g_tree_node_dump (GTreeNode *node, 
+g_tree_node_dump (GTreeNode *node,
+                  guint      version,
                  gint       indent)
 {
-  g_print ("%*s%c\n", indent, "", *(char *)node->key);
+  GTreeNodeVersion *nv = g_tree_node_find_version (node, version);
+
+  g_print ("%*s%c\n", indent, "", *(char *)node->data->key);
 
-  if (node->left_child)
-    g_tree_node_dump (node->left, indent + 2);
-  else if (node->left)
-    g_print ("%*s<%c\n", indent + 2, "", *(char *)node->left->key);
+  if (nv->left)
+    g_tree_node_dump (nv->left, version, indent + 2);
+  else if ((node = g_tree_node_previous (node, version)))
+    g_print ("%*s<%c\n", indent + 2, "", *(char *)node->data->key);
 
-  if (node->right_child)
-    g_tree_node_dump (node->right, indent + 2);
-  else if (node->right)
-    g_print ("%*s>%c\n", indent + 2, "", *(char *)node->right->key);
+  if (nv->right)
+    g_tree_node_dump (nv->right, version, indent + 2);
+  else if ((node = g_tree_node_next (node, version)))
+    g_print ("%*s>%c\n", indent + 2, "", *(char *)node->data->key);
 }
 
 
 void
-g_tree_dump (GTree *tree)
+g_tree_dump (GTree *tree,
+             guint  version)
 {
-  if (tree->root)
-    g_tree_node_dump (tree->root, 0);
+  GTreeRootVersion *rv = g_tree_root_find_version (tree, version);
+  if (rv->root)
+    g_tree_node_dump (rv->root, version, 0);
 }
 #endif
 
index 45f15b6d06e933e1ffced7fb94e0c52beabe77d1..9665d70bd8f2dcc587a889271f5ba1a2d7d56ee0 100644 (file)
@@ -61,6 +61,8 @@ GTree*   g_tree_new_full        (GCompareDataFunc  key_compare_func,
 GTree*   g_tree_ref             (GTree            *tree);
 void     g_tree_unref           (GTree            *tree);
 void     g_tree_destroy         (GTree            *tree);
+guint    g_tree_current_version (GTree            *tree);
+guint    g_tree_next_version    (GTree            *tree);
 void     g_tree_insert          (GTree            *tree,
                                  gpointer          key,
                                  gpointer          value);
@@ -79,7 +81,8 @@ gboolean g_tree_lookup_extended (GTree            *tree,
                                  gpointer         *value);
 gpointer g_tree_lookup_related  (GTree            *tree,
                                  gconstpointer     key,
-                                 GTreeSearchType   search_type);
+                                 GTreeSearchType   search_type,
+                                 guint             version);
 void     g_tree_foreach         (GTree            *tree,
                                  GTraverseFunc    func,
                                  gpointer         user_data);
index 709b5044841b06f44144932fbacc0aab0cd29b14..6e32ea058872edfec1c9d51e30e05975f2a29503 100644 (file)
@@ -162,64 +162,26 @@ main (int   argc,
     gchar *r;
 
     r = g_tree_lookup_related (tree, &chars[i],
-                               G_TREE_SEARCH_SUCCESSOR);
+                               G_TREE_SEARCH_SUCCESSOR, 0);
     g_assert((i == n-1 && r == NULL) || (r && *r == chars[i+1]));
 
     g_tree_insert (tree, &chars[i], &chars[i]);
 
     r = g_tree_lookup_related (tree, &chars[i],
-                               G_TREE_SEARCH_PREDECESSOR);
+                               G_TREE_SEARCH_PREDECESSOR, 0);
     g_assert(r && *r == chars[i]);
     r = g_tree_lookup_related (tree, &chars[i],
-                               G_TREE_SEARCH_SUCCESSOR);
+                               G_TREE_SEARCH_SUCCESSOR, 0);
     g_assert(r && *r == chars[i]);
   }
   g_assert(n == g_tree_nnodes(tree));
 
-  g_tree_unref (tree);
-  tree = g_tree_new (my_compare);
-
-  for (i = 0; chars[i]; i++) {
-    gchar *r;
-
-    r = g_tree_lookup_related (tree, &chars[i],
-                               G_TREE_SEARCH_PREDECESSOR);
-    g_assert((i == 0 && r == NULL) || (r && *r == chars[i-1]));
-
-    g_tree_insert (tree, &chars[i], &chars[i]);
-
-    r = g_tree_lookup_related (tree, &chars[i],
-                               G_TREE_SEARCH_PREDECESSOR);
-    g_assert(r && *r == chars[i]);
-    r = g_tree_lookup_related (tree, &chars[i],
-                               G_TREE_SEARCH_SUCCESSOR);
-    g_assert(r && *r == chars[i]);
-  }
-  g_assert(i == g_tree_nnodes(tree));
-
-  traverse_num = 0;
-  g_tree_foreach (tree, my_traverse, NULL);
-  g_assert(traverse_num == g_tree_nnodes(tree));
-
-  g_assert (g_tree_nnodes (tree) == strlen (chars));
-  /*g_assert (g_tree_height (tree) == 6);*/
-
-  preorder = g_malloc(strlen(chars)+1);
-  preorder[strlen(chars)] = 0;
-  preorder_i = 0;
-  g_tree_traverse (tree, traverse_preorder, G_PRE_ORDER, NULL);
-  g_assert(traverse_num == g_tree_nnodes(tree));
-  g_assert(calc_height(chars, preorder, strlen(chars)) == g_tree_height(tree));
-  g_free(preorder);
-  
-  p = chars;
-  g_tree_foreach (tree, check_order, &p);
-
   for (i = 0; i < 26; i++)
     {
       removed = g_tree_remove (tree, &chars[i + 10]);
       g_assert (removed);
     }
+  g_assert (g_tree_nnodes (tree) == strlen (chars2));
 
   c = '\0';
   removed = g_tree_remove (tree, &c);
@@ -229,9 +191,6 @@ main (int   argc,
   g_tree_foreach (tree, my_traverse, NULL);
   g_assert(traverse_num == g_tree_nnodes(tree));
 
-  g_assert (g_tree_nnodes (tree) == strlen (chars2));
-  /*g_assert (g_tree_height (tree) == 6);*/
-
   p = chars2;
   g_tree_foreach (tree, check_order, &p);
 
@@ -297,9 +256,123 @@ main (int   argc,
   p = g_tree_search (tree, my_search, &c);
   g_assert (p == NULL);
 
+  g_tree_unref (tree);
+  tree = g_tree_new (my_compare);
+
+  for (i = 0; chars[i]; i++) {
+    gchar *r;
+    gint j;
+
+    r = g_tree_lookup_related (tree, &chars[i],
+                               G_TREE_SEARCH_PREDECESSOR, i);
+    g_assert((i == 0 && r == NULL) || (r && *r == chars[i-1]));
+
+    if (i > 0)
+      {
+        r = g_tree_lookup_related (tree, &chars[i],
+                                   G_TREE_SEARCH_PREDECESSOR, i-1);
+        g_assert((i == 1 && r == NULL) || (r && *r == chars[i-2]));
+      }
+
+    g_tree_next_version (tree);
+    g_tree_insert (tree, &chars[i], &chars[i]);
+
+    r = g_tree_lookup_related (tree, &chars[i],
+                               G_TREE_SEARCH_PREDECESSOR, i+1);
+    g_assert(r && *r == chars[i]);
+    r = g_tree_lookup_related (tree, &chars[i],
+                               G_TREE_SEARCH_SUCCESSOR, i+1);
+    g_assert(r && *r == chars[i]);
+
+    for (j = 0; j <= i+1; ++j)
+      {
+        r = g_tree_lookup_related (tree, &chars[i],
+                                   G_TREE_SEARCH_PREDECESSOR, j);
+        g_assert((j == 0 && r == NULL) || (r && *r == chars[j-1]));
+      }
+  }
+  g_assert(i == g_tree_nnodes(tree));
+
+  traverse_num = 0;
+  g_tree_foreach (tree, my_traverse, NULL);
+  g_assert(traverse_num == g_tree_nnodes(tree));
+
+  g_assert (g_tree_nnodes (tree) == strlen (chars));
+
+  preorder = g_malloc(strlen(chars)+1);
+  preorder[strlen(chars)] = 0;
+  preorder_i = 0;
+  g_tree_traverse (tree, traverse_preorder, G_PRE_ORDER, NULL);
+  g_assert(traverse_num == g_tree_nnodes(tree));
+  g_assert(calc_height(chars, preorder, strlen(chars)) == g_tree_height(tree));
+  g_free(preorder);
+
+  p = chars;
+  g_tree_foreach (tree, check_order, &p);
+
+  for (i = 0; i < 26; i++)
+    {
+      g_tree_next_version (tree);
+      removed = g_tree_remove (tree, &chars[i + 10]);
+      g_assert (removed == FALSE);
+    }
+  g_assert (g_tree_nnodes (tree) == strlen (chars2));
+
+  g_tree_next_version (tree);
+  removed = g_tree_remove (tree, &chars[36]);
+  g_assert (removed == FALSE);
+  removed = g_tree_remove (tree, &chars[37]);
+  g_assert (removed == FALSE);
+  removed = g_tree_remove (tree, &chars[38]);
+  g_assert (removed == FALSE);
+
+  n = strlen(chars);
+  for (i = 0; i <= n+26+1; ++i)
+    {
+      gchar *r;
+      gint j;
+
+      for (j = 0; chars[j]; ++j)
+        {
+          r = g_tree_lookup_related (tree, &chars[j],
+                                     G_TREE_SEARCH_PREDECESSOR, i);
+
+          if (i == 0) /* the tree was empty */
+            g_assert (r == NULL);
+          else if (i <= n) /* items were being inserted */
+            {
+              if (j < i)
+                g_assert (r && *r == chars[j]);
+              else
+                g_assert (r && *r == chars[i-1]);
+            }
+          else /* some items have been removed */
+            {
+              if (j < 10)
+                g_assert (r && *r == chars[j]);
+              else if (i == n+26+1 && j >= 36 && j <= 38)
+                g_assert (r && *r == chars[9]);
+              else if (j > 10+i-(n+1)) /* last char that has been removed in
+                                          version i */
+                g_assert (r && *r == chars[j]);
+              else
+                g_assert (r && *r == chars[9]);
+            }
+        }
+    }
 
   g_tree_destroy (tree);
 
+  /* test destroying a versioned tree */
+  tree = g_tree_new (my_compare);
+
+  for (i = 0; chars[i]; i++) {
+    g_tree_next_version (tree);
+    g_tree_insert (tree, &chars[i], &chars[i]);
+  }
+
+  g_tree_unref (tree);
+
   tree = g_tree_new_full ((GCompareDataFunc)my_compare, NULL, 
                          my_key_destroy, 
                          my_value_destroy);
@@ -335,6 +408,8 @@ main (int   argc,
   g_assert (destroyed_key == NULL);
   g_assert (destroyed_value == NULL);
 
+  g_tree_unref (tree);
+
   return 0;
 }