From: Dana Jansens Date: Fri, 13 Nov 2009 16:20:47 +0000 (-0500) Subject: Make a GTree persistent. X-Git-Url: http://git.openbox.org/?a=commitdiff_plain;h=e5140fdb4ec99728bedab4765bd644f1af674ac4;p=dana%2Fcg-glib.git Make a GTree persistent. * 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(). --- diff --git a/glib/gtree.c b/glib/gtree.c index 8870a8f5..34946f80 100644 --- a/glib/gtree.c +++ b/glib/gtree.c @@ -35,34 +35,68 @@ #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 diff --git a/glib/gtree.h b/glib/gtree.h index 45f15b6d..9665d70b 100644 --- a/glib/gtree.h +++ b/glib/gtree.h @@ -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); diff --git a/tests/tree-test.c b/tests/tree-test.c index 709b5044..6e32ea05 100644 --- a/tests/tree-test.c +++ b/tests/tree-test.c @@ -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; }