trying to make you delete versions. instead it'd be nice to insert/delete in any...
authorDana Jansens <danakj@orodu.net>
Tue, 3 Nov 2009 23:19:38 +0000 (18:19 -0500)
committerDana Jansens <danakj@orodu.net>
Thu, 12 Nov 2009 22:21:08 +0000 (17:21 -0500)
glib/gtree.c
glib/gtree.h
tests/tree-test.c

index 65e00fc0753e1b3c348eb68868144f0270a96853..67f4671ea30e6cf4e734c6ece10a85979af0f48f 100644 (file)
@@ -84,7 +84,8 @@ struct _GTreeNodeData
 {
   gpointer   key;         /* key for this node */
   gpointer   value;       /* value stored at this node */
-  guint      ref_count;
+  guint16    ref_count;
+  guint8     stolen;      /* true if the node is stolen instead of removed */
 };
 
 struct _GTreeNode
@@ -153,6 +154,7 @@ g_tree_node_new (GTree   *tree,
   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
@@ -527,9 +529,125 @@ g_tree_current_version (GTree *tree)
 guint
 g_tree_next_version (GTree *tree)
 {
+  g_assert (tree->version+1 != 0);
   return ++tree->version;
 }
 
+/* return TRUE if the node no stores the pointers that were in the version */
+static gboolean
+g_tree_node_delete_version (GTree     *tree,
+                            GTreeNode *node,
+                            guint      version)
+{
+  GTreeNodeVersion *nv;
+  guint next, i;
+  gboolean l, r, removed = FALSE;
+
+  if (!node)
+    return TRUE;
+
+  nv = g_tree_node_find_version (node, version);
+
+  l = g_tree_node_delete_version (tree, nv->left, version);
+  r = g_tree_node_delete_version (tree, nv->right, version);
+
+  if (nv->version == version)
+    {
+      i = nv - node->v;
+      if (i == 0) /* latest version */
+        next = tree->version;
+      else if (i == node->nv-1) /* last in the node's list */
+        next = node->v[0].version;
+      else /* somewhere else in the node's list (has something after it) */
+        next = (nv+1)->version;
+
+      if (nv->version+1 < next)
+        nv->version++; /* remove the version, but keep the pointer for versions
+                          larger than it */
+      else
+        {
+          /* already have a different set of pointers for version+1, so we can
+             remove this set entirely */
+          if (i == 0)
+            {
+              node->v[0] = node->v[node->nv-1];
+              node->nv--;
+            }
+          else
+            {
+              for (; i < node->nv-1; ++i)
+                node->v[i] = node->v[i+1];
+              node->nv--;
+            }
+          removed = TRUE;
+        }
+    }
+
+  /* if the child is not pointing to us anymore then we'd better not be 
+     pointing to it, and if it is, then we'd better still be too */
+  g_assert (l == removed && r == removed);
+
+  /* if we removed the last version inside the node, then we can free it */
+  if (node->nv == 0)
+    {
+      if (--node->data->ref_count == 0)
+        {
+          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);
+        }
+
+      g_slice_free1 (sizeof(GTreeNodeVersion) *
+                     (node->version(NOW) == 0 ? 1 : TABLE_SIZE),
+                     node->v);
+      g_slice_free (GTreeNode, node);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+void
+g_tree_delete_version (GTree *tree,
+                       guint  version)
+{
+  GTreeRootVersion *rv;
+
+  rv = g_tree_root_find_version (tree, version);
+
+  if (g_tree_node_delete_version (tree, rv->root, version))
+    {
+      guint i;
+
+      i = rv - tree->roots;
+      if (i == 0)
+        {
+          tree->roots[0] = tree->roots[tree->nroots-1];
+          tree->nroots--;
+        }
+      else
+        {
+          for (; i < tree->nroots-1; ++i)
+            tree->roots[i] = tree->roots[i+1];
+          tree->nroots--;
+        }
+    }
+
+#ifdef G_TREE_DEBUG
+  {
+    guint i;
+    for (i = 0; i < tree->roots[0].version; ++i)
+      g_tree_node_check (g_tree_root_find_version (tree, i)->root, i);
+  }
+#endif
+}
+
 /* Make a new root pointer if the current one is not for the current version
    of the tree */
 static void
@@ -929,7 +1047,8 @@ g_tree_remove (GTree         *tree,
  * Removes a key and its associated value from a #GTree without calling 
  * 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. *
+ * it was inserted), then it cannot be removed from the tree, until all
+ * versions containing the node are removed from the tree.
  * If the key does not exist in the #GTree, the function does nothing.
  *
  * Returns: %TRUE if the key was found and able to be removed
@@ -1099,6 +1218,8 @@ g_tree_remove_internal (GTree         *tree,
                      node->v);
       g_slice_free (GTreeNode, node);
     }
+  else
+    node->data->stolen = TRUE;
 
   return key_removed;
 }
index 9665d70bd8f2dcc587a889271f5ba1a2d7d56ee0..b723aebb8f7a0530c44ee3149e8a351595665eee 100644 (file)
@@ -63,6 +63,8 @@ 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_delete_version  (GTree            *tree,
+                                 guint             version);
 void     g_tree_insert          (GTree            *tree,
                                  gpointer          key,
                                  gpointer          value);
index e4c57cd19f80a9c87a75c4926d297ebbf75dd5ce..4ed704cedeb6ff1c9f577f84bd03d5fe0e6d3a12 100644 (file)
@@ -257,6 +257,8 @@ main (int   argc,
   g_assert (p == NULL);
 
   g_tree_unref (tree);
+
+  /* test adding and removing items in a versioned tree */
   tree = g_tree_new (my_compare);
 
   for (i = 0; chars[i]; i++) {
@@ -363,6 +365,58 @@ main (int   argc,
 
   g_tree_destroy (tree);
 
+  /* test removing versions from a tree */
+  tree = g_tree_new (my_compare);
+
+  for (i = 0; chars[i]; i++) {
+    g_tree_insert (tree, &chars[i], &chars[i]);
+    g_tree_next_version (tree);
+  }
+  g_assert(i == g_tree_nnodes(tree));
+
+  /* remove everything from the last version */
+  for (i = 0; chars[i]; i++)
+    g_tree_remove (tree, &chars[i]);
+  g_assert(g_tree_nnodes(tree) == 0);
+
+  g_assert (g_tree_current_version (tree) == i);
+
+  for (i = 0; i < g_tree_current_version (tree); ++i)
+    {
+      char *r;
+      int j;
+
+      for (j = 0; chars[j]; j++)
+        {
+          r = g_tree_lookup_related (tree, &chars[j],
+                                     G_TREE_SEARCH_PREDECESSOR, i);
+          g_assert (r && *r == chars[MIN(j,i)]);
+        }
+    }
+
+  for (i = 0; chars[i]; i++)
+    if (i%2==0)
+      g_tree_delete_version (tree, i);
+  
+  for (i = 0; chars[i]; i++)
+    if (i%2!=0)
+      g_tree_delete_version (tree, i);
+
+  for (i = 0; i < g_tree_current_version (tree); ++i)
+    {
+      char *r;
+      int j;
+
+      for (j = 0; chars[j]; j++)
+        {
+          r = g_tree_lookup_related (tree, &chars[j],
+                                     G_TREE_SEARCH_PREDECESSOR, i);
+          g_assert (r == NULL);
+        }
+    }
+
+  g_tree_unref (tree);
+
   tree = g_tree_new_full ((GCompareDataFunc)my_compare, NULL, 
                          my_key_destroy, 
                          my_value_destroy);