aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/marktree.c
diff options
context:
space:
mode:
authorbfredl <bjorn.linse@gmail.com>2020-11-22 10:10:37 +0100
committerbfredl <bjorn.linse@gmail.com>2023-09-12 10:38:23 +0200
commitb04286a187d57c50f01cd36cd4668b7a69026579 (patch)
tree2f2a403f8b0132976dfe0a61eb78f65028329cdd /src/nvim/marktree.c
parent6b5f44817e93c2985f3ea32122f1dc0047054018 (diff)
downloadrneovim-b04286a187d57c50f01cd36cd4668b7a69026579.tar.gz
rneovim-b04286a187d57c50f01cd36cd4668b7a69026579.tar.bz2
rneovim-b04286a187d57c50f01cd36cd4668b7a69026579.zip
feat(extmark): support proper multiline ranges
The removes the previous restriction that nvim_buf_set_extmark() could not be used to highlight arbitrary multi-line regions The problem can be summarized as follows: let's assume an extmark with a hl_group is placed covering the region (5,0) to (50,0) Now, consider what happens if nvim needs to redraw a window covering the lines 20-30. It needs to be able to ask the marktree what extmarks cover this region, even if they don't begin or end here. Therefore the marktree needs to be augmented with the information covers a point, not just what marks begin or end there. To do this, we augment each node with a field "intersect" which is a set the ids of the marks which overlap this node, but only if it is not part of the set of any parent. This ensures the number of nodes that need to be explicitly marked grows only logarithmically with the total number of explicitly nodes (and thus the number of of overlapping marks). Thus we can quickly iterate all marks which overlaps any query position by looking up what leaf node contains that position. Then we only need to consider all "start" marks within that leaf node, and the "intersect" set of that node and all its parents. Now, and the major source of complexity is that the tree restructuring operations (to ensure that each node has T-1 <= size <= 2*T-1) also need to update these sets. If a full inner node is split in two, one of the new parents might start to completely overlap some ranges and its ids will need to be moved from its children's sets to its own set. Similarly, if two undersized nodes gets joined into one, it might no longer completely overlap some ranges, and now the children which do needs to have the have the ids in its set instead. And then there are the pivots! Yes the pivot operations when a child gets moved from one parent to another.
Diffstat (limited to 'src/nvim/marktree.c')
-rw-r--r--src/nvim/marktree.c1596
1 files changed, 1339 insertions, 257 deletions
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index d07d176b6d..d8b8dbba29 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -14,8 +14,6 @@
// Use marktree_itr_current and marktree_itr_next/prev to read marks in a loop.
// marktree_del_itr deletes the current mark of the iterator and implicitly
// moves the iterator to the next mark.
-//
-// Work is ongoing to fully support ranges (mark pairs).
// Copyright notice for kbtree (included in heavily modified form):
//
@@ -58,19 +56,27 @@
#include "nvim/memory.h"
#include "nvim/pos.h"
+// only for debug functions
+#include "nvim/api/private/helpers.h"
+
#define T MT_BRANCH_FACTOR
-#define ILEN (sizeof(mtnode_t) + (2 * T) * sizeof(void *))
+#define ILEN (sizeof(MTNode) + (2 * T) * sizeof(void *))
#define ID_INCR (((uint64_t)1) << 2)
-#define rawkey(itr) ((itr)->node->key[(itr)->i])
+#define rawkey(itr) ((itr)->x->key[(itr)->i])
-static bool pos_leq(mtpos_t a, mtpos_t b)
+static bool pos_leq(MTPos a, MTPos b)
{
return a.row < b.row || (a.row == b.row && a.col <= b.col);
}
-static void relative(mtpos_t base, mtpos_t *val)
+static bool pos_less(MTPos a, MTPos b)
+{
+ return !pos_leq(b, a);
+}
+
+static void relative(MTPos base, MTPos *val)
{
assert(pos_leq(base, *val));
if (val->row == base.row) {
@@ -81,7 +87,7 @@ static void relative(mtpos_t base, mtpos_t *val)
}
}
-static void unrelative(mtpos_t base, mtpos_t *val)
+static void unrelative(MTPos base, MTPos *val)
{
if (val->row == 0) {
val->row = base.row;
@@ -91,7 +97,7 @@ static void unrelative(mtpos_t base, mtpos_t *val)
}
}
-static void compose(mtpos_t *base, mtpos_t val)
+static void compose(MTPos *base, MTPos val)
{
if (val.row == 0) {
base->col += val.col;
@@ -101,12 +107,21 @@ static void compose(mtpos_t *base, mtpos_t val)
}
}
+// Used by `marktree_splice`. Need to keep track of marks which moved
+// in order to repair intersections.
+typedef struct {
+ uint64_t id;
+ MTNode *old, *new;
+ int old_i, new_i;
+} Damage;
+typedef kvec_withinit_t(Damage, 8) DamageList;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "marktree.c.generated.h"
#endif
#define mt_generic_cmp(a, b) (((b) < (a)) - ((a) < (b)))
-static int key_cmp(mtkey_t a, mtkey_t b)
+static int key_cmp(MTKey a, MTKey b)
{
int cmp = mt_generic_cmp(a.pos.row, b.pos.row);
if (cmp != 0) {
@@ -116,18 +131,25 @@ static int key_cmp(mtkey_t a, mtkey_t b)
if (cmp != 0) {
return cmp;
}
- // NB: keeping the events at the same pos sorted by id is actually not
- // necessary only make sure that START is before END etc.
- return mt_generic_cmp(a.flags, b.flags);
+
+ // TODO(bfredl): MT_FLAG_REAL could go away if we fix marktree_getp_aux for real
+ const uint16_t cmp_mask = MT_FLAG_RIGHT_GRAVITY | MT_FLAG_END | MT_FLAG_REAL | MT_FLAG_LAST;
+ return mt_generic_cmp(a.flags & cmp_mask, b.flags & cmp_mask);
}
-static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
+/// @return position of k if it exists in the node, otherwise the position
+/// it should be inserted, which ranges from 0 to x->n _inclusively_
+/// @param match (optional) set to TRUE if match (pos, gravity) was found
+static inline int marktree_getp_aux(const MTNode *x, MTKey k, bool *match)
{
- int tr, *rr, begin = 0, end = x->n;
+ bool dummy_match;
+ bool *m = match ? match : &dummy_match;
+
+ int begin = 0, end = x->n;
if (x->n == 0) {
+ *m = false;
return -1;
}
- rr = r ? r : &tr;
while (begin < end) {
int mid = (begin + end) >> 1;
if (key_cmp(x->key[mid], k) < 0) {
@@ -137,47 +159,84 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
}
}
if (begin == x->n) {
- *rr = 1; return x->n - 1;
+ *m = false;
+ return x->n - 1;
}
- if ((*rr = key_cmp(k, x->key[begin])) < 0) {
+ if (!(*m = (key_cmp(k, x->key[begin]) == 0))) {
begin--;
}
return begin;
}
-static inline void refkey(MarkTree *b, mtnode_t *x, int i)
+static inline void refkey(MarkTree *b, MTNode *x, int i)
{
pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x);
}
+static MTNode *id2node(MarkTree *b, uint64_t id)
+{
+ return pmap_get(uint64_t)(b->id2node, id);
+}
+
// put functions
// x must be an internal node, which is not full
// x->ptr[i] should be a full node, i e x->ptr[i]->n == 2*T-1
-static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
+static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
{
- mtnode_t *y = x->ptr[i];
- mtnode_t *z;
- z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t));
- b->n_nodes++;
+ MTNode *y = x->ptr[i];
+ MTNode *z = marktree_alloc_node(b, y->level);
z->level = y->level;
z->n = T - 1;
- memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (T - 1));
+
+ // tricky: we might split a node in between inserting the start node and the end
+ // node of the same pair. Then we must not intersect this id yet (done later
+ // in marktree_intersect_pair).
+ uint64_t last_start = mt_end(next) ? mt_lookup_id(next.ns, next.id, false) : MARKTREE_END_FLAG;
+
+ // no alloc in the common case (less than 4 intersects)
+ kvi_copy(z->intersect, y->intersect);
+
+ if (!y->level) {
+ uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index
+ for (int j = 0; j < T; j++) {
+ MTKey k = y->key[j];
+ uint64_t pi_end = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, true), true);
+ if (mt_start(k) && pi_end > pi && mt_lookup_key(k) != last_start) {
+ intersect_node(b, z, mt_lookup_id(k.ns, k.id, false));
+ }
+ }
+
+ // note: y->key[T-1] is moved up and thus checked for both
+ for (int j = T - 1; j < (T * 2) - 1; j++) {
+ MTKey k = y->key[j];
+ uint64_t pi_start = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, false), true);
+ if (mt_end(k) && pi_start > 0 && pi_start < pi) {
+ intersect_node(b, y, mt_lookup_id(k.ns, k.id, false));
+ }
+ }
+ }
+
+ memcpy(z->key, &y->key[T], sizeof(MTKey) * (T - 1));
for (int j = 0; j < T - 1; j++) {
refkey(b, z, j);
}
if (y->level) {
- memcpy(z->ptr, &y->ptr[T], sizeof(mtnode_t *) * T);
+ memcpy(z->ptr, &y->ptr[T], sizeof(MTNode *) * T);
for (int j = 0; j < T; j++) {
z->ptr[j]->parent = z;
+ z->ptr[j]->p_idx = (int16_t)j;
}
}
y->n = T - 1;
memmove(&x->ptr[i + 2], &x->ptr[i + 1],
- sizeof(mtnode_t *) * (size_t)(x->n - i));
+ sizeof(MTNode *) * (size_t)(x->n - i));
x->ptr[i + 1] = z;
z->parent = x; // == y->parent
- memmove(&x->key[i + 1], &x->key[i], sizeof(mtkey_t) * (size_t)(x->n - i));
+ for (int j = i + 1; j < x->n + 2; j++) {
+ x->ptr[j]->p_idx = (int16_t)j;
+ }
+ memmove(&x->key[i + 1], &x->key[i], sizeof(MTKey) * (size_t)(x->n - i));
// move key to internal layer:
x->key[i] = y->key[T - 1];
@@ -190,25 +249,32 @@ static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
if (i > 0) {
unrelative(x->key[i - 1].pos, &x->key[i].pos);
}
+
+ if (y->level) {
+ bubble_up(y);
+ bubble_up(z);
+ } else {
+ // code above goose here
+ }
}
// x must not be a full node (even if there might be internal space)
-static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
+static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k)
{
- int i;
+ // TODO(bfredl): ugh, make sure this is the _last_ valid (pos, gravity) position,
+ // to minimize movement
+ int i = marktree_getp_aux(x, k, NULL) + 1;
if (x->level == 0) {
- i = marktree_getp_aux(x, k, 0);
- if (i != x->n - 1) {
- memmove(&x->key[i + 2], &x->key[i + 1],
- (size_t)(x->n - i - 1) * sizeof(mtkey_t));
+ if (i != x->n) {
+ memmove(&x->key[i + 1], &x->key[i],
+ (size_t)(x->n - i) * sizeof(MTKey));
}
- x->key[i + 1] = k;
- refkey(b, x, i + 1);
+ x->key[i] = k;
+ refkey(b, x, i);
x->n++;
} else {
- i = marktree_getp_aux(x, k, 0) + 1;
if (x->ptr[i]->n == 2 * T - 1) {
- split_node(b, x, i);
+ split_node(b, x, i, k);
if (key_cmp(k, x->key[i]) > 0) {
i++;
}
@@ -220,7 +286,7 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
}
}
-void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right)
+void marktree_put(MarkTree *b, MTKey key, int end_row, int end_col, bool end_right)
{
assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK));
if (end_row >= 0) {
@@ -230,32 +296,151 @@ void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_r
marktree_put_key(b, key);
if (end_row >= 0) {
- mtkey_t end_key = key;
+ MTKey end_key = key;
end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY)
|(uint16_t)MT_FLAG_END
|(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0));
- end_key.pos = (mtpos_t){ end_row, end_col };
+ end_key.pos = (MTPos){ end_row, end_col };
marktree_put_key(b, end_key);
+ MarkTreeIter itr[1] = { 0 }, end_itr[1] = { 0 };
+ marktree_lookup(b, mt_lookup_key(key), itr);
+ marktree_lookup(b, mt_lookup_key(end_key), end_itr);
+
+ marktree_intersect_pair(b, mt_lookup_key(key), itr, end_itr, false);
+ }
+}
+
+// this is currently not used very often, but if it was it should use binary search
+static bool intersection_has(Intersection *x, uint64_t id)
+{
+ for (size_t i = 0; i < kv_size(*x); i++) {
+ if (kv_A(*x, i) == id) {
+ return true;
+ } else if (kv_A(*x, i) >= id) {
+ return false;
+ }
}
+ return false;
}
-void marktree_put_key(MarkTree *b, mtkey_t k)
+static void intersect_node(MarkTree *b, MTNode *x, uint64_t id)
+{
+ assert(!(id & MARKTREE_END_FLAG));
+ kvi_pushp(x->intersect);
+ // optimized for the common case: new key is always in the end
+ for (ssize_t i = (ssize_t)kv_size(x->intersect) - 1; i >= 0; i--) {
+ if (i > 0 && kv_A(x->intersect, i - 1) > id) {
+ kv_A(x->intersect, i) = kv_A(x->intersect, i - 1);
+ } else {
+ kv_A(x->intersect, i) = id;
+ break;
+ }
+ }
+}
+
+static void unintersect_node(MarkTree *b, MTNode *x, uint64_t id, bool strict)
+{
+ assert(!(id & MARKTREE_END_FLAG));
+ bool seen = false;
+ size_t i;
+ for (i = 0; i < kv_size(x->intersect); i++) {
+ if (kv_A(x->intersect, i) < id) {
+ continue;
+ } else if (kv_A(x->intersect, i) == id) {
+ seen = true;
+ break;
+ } else { // (kv_A(x->intersect, i) > id)
+ break;
+ }
+ }
+ if (strict) {
+ assert(seen);
+ }
+
+ if (seen) {
+ if (i < kv_size(x->intersect) - 1) {
+ memmove(&kv_A(x->intersect, i), &kv_A(x->intersect, i + 1), (kv_size(x->intersect) - i - 1) *
+ sizeof(kv_A(x->intersect, i)));
+ }
+ kv_size(x->intersect)--;
+ }
+}
+
+/// @param itr mutated
+/// @param end_itr not mutated
+void marktree_intersect_pair(MarkTree *b, uint64_t id, MarkTreeIter *itr, MarkTreeIter *end_itr,
+ bool delete)
+{
+ int lvl = 0, maxlvl = MIN(itr->lvl, end_itr->lvl);
+#define iat(itr, l, q) ((l == itr->lvl) ? itr->i + q : itr->s[l].i)
+ for (; lvl < maxlvl; lvl++) {
+ if (itr->s[lvl].i > end_itr->s[lvl].i) {
+ return; // empty range
+ } else if (itr->s[lvl].i < end_itr->s[lvl].i) {
+ break; // work to do
+ }
+ }
+ if (lvl == maxlvl && iat(itr, lvl, 1) > iat(end_itr, lvl, 0)) {
+ return; // empty range
+ }
+
+ while (itr->x) {
+ bool skip = false;
+ if (itr->x == end_itr->x) {
+ if (itr->x->level == 0 || itr->i >= end_itr->i) {
+ break;
+ } else {
+ skip = true;
+ }
+ } else if (itr->lvl > lvl) {
+ skip = true;
+ } else {
+ if (iat(itr, lvl, 1) < iat(end_itr, lvl, 1)) {
+ skip = true;
+ } else {
+ lvl++;
+ }
+ }
+
+ if (skip) {
+ if (itr->x->level) {
+ MTNode *x = itr->x->ptr[itr->i + 1];
+ if (delete) {
+ unintersect_node(b, x, id, true);
+ } else {
+ intersect_node(b, x, id);
+ }
+ }
+ }
+ marktree_itr_next_skip(b, itr, skip, true, NULL);
+ }
+#undef iat
+}
+
+static MTNode *marktree_alloc_node(MarkTree *b, bool internal)
+{
+ MTNode *x = xcalloc(1, internal ? ILEN : sizeof(MTNode));
+ kvi_init(x->intersect);
+ b->n_nodes++;
+ return x;
+}
+
+void marktree_put_key(MarkTree *b, MTKey k)
{
k.flags |= MT_FLAG_REAL; // let's be real.
if (!b->root) {
- b->root = (mtnode_t *)xcalloc(1, ILEN);
- b->n_nodes++;
+ b->root = marktree_alloc_node(b, true);
}
- mtnode_t *r, *s;
+ MTNode *r, *s;
b->n_keys++;
r = b->root;
if (r->n == 2 * T - 1) {
- b->n_nodes++;
- s = (mtnode_t *)xcalloc(1, ILEN);
+ s = marktree_alloc_node(b, true);
b->root = s; s->level = r->level + 1; s->n = 0;
s->ptr[0] = r;
r->parent = s;
- split_node(b, s, 0);
+ r->p_idx = 0;
+ split_node(b, s, 0, k);
r = s;
}
marktree_putp_aux(b, r, k);
@@ -289,22 +474,31 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
{
int adjustment = 0;
- mtnode_t *cur = itr->node;
+ MTNode *cur = itr->x;
int curi = itr->i;
uint64_t id = mt_lookup_key(cur->key[curi]);
- // fprintf(stderr, "\nDELET %lu\n", id);
- mtkey_t raw = rawkey(itr);
+ MTKey raw = rawkey(itr);
uint64_t other = 0;
- if (mt_paired(raw)) {
- other = mt_lookup_id(raw.ns, raw.id, !mt_end(raw));
+ if (mt_paired(raw) && !(raw.flags & MT_FLAG_ORPHANED)) {
+ other = mt_lookup_key_side(raw, !mt_end(raw));
+
+ MarkTreeIter other_itr[1];
+ marktree_lookup(b, other, other_itr);
+ rawkey(other_itr).flags |= MT_FLAG_ORPHANED;
+ // Remove intersect markers. NB: must match exactly!
+ if (mt_start(raw)) {
+ MarkTreeIter this_itr[1] = { *itr }; // mutated copy
+ marktree_intersect_pair(b, id, this_itr, other_itr, true);
+ } else {
+ marktree_intersect_pair(b, other, other_itr, itr, true);
+ }
}
- if (itr->node->level) {
+ if (itr->x->level) {
if (rev) {
abort();
} else {
- // fprintf(stderr, "INTERNAL %d\n", cur->level);
// steal previous node
marktree_itr_prev(b, itr);
adjustment = -1;
@@ -312,41 +506,72 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
}
// 3.
- mtnode_t *x = itr->node;
+ MTNode *x = itr->x;
assert(x->level == 0);
- mtkey_t intkey = x->key[itr->i];
+ MTKey intkey = x->key[itr->i];
if (x->n > itr->i + 1) {
memmove(&x->key[itr->i], &x->key[itr->i + 1],
- sizeof(mtkey_t) * (size_t)(x->n - itr->i - 1));
+ sizeof(MTKey) * (size_t)(x->n - itr->i - 1));
}
x->n--;
+ b->n_keys--;
+ pmap_del(uint64_t)(b->id2node, id, NULL);
+
// 4.
// if (adjustment == 1) {
// abort();
// }
if (adjustment == -1) {
int ilvl = itr->lvl - 1;
- const mtnode_t *lnode = x;
+ MTNode *lnode = x;
+ uint64_t start_id = 0;
+ bool did_bubble = false;
+ if (mt_end(intkey)) {
+ start_id = mt_lookup_key_side(intkey, false);
+ }
do {
- const mtnode_t *const p = lnode->parent;
+ MTNode *p = lnode->parent;
if (ilvl < 0) {
abort();
}
- const int i = itr->s[ilvl].i;
+ int i = itr->s[ilvl].i;
assert(p->ptr[i] == lnode);
if (i > 0) {
unrelative(p->key[i - 1].pos, &intkey.pos);
}
+
+ if (p != cur && start_id) {
+ if (intersection_has(&p->ptr[0]->intersect, start_id)) {
+ // if not the first time, we need to undo the addition in the
+ // previous step (`intersect_node` just below)
+ int last = (lnode != x) ? 1 : 0;
+ for (int k = 0; k < p->n + last; k++) { // one less as p->ptr[n] is the last
+ unintersect_node(b, p->ptr[k], start_id, true);
+ }
+ intersect_node(b, p, start_id);
+ did_bubble = true;
+ }
+ }
+
lnode = p;
ilvl--;
} while (lnode != cur);
- mtkey_t deleted = cur->key[curi];
+ MTKey deleted = cur->key[curi];
cur->key[curi] = intkey;
refkey(b, cur, curi);
+ // if `did_bubble` then we already added `start_id` to some parent
+ if (mt_end(cur->key[curi]) && !did_bubble) {
+ uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index
+ uint64_t pi_start = pseudo_index_for_id(b, start_id, true);
+ if (pi_start > 0 && pi_start < pi) {
+ intersect_node(b, x, start_id);
+ }
+ }
+
relative(intkey.pos, &deleted.pos);
- mtnode_t *y = cur->ptr[curi + 1];
+ MTNode *y = cur->ptr[curi + 1];
if (deleted.pos.row || deleted.pos.col) {
while (y) {
for (int k = 0; k < y->n; k++) {
@@ -358,46 +583,48 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
itr->i--;
}
- b->n_keys--;
- pmap_del(uint64_t)(b->id2node, id, NULL);
-
// 5.
bool itr_dirty = false;
int rlvl = itr->lvl - 1;
int *lasti = &itr->i;
+ MTPos ppos = itr->pos;
while (x != b->root) {
assert(rlvl >= 0);
- mtnode_t *p = x->parent;
+ MTNode *p = x->parent;
if (x->n >= T - 1) {
// we are done, if this node is fine the rest of the tree will be
break;
}
int pi = itr->s[rlvl].i;
assert(p->ptr[pi] == x);
+ if (pi > 0) {
+ ppos.row -= p->key[pi - 1].pos.row;
+ ppos.col = itr->s[rlvl].oldcol;
+ }
+ // ppos is now the pos of p
+
if (pi > 0 && p->ptr[pi - 1]->n > T - 1) {
*lasti += 1;
itr_dirty = true;
// steal one key from the left neighbour
- pivot_right(b, p, pi - 1);
+ pivot_right(b, ppos, p, pi - 1);
break;
} else if (pi < p->n && p->ptr[pi + 1]->n > T - 1) {
// steal one key from right neighbour
- pivot_left(b, p, pi);
+ pivot_left(b, ppos, p, pi);
break;
} else if (pi > 0) {
- // fprintf(stderr, "LEFT ");
assert(p->ptr[pi - 1]->n == T - 1);
// merge with left neighbour
*lasti += T;
x = merge_node(b, p, pi - 1);
if (lasti == &itr->i) {
// TRICKY: we merged the node the iterator was on
- itr->node = x;
+ itr->x = x;
}
itr->s[rlvl].i--;
itr_dirty = true;
} else {
- // fprintf(stderr, "RIGHT ");
assert(pi < p->n && p->ptr[pi + 1]->n == T - 1);
merge_node(b, p, pi);
// no iter adjustment needed
@@ -414,18 +641,18 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
itr->lvl--;
}
if (b->root->level) {
- mtnode_t *oldroot = b->root;
+ MTNode *oldroot = b->root;
b->root = b->root->ptr[0];
b->root->parent = NULL;
- xfree(oldroot);
+ marktree_free_node(b, oldroot);
} else {
// no items, nothing for iterator to point to
// not strictly needed, should handle delete right-most mark anyway
- itr->node = NULL;
+ itr->x = NULL;
}
}
- if (itr->node && itr_dirty) {
+ if (itr->x && itr_dirty) {
marktree_itr_fix_pos(b, itr);
}
@@ -441,10 +668,10 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
marktree_itr_next(b, itr);
marktree_itr_next(b, itr);
} else {
- if (itr->node && itr->i >= itr->node->n) {
+ if (itr->x && itr->i >= itr->x->n) {
// we deleted the last key of a leaf node
// go to the inner key after that.
- assert(itr->node->level == 0);
+ assert(itr->x->level == 0);
marktree_itr_next(b, itr);
}
}
@@ -452,9 +679,229 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
return other;
}
-static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
+/// similar to intersect_common but modify x and y in place to retain
+/// only the items which are NOT in common
+static void intersect_merge(Intersection *restrict m, Intersection *restrict x,
+ Intersection *restrict y)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
+ size_t xi = 0, yi = 0;
+ size_t xn = 0, yn = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ // TODO(bfredl): kvi_pushp is actually quite complex, break out kvi_resize() to a function?
+ kvi_push(*m, kv_A(*x, xi));
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ kv_A(*x, xn++) = kv_A(*x, xi++);
+ } else {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ }
+ }
+
+ if (xi < kv_size(*x)) {
+ memmove(&kv_A(*x, xn), &kv_A(*x, xi), sizeof(kv_A(*x, xn)) * (kv_size(*x) - xi));
+ xn += kv_size(*x) - xi;
+ }
+ if (yi < kv_size(*y)) {
+ memmove(&kv_A(*y, yn), &kv_A(*y, yi), sizeof(kv_A(*y, yn)) * (kv_size(*y) - yi));
+ yn += kv_size(*y) - yi;
+ }
+
+ kv_size(*x) = xn;
+ kv_size(*y) = yn;
+}
+
+// w used to be a child of x but it is now a child of y, adjust intersections accordingly
+// @param[out] d are intersections which should be added to the old children of y
+static void intersect_mov(Intersection *restrict x, Intersection *restrict y,
+ Intersection *restrict w, Intersection *restrict d)
+{
+ size_t wi = 0, yi = 0;
+ size_t wn = 0, yn = 0;
+ size_t xi = 0;
+ while (wi < kv_size(*w) || xi < kv_size(*x)) {
+ if (wi < kv_size(*w) && (xi >= kv_size(*x) || kv_A(*x, xi) >= kv_A(*w, wi))) {
+ if (xi < kv_size(*x) && kv_A(*x, xi) == kv_A(*w, wi)) {
+ xi++;
+ }
+ // now w < x strictly
+ while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*w, wi)) {
+ kvi_push(*d, kv_A(*y, yi));
+ yi++;
+ }
+ if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*w, wi)) {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ wi++;
+ } else {
+ kv_A(*w, wn++) = kv_A(*w, wi++);
+ }
+ } else {
+ // x < w strictly
+ while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*x, xi)) {
+ kvi_push(*d, kv_A(*y, yi));
+ yi++;
+ }
+ if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*x, xi)) {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ xi++;
+ } else {
+ // add kv_A(x, xi) at kv_A(w, wn), pushing up wi if wi == wn
+ if (wi == wn) {
+ size_t n = kv_size(*w) - wn;
+ kvi_pushp(*w);
+ if (n > 0) {
+ memmove(&kv_A(*w, wn + 1), &kv_A(*w, wn), n * sizeof(kv_A(*w, 0)));
+ }
+ kv_A(*w, wi) = kv_A(*x, xi);
+ wn++;
+ wi++; // no need to consider the added element again
+ } else {
+ assert(wn < wi);
+ kv_A(*w, wn++) = kv_A(*x, xi);
+ }
+ xi++;
+ }
+ }
+ }
+ if (yi < kv_size(*y)) {
+ // move remaining items to d
+ size_t n = kv_size(*y) - yi; // at least one
+ kvi_ensure_more_space(*d, n);
+ memcpy(&kv_A(*d, kv_size(*d)), &kv_A(*y, yi), n * sizeof(kv_A(*d, 0)));
+ kv_size(*d) += n;
+ }
+ kv_size(*w) = wn;
+ kv_size(*y) = yn;
+}
+
+bool intersect_mov_test(uint64_t *x, size_t nx, uint64_t *y, size_t ny, uint64_t *win, size_t nwin,
+ uint64_t *wout, size_t *nwout, uint64_t *dout, size_t *ndout)
+{
+ // x is immutable in the context of intersect_mov. y might shrink, but we
+ // don't care about it (we get it the deleted ones in d)
+ Intersection xi = { .items = x, .size = nx };
+ Intersection yi = { .items = y, .size = ny };
+
+ Intersection w;
+ kvi_init(w);
+ for (size_t i = 0; i < nwin; i++) {
+ kvi_push(w, win[i]);
+ }
+ Intersection d;
+ kvi_init(d);
+
+ intersect_mov(&xi, &yi, &w, &d);
+
+ if (w.size > *nwout || d.size > *ndout) {
+ return false;
+ }
+
+ memcpy(wout, w.items, sizeof(w.items[0]) * w.size);
+ *nwout = w.size;
+
+ memcpy(dout, d.items, sizeof(d.items[0]) * d.size);
+ *ndout = d.size;
+
+ return true;
+}
+
+/// intersection: i = x & y
+static void intersect_common(Intersection *i, Intersection *x, Intersection *y)
+{
+ size_t xi = 0, yi = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ kvi_push(*i, kv_A(*x, xi));
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ xi++;
+ } else {
+ yi++;
+ }
+ }
+}
+
+// inplace union: x |= y
+static void intersect_add(Intersection *x, Intersection *y)
+{
+ size_t xi = 0, yi = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ xi++;
+ yi++;
+ } else if (kv_A(*y, yi) < kv_A(*x, xi)) {
+ size_t n = kv_size(*x) - xi; // at least one
+ kvi_pushp(*x);
+ memmove(&kv_A(*x, xi + 1), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0)));
+ kv_A(*x, xi) = kv_A(*y, yi);
+ xi++; // newly added element
+ yi++;
+ } else {
+ xi++;
+ }
+ }
+ if (yi < kv_size(*y)) {
+ size_t n = kv_size(*y) - yi; // at least one
+ kvi_ensure_more_space(*x, n);
+ memcpy(&kv_A(*x, kv_size(*x)), &kv_A(*y, yi), n * sizeof(kv_A(*x, 0)));
+ kv_size(*x) += n;
+ }
+}
+
+// inplace assymetric difference: x &= ~y
+static void intersect_sub(Intersection *restrict x, Intersection *restrict y)
+{
+ size_t xi = 0, yi = 0;
+ size_t xn = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ kv_A(*x, xn++) = kv_A(*x, xi++);
+ } else {
+ yi++;
+ }
+ }
+ if (xi < kv_size(*x)) {
+ size_t n = kv_size(*x) - xi;
+ if (xn < xi) { // otherwise xn == xi
+ memmove(&kv_A(*x, xn), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0)));
+ }
+ xn += n;
+ }
+ kv_size(*x) = xn;
+}
+
+/// x is a node which shrunk, or the half of a split
+///
+/// this means that intervals which previously intersected all the (current)
+/// child nodes, now instead intersects `x` itself.
+static void bubble_up(MTNode *x)
+{
+ Intersection xi;
+ kvi_init(xi);
+ // due to invariants, the largest subset of _all_ subnodes is the intersection
+ // between the first and the last
+ intersect_common(&xi, &x->ptr[0]->intersect, &x->ptr[x->n]->intersect);
+ if (kv_size(xi)) {
+ for (int i = 0; i < x->n + 1; i++) {
+ intersect_sub(&x->ptr[i]->intersect, &xi);
+ }
+ intersect_add(&x->intersect, &xi);
+ }
+ kvi_destroy(xi);
+}
+
+static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
+{
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
+ Intersection m;
+ kvi_init(m);
+
+ intersect_merge(&m, &x->intersect, &y->intersect);
x->key[x->n] = p->key[i];
refkey(b, x, x->n);
@@ -462,35 +909,78 @@ static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
relative(p->key[i - 1].pos, &x->key[x->n].pos);
}
- memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey));
for (int k = 0; k < y->n; k++) {
refkey(b, x, x->n + 1 + k);
unrelative(x->key[x->n].pos, &x->key[x->n + 1 + k].pos);
}
if (x->level) {
- memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *));
- for (int k = 0; k < y->n + 1; k++) {
- x->ptr[x->n + k + 1]->parent = x;
+ // bubble down: ranges that intersected old-x but not old-y or vice versa
+ // must be moved to their respective children
+ memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
+ for (int k = 0; k < x->n + 1; k++) {
+ // TODO(bfredl): dedicated impl for "Z |= Y"
+ for (size_t idx = 0; idx < kv_size(x->intersect); idx++) {
+ intersect_node(b, x->ptr[k], kv_A(x->intersect, idx));
+ }
+ }
+ for (int ky = 0; ky < y->n + 1; ky++) {
+ int k = x->n + ky + 1;
+ // nodes that used to be in y, now the second half of x
+ x->ptr[k]->parent = x;
+ x->ptr[k]->p_idx = (int16_t)k;
+ // TODO(bfredl): dedicated impl for "Z |= X"
+ for (size_t idx = 0; idx < kv_size(y->intersect); idx++) {
+ intersect_node(b, x->ptr[k], kv_A(y->intersect, idx));
+ }
}
}
x->n += y->n + 1;
- memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t));
+ memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(MTKey));
memmove(&p->ptr[i + 1], &p->ptr[i + 2],
- (size_t)(p->n - i - 1) * sizeof(mtkey_t *));
+ (size_t)(p->n - i - 1) * sizeof(MTKey *));
+ for (int j = i + 1; j < p->n; j++) { // note: one has been deleted
+ p->ptr[j]->p_idx = (int16_t)j;
+ }
p->n--;
- xfree(y);
- b->n_nodes--;
+ marktree_free_node(b, y);
+
+ kvi_destroy(x->intersect);
+
+ // move of a kvec_withinit_t, messy!
+ // TODO(bfredl): special case version of intersect_merge(x_out, x_in_m_out, y) to avoid this
+ kvi_move(&x->intersect, &m);
+
return x;
}
+/// @param dest is overwritten (assumed to already been freed/moved)
+/// @param src consumed (don't free or use)
+void kvi_move(Intersection *dest, Intersection *src)
+{
+ dest->size = src->size;
+ dest->capacity = src->capacity;
+ if (src->items == src->init_array) {
+ memcpy(dest->init_array, src->init_array, src->size * sizeof(*src->init_array));
+ dest->items = dest->init_array;
+ } else {
+ dest->items = src->items;
+ }
+}
+
// TODO(bfredl): as a potential "micro" optimization, pivoting should balance
// the two nodes instead of stealing just one key
-static void pivot_right(MarkTree *b, mtnode_t *p, int i)
+// x_pos is the absolute position of the key just before x (or a dummy key strictly less than any
+// key inside x, if x is the first leaf)
+static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
- memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
+ memmove(&y->key[1], y->key, (size_t)y->n * sizeof(MTKey));
if (y->level) {
- memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *));
+ memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
+ for (int j = 1; j < y->n + 2; j++) {
+ y->ptr[j]->p_idx = (int16_t)j;
+ }
}
y->key[0] = p->key[i];
refkey(b, y, 0);
@@ -499,6 +989,7 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i)
if (x->level) {
y->ptr[0] = x->ptr[x->n];
y->ptr[0]->parent = y;
+ y->ptr[0]->p_idx = 0;
}
x->n--;
y->n++;
@@ -509,11 +1000,46 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i)
for (int k = 1; k < y->n; k++) {
unrelative(y->key[0].pos, &y->key[k].pos);
}
+
+ // repair intersections of x
+ if (x->level) {
+ // handle y and first new y->ptr[0]
+ Intersection d;
+ kvi_init(d);
+ // y->ptr[0] was moved from x to y
+ // adjust y->ptr[0] for a difference between the parents
+ // in addition, this might cause some intersection of the old y
+ // to bubble down to the old children of y (if y->ptr[0] wasn't intersected)
+ intersect_mov(&x->intersect, &y->intersect, &y->ptr[0]->intersect, &d);
+ if (kv_size(d)) {
+ for (int yi = 1; yi < y->n + 1; yi++) {
+ intersect_add(&y->ptr[yi]->intersect, &d);
+ }
+ }
+ kvi_destroy(d);
+
+ bubble_up(x);
+ } else {
+ // if the last element of x used to be an end node, check if it now covers all of x
+ if (mt_end(p->key[i])) {
+ uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index
+ uint64_t start_id = mt_lookup_key_side(p->key[i], false);
+ uint64_t pi_start = pseudo_index_for_id(b, start_id, true);
+ if (pi_start > 0 && pi_start < pi) {
+ intersect_node(b, x, start_id);
+ }
+ }
+
+ if (mt_start(y->key[0])) {
+ // no need for a check, just delet it if it was there
+ unintersect_node(b, y, mt_lookup_key(y->key[0]), false);
+ }
+ }
}
-static void pivot_left(MarkTree *b, mtnode_t *p, int i)
+static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
// reverse from how we "always" do it. but pivot_left
// is just the inverse of pivot_right, so reverse it literally.
@@ -532,40 +1058,88 @@ static void pivot_left(MarkTree *b, mtnode_t *p, int i)
if (x->level) {
x->ptr[x->n + 1] = y->ptr[0];
x->ptr[x->n + 1]->parent = x;
+ x->ptr[x->n + 1]->p_idx = (int16_t)(x->n + 1);
}
- memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(mtkey_t));
+ memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(MTKey));
if (y->level) {
- memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *));
+ memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(MTNode *));
+ for (int j = 0; j < y->n; j++) { // note: last item deleted
+ y->ptr[j]->p_idx = (int16_t)j;
+ }
}
x->n++;
y->n--;
+
+ // repair intersections of x,y
+ if (x->level) {
+ // handle y and first new y->ptr[0]
+ Intersection d;
+ kvi_init(d);
+ // x->ptr[x->n] was moved from y to x
+ // adjust x->ptr[x->n] for a difference between the parents
+ // in addition, this might cause some intersection of the old x
+ // to bubble down to the old children of x (if x->ptr[n] wasn't intersected)
+ intersect_mov(&y->intersect, &x->intersect, &x->ptr[x->n]->intersect, &d);
+ if (kv_size(d)) {
+ for (int xi = 0; xi < x->n; xi++) { // ptr[x->n| deliberately skipped
+ intersect_add(&x->ptr[xi]->intersect, &d);
+ }
+ }
+ kvi_destroy(d);
+
+ bubble_up(y);
+ } else {
+ // if the first element of y used to be an start node, check if it now covers all of y
+ if (mt_start(p->key[i])) {
+ uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index
+
+ uint64_t end_id = mt_lookup_key_side(p->key[i], true);
+ uint64_t pi_end = pseudo_index_for_id(b, end_id, true);
+
+ if (pi_end > pi) {
+ intersect_node(b, y, mt_lookup_key(p->key[i]));
+ }
+ }
+
+ if (mt_end(x->key[x->n - 1])) {
+ // no need for a check, just delet it if it was there
+ unintersect_node(b, x, mt_lookup_key_side(x->key[x->n - 1], false), false);
+ }
+ }
}
/// frees all mem, resets tree to valid empty state
void marktree_clear(MarkTree *b)
{
if (b->root) {
- marktree_free_node(b->root);
+ marktree_free_subtree(b, b->root);
b->root = NULL;
}
map_destroy(uint64_t, b->id2node);
*b->id2node = (PMap(uint64_t)) MAP_INIT;
b->n_keys = 0;
- b->n_nodes = 0;
+ assert(b->n_nodes == 0);
}
-void marktree_free_node(mtnode_t *x)
+void marktree_free_subtree(MarkTree *b, MTNode *x)
{
if (x->level) {
for (int i = 0; i < x->n + 1; i++) {
- marktree_free_node(x->ptr[i]);
+ marktree_free_subtree(b, x->ptr[i]);
}
}
+ marktree_free_node(b, x);
+}
+
+static void marktree_free_node(MarkTree *b, MTNode *x)
+{
+ kvi_destroy(x->intersect);
xfree(x);
+ b->n_nodes--;
}
/// NB: caller must check not pair!
-void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key)
+void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, MTKey key)
{
// TODO(bfredl): clean up this mess and re-instantiate &= and |= forms
// once we upgrade to a non-broken version of gcc in functionaltest-lua CI
@@ -578,49 +1152,108 @@ void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_
rawkey(itr).priority = key.priority;
}
+/// @param itr iterator is invalid after call
void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col)
{
- mtkey_t key = rawkey(itr);
- // TODO(bfredl): optimize when moving a mark within a leaf without moving it
- // across neighbours!
- marktree_del_itr(b, itr, false);
- key.pos = (mtpos_t){ row, col };
+ MTKey key = rawkey(itr);
+ MTNode *x = itr->x;
+ if (!x->level) {
+ bool internal = false;
+ MTPos newpos = MTPos(row, col);
+ if (x->parent != NULL) {
+ // strictly _after_ key before `x`
+ // (not optimal when x is very first leaf of the entire tree, but that's fine)
+ if (pos_less(itr->pos, newpos)) {
+ relative(itr->pos, &newpos);
+
+ // strictly before the end of x. (this could be made sharper by
+ // finding the internal key just after x, but meh)
+ if (pos_less(newpos, x->key[x->n - 1].pos)) {
+ internal = true;
+ }
+ }
+ } else {
+ // tree is one node. newpos thus is already "relative" itr->pos
+ internal = true;
+ }
+
+ if (internal) {
+ key.pos = newpos;
+ bool match;
+ // tricky: could minimize movement in either direction better
+ int new_i = marktree_getp_aux(x, key, &match);
+ if (!match) {
+ new_i++;
+ }
+ if (new_i == itr->i || key_cmp(key, x->key[new_i]) == 0) {
+ x->key[itr->i].pos = newpos;
+ } else if (new_i < itr->i) {
+ memmove(&x->key[new_i + 1], &x->key[new_i], sizeof(MTKey) * (size_t)(itr->i - new_i));
+ x->key[new_i] = key;
+ } else if (new_i > itr->i) {
+ memmove(&x->key[itr->i], &x->key[itr->i + 1], sizeof(MTKey) * (size_t)(new_i - itr->i));
+ x->key[new_i] = key;
+ }
+ return;
+ }
+ }
+ uint64_t other = marktree_del_itr(b, itr, false);
+ key.pos = (MTPos){ row, col };
marktree_put_key(b, key);
- itr->node = NULL; // itr might become invalid by put
+
+ if (other) {
+ marktree_restore_pair(b, key);
+ }
+ itr->x = NULL; // itr might become invalid by put
+}
+
+void marktree_restore_pair(MarkTree *b, MTKey key)
+{
+ MarkTreeIter itr[1];
+ MarkTreeIter end_itr[1];
+ marktree_lookup(b, mt_lookup_key_side(key, false), itr);
+ marktree_lookup(b, mt_lookup_key_side(key, true), end_itr);
+ if (!itr->x || !end_itr->x) {
+ // this could happen if the other end is waiting to be restored later
+ // this function will be called again for the other end.
+ return;
+ }
+ rawkey(itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED;
+ rawkey(end_itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED;
+
+ marktree_intersect_pair(b, mt_lookup_key_side(key, false), itr, end_itr, false);
}
// itr functions
-// TODO(bfredl): static inline?
bool marktree_itr_get(MarkTree *b, int32_t row, int col, MarkTreeIter *itr)
{
- return marktree_itr_get_ext(b, (mtpos_t){ row, col },
- itr, false, false, NULL);
+ return marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL);
}
-bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity,
- mtpos_t *oldbase)
+bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bool gravity,
+ MTPos *oldbase)
{
if (b->n_keys == 0) {
- itr->node = NULL;
+ itr->x = NULL;
return false;
}
- mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
+ MTKey k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
if (last && !gravity) {
k.flags = MT_FLAG_LAST;
}
- itr->pos = (mtpos_t){ 0, 0 };
- itr->node = b->root;
+ itr->pos = (MTPos){ 0, 0 };
+ itr->x = b->root;
itr->lvl = 0;
if (oldbase) {
oldbase[itr->lvl] = itr->pos;
}
while (true) {
- itr->i = marktree_getp_aux(itr->node, k, 0) + 1;
+ itr->i = marktree_getp_aux(itr->x, k, 0) + 1;
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
break;
}
@@ -628,10 +1261,10 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
itr->s[itr->lvl].oldcol = itr->pos.col;
if (itr->i > 0) {
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
- relative(itr->node->key[itr->i - 1].pos, &k.pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
+ relative(itr->x->key[itr->i - 1].pos, &k.pos);
}
- itr->node = itr->node->ptr[itr->i];
+ itr->x = itr->x->ptr[itr->i];
itr->lvl++;
if (oldbase) {
oldbase[itr->lvl] = itr->pos;
@@ -640,7 +1273,7 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
if (last) {
return marktree_itr_prev(b, itr);
- } else if (itr->i >= itr->node->n) {
+ } else if (itr->i >= itr->x->n) {
return marktree_itr_next(b, itr);
}
return true;
@@ -648,19 +1281,20 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
{
- itr->node = b->root;
if (b->n_keys == 0) {
+ itr->x = NULL;
return false;
}
+ itr->x = b->root;
itr->i = 0;
itr->lvl = 0;
- itr->pos = (mtpos_t){ 0, 0 };
- while (itr->node->level > 0) {
+ itr->pos = MTPos(0, 0);
+ while (itr->x->level > 0) {
itr->s[itr->lvl].i = 0;
itr->s[itr->lvl].oldcol = 0;
itr->lvl++;
- itr->node = itr->node->ptr[0];
+ itr->x = itr->x->ptr[0];
}
return true;
}
@@ -669,16 +1303,16 @@ bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
{
if (b->n_keys == 0) {
- itr->node = NULL;
+ itr->x = NULL;
return false;
}
- itr->pos = (mtpos_t){ 0, 0 };
- itr->node = b->root;
+ itr->pos = MTPos(0, 0);
+ itr->x = b->root;
itr->lvl = 0;
while (true) {
- itr->i = itr->node->n;
+ itr->i = itr->x->n;
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
break;
}
@@ -686,63 +1320,71 @@ int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
itr->s[itr->lvl].oldcol = itr->pos.col;
assert(itr->i > 0);
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
- itr->node = itr->node->ptr[itr->i];
+ itr->x = itr->x->ptr[itr->i];
itr->lvl++;
}
itr->i--;
return true;
}
-// TODO(bfredl): static inline
bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr)
{
- return marktree_itr_next_skip(b, itr, false, NULL);
+ return marktree_itr_next_skip(b, itr, false, false, NULL);
}
-static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mtpos_t oldbase[])
+static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, bool preload,
+ MTPos oldbase[])
{
- if (!itr->node) {
+ if (!itr->x) {
return false;
}
itr->i++;
- if (itr->node->level == 0 || skip) {
- if (itr->i < itr->node->n) {
+ if (itr->x->level == 0 || skip) {
+ if (preload && itr->x->level == 0 && skip) {
+ // skip rest of this leaf node
+ itr->i = itr->x->n;
+ } else if (itr->i < itr->x->n) {
// TODO(bfredl): this is the common case,
// and could be handled by inline wrapper
return true;
}
// we ran out of non-internal keys. Go up until we find an internal key
- while (itr->i >= itr->node->n) {
- itr->node = itr->node->parent;
- if (itr->node == NULL) {
+ while (itr->i >= itr->x->n) {
+ itr->x = itr->x->parent;
+ if (itr->x == NULL) {
return false;
}
itr->lvl--;
itr->i = itr->s[itr->lvl].i;
if (itr->i > 0) {
- itr->pos.row -= itr->node->key[itr->i - 1].pos.row;
+ itr->pos.row -= itr->x->key[itr->i - 1].pos.row;
itr->pos.col = itr->s[itr->lvl].oldcol;
}
}
} else {
// we stood at an "internal" key. Go down to the first non-internal
// key after it.
- while (itr->node->level > 0) {
+ while (itr->x->level > 0) {
// internal key, there is always a child after
if (itr->i > 0) {
itr->s[itr->lvl].oldcol = itr->pos.col;
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
}
if (oldbase && itr->i == 0) {
oldbase[itr->lvl + 1] = oldbase[itr->lvl];
}
itr->s[itr->lvl].i = itr->i;
- assert(itr->node->ptr[itr->i]->parent == itr->node);
- itr->node = itr->node->ptr[itr->i];
- itr->i = 0;
+ assert(itr->x->ptr[itr->i]->parent == itr->x);
itr->lvl++;
+ itr->x = itr->x->ptr[itr->i];
+ if (preload && itr->x->level) {
+ itr->i = -1;
+ break;
+ } else {
+ itr->i = 0;
+ }
}
}
return true;
@@ -750,10 +1392,10 @@ static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mt
bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
{
- if (!itr->node) {
+ if (!itr->x) {
return false;
}
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
itr->i--;
if (itr->i >= 0) {
// TODO(bfredl): this is the common case,
@@ -762,30 +1404,30 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
}
// we ran out of non-internal keys. Go up until we find a non-internal key
while (itr->i < 0) {
- itr->node = itr->node->parent;
- if (itr->node == NULL) {
+ itr->x = itr->x->parent;
+ if (itr->x == NULL) {
return false;
}
itr->lvl--;
itr->i = itr->s[itr->lvl].i - 1;
if (itr->i >= 0) {
- itr->pos.row -= itr->node->key[itr->i].pos.row;
+ itr->pos.row -= itr->x->key[itr->i].pos.row;
itr->pos.col = itr->s[itr->lvl].oldcol;
}
}
} else {
// we stood at an "internal" key. Go down to the last non-internal
// key before it.
- while (itr->node->level > 0) {
+ while (itr->x->level > 0) {
// internal key, there is always a child before
if (itr->i > 0) {
itr->s[itr->lvl].oldcol = itr->pos.col;
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
}
itr->s[itr->lvl].i = itr->i;
- assert(itr->node->ptr[itr->i]->parent == itr->node);
- itr->node = itr->node->ptr[itr->i];
- itr->i = itr->node->n;
+ assert(itr->x->ptr[itr->i]->parent == itr->x);
+ itr->x = itr->x->ptr[itr->i];
+ itr->i = itr->x->n;
itr->lvl++;
}
itr->i--;
@@ -793,33 +1435,22 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
return true;
}
-void marktree_itr_rewind(MarkTree *b, MarkTreeIter *itr)
-{
- if (!itr->node) {
- return;
- }
- if (itr->node->level) {
- marktree_itr_prev(b, itr);
- }
- itr->i = 0;
-}
-
bool marktree_itr_node_done(MarkTreeIter *itr)
{
- return !itr->node || itr->i == itr->node->n - 1;
+ return !itr->x || itr->i == itr->x->n - 1;
}
-mtpos_t marktree_itr_pos(MarkTreeIter *itr)
+MTPos marktree_itr_pos(MarkTreeIter *itr)
{
- mtpos_t pos = rawkey(itr).pos;
+ MTPos pos = rawkey(itr).pos;
unrelative(itr->pos, &pos);
return pos;
}
-mtkey_t marktree_itr_current(MarkTreeIter *itr)
+MTKey marktree_itr_current(MarkTreeIter *itr)
{
- if (itr->node) {
- mtkey_t key = rawkey(itr);
+ if (itr->x) {
+ MTKey key = rawkey(itr);
key.pos = marktree_itr_pos(itr);
return key;
}
@@ -831,47 +1462,198 @@ static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2)
return (&rawkey(itr1) == &rawkey(itr2));
}
-static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2)
+/// Get all marks which overlaps the position (row,col)
+///
+/// After calling this function, use marktree_itr_step_overlap to step through
+/// one overlapping mark at a time, until it returns false
+///
+/// NOTE: It's possible to get all marks which overlaps a region (row,col) to (row_end,col_end)
+/// To do this, first call marktree_itr_get_overlap with the start position and
+/// keep calling marktree_itr_step_overlap until it returns false.
+/// After this, as a second loop, keep calling the marktree_itr_next() until
+/// the iterator is invalid or reaches past (row_end, col_end). In this loop,
+/// consider all "start" marks (and unpaired marks if relevant), but skip over
+/// all "end" marks, using mt_end(mark).
+///
+/// @return false if we already know no marks can be found
+/// even if "true" the first call to marktree_itr_step_overlap
+/// could return false
+bool marktree_itr_get_overlap(MarkTree *b, int row, int col, MarkTreeIter *itr)
+{
+ if (b->n_keys == 0) {
+ itr->x = NULL;
+ return false;
+ }
+
+ itr->x = b->root;
+ itr->i = -1;
+ itr->lvl = 0;
+ itr->pos = MTPos(0, 0);
+ itr->intersect_pos = MTPos(row, col);
+ // intersect_pos but will be adjusted relative itr->x
+ itr->intersect_pos_x = MTPos(row, col);
+ itr->intersect_idx = 0;
+ return true;
+}
+
+static inline MTPair pair_from(MTKey start, MTKey end)
+{
+ return (MTPair){ .start = start, .end_pos = end.pos, .end_right_gravity = mt_right(end) };
+}
+
+/// Step through all overlapping pairs at a position.
+///
+/// This function must only be used with an iterator from |marktree_itr_step_overlap|
+///
+/// @return true if a valid pair was found (returned as `pair`)
+/// When all overlapping mark pairs have been found, false will be returned. `itr`
+/// is then valid as an ordinary iterator at the (row, col) position specified in
+/// marktree_itr_step_overlap
+bool marktree_itr_step_overlap(MarkTree *b, MarkTreeIter *itr, MTPair *pair)
+{
+ // phase one: we start at the root node and step inwards towards itr->intersect_pos
+ // (the position queried in marktree_itr_get_overlap)
+ //
+ // For each node (ancestor node to the node containing the sought position)
+ // we return all intersecting intervals, one at a time
+ while (itr->i == -1) {
+ if (itr->intersect_idx < kv_size(itr->x->intersect)) {
+ uint64_t id = kv_A(itr->x->intersect, itr->intersect_idx++);
+ *pair = pair_from(marktree_lookup(b, id, NULL),
+ marktree_lookup(b, id|MARKTREE_END_FLAG, NULL));
+ return true;
+ }
+
+ if (itr->x->level == 0) {
+ itr->s[itr->lvl].i = itr->i = 0;
+ break;
+ }
+
+ MTKey k = { .pos = itr->intersect_pos_x, .flags = 0 };
+ itr->i = marktree_getp_aux(itr->x, k, 0) + 1;
+
+ itr->s[itr->lvl].i = itr->i;
+ itr->s[itr->lvl].oldcol = itr->pos.col;
+
+ if (itr->i > 0) {
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
+ relative(itr->x->key[itr->i - 1].pos, &itr->intersect_pos_x);
+ }
+ itr->x = itr->x->ptr[itr->i];
+ itr->lvl++;
+ itr->i = -1;
+ itr->intersect_idx = 0;
+ }
+
+ // phase two: we now need to handle the node found at itr->intersect_pos
+ // first consider all start nodes in the node before this position.
+ while (itr->i < itr->x->n && pos_less(rawkey(itr).pos, itr->intersect_pos_x)) {
+ MTKey k = itr->x->key[itr->i++];
+ itr->s[itr->lvl].i = itr->i;
+ if (mt_start(k)) {
+ MTKey end = marktree_lookup(b, mt_lookup_id(k.ns, k.id, true), NULL);
+ if (pos_less(end.pos, itr->intersect_pos)) {
+ continue;
+ }
+
+ unrelative(itr->pos, &k.pos);
+ *pair = pair_from(k, end);
+ return true; // it's a start!
+ }
+ }
+
+ // phase 2B: We also need to step to the end of this node and consider all end marks, which
+ // might end an interval overlapping itr->intersect_pos
+ while (itr->i < itr->x->n) {
+ MTKey k = itr->x->key[itr->i++];
+ if (mt_end(k)) {
+ uint64_t id = mt_lookup_id(k.ns, k.id, false);
+ if (id2node(b, id) == itr->x) {
+ continue;
+ }
+ unrelative(itr->pos, &k.pos);
+ MTKey start = marktree_lookup(b, id, NULL);
+ if (pos_less(itr->intersect_pos, start.pos)) {
+ continue;
+ }
+ *pair = pair_from(start, k);
+ return true; // end of a range which began before us!
+ }
+ }
+
+ // when returning false, get back to the queried position, to ensure the caller
+ // can keep using it as an ordinary iterator at the queried position. The docstring
+ // for marktree_itr_get_overlap explains how this is useful.
+ itr->i = itr->s[itr->lvl].i;
+ assert(itr->i >= 0);
+ if (itr->i >= itr->x->n) {
+ marktree_itr_next(b, itr);
+ }
+
+ // either on or after the intersected position, bail out
+ return false;
+}
+
+static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, DamageList *damage)
{
- mtkey_t key1 = rawkey(itr1);
- mtkey_t key2 = rawkey(itr2);
+ if (itr1->x != itr2->x) {
+ if (mt_paired(rawkey(itr1))) {
+ kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr1)), itr1->x, itr2->x,
+ itr1->i, itr2->i }));
+ }
+ if (mt_paired(rawkey(itr2))) {
+ kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x,
+ itr2->i, itr1->i }));
+ }
+ }
+
+ MTKey key1 = rawkey(itr1);
+ MTKey key2 = rawkey(itr2);
rawkey(itr1) = key2;
rawkey(itr1).pos = key1.pos;
rawkey(itr2) = key1;
rawkey(itr2).pos = key2.pos;
+ refkey(b, itr1->x, itr1->i);
+ refkey(b, itr2->x, itr2->i);
+}
+
+static int damage_cmp(const void *s1, const void *s2)
+{
+ Damage *d1 = (Damage *)s1, *d2 = (Damage *)s2;
+ assert(d1->id != d2->id);
+ return d1->id > d2->id;
}
bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_extent_line,
int old_extent_col, int new_extent_line, int new_extent_col)
{
- mtpos_t start = { start_line, start_col };
- mtpos_t old_extent = { old_extent_line, old_extent_col };
- mtpos_t new_extent = { new_extent_line, new_extent_col };
+ MTPos start = { start_line, start_col };
+ MTPos old_extent = { old_extent_line, old_extent_col };
+ MTPos new_extent = { new_extent_line, new_extent_col };
bool may_delete = (old_extent.row != 0 || old_extent.col != 0);
bool same_line = old_extent.row == 0 && new_extent.row == 0;
unrelative(start, &old_extent);
unrelative(start, &new_extent);
- MarkTreeIter itr[1] = { 0 };
- MarkTreeIter enditr[1] = { 0 };
+ MarkTreeIter itr[1] = { 0 }, enditr[1] = { 0 };
- mtpos_t oldbase[MT_MAX_DEPTH] = { 0 };
+ MTPos oldbase[MT_MAX_DEPTH] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, oldbase);
- if (!itr->node) {
+ if (!itr->x) {
// den e FÄRDIG
return false;
}
- mtpos_t delta = { new_extent.row - old_extent.row,
- new_extent.col - old_extent.col };
+ MTPos delta = { new_extent.row - old_extent.row,
+ new_extent.col - old_extent.col };
if (may_delete) {
- mtpos_t ipos = marktree_itr_pos(itr);
+ MTPos ipos = marktree_itr_pos(itr);
if (!pos_leq(old_extent, ipos)
|| (old_extent.row == ipos.row && old_extent.col == ipos.col
&& !mt_right(rawkey(itr)))) {
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
- assert(enditr->node);
+ assert(enditr->x);
// "assert" (itr <= enditr)
} else {
may_delete = false;
@@ -880,14 +1662,16 @@ bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_ext
bool past_right = false;
bool moved = false;
+ DamageList damage;
+ kvi_init(damage);
// Follow the general strategy of messing things up and fix them later
// "oldbase" carries the information needed to calculate old position of
// children.
if (may_delete) {
- while (itr->node && !past_right) {
- mtpos_t loc_start = start;
- mtpos_t loc_old = old_extent;
+ while (itr->x && !past_right) {
+ MTPos loc_start = start;
+ MTPos loc_old = old_extent;
relative(itr->pos, &loc_start);
relative(oldbase[itr->lvl], &loc_old);
@@ -905,9 +1689,7 @@ continue_same_node:
marktree_itr_prev(b, enditr);
}
if (!mt_right(rawkey(enditr))) {
- itr_swap(itr, enditr);
- refkey(b, itr->node, itr->i);
- refkey(b, enditr->node, enditr->i);
+ swap_keys(b, itr, enditr, &damage);
} else {
past_right = true; // NOLINT
(void)past_right;
@@ -921,14 +1703,14 @@ continue_same_node:
}
moved = true;
- if (itr->node->level) {
+ if (itr->x->level) {
oldbase[itr->lvl + 1] = rawkey(itr).pos;
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
rawkey(itr).pos = loc_start;
- marktree_itr_next_skip(b, itr, false, oldbase);
+ marktree_itr_next_skip(b, itr, false, false, oldbase);
} else {
rawkey(itr).pos = loc_start;
- if (itr->i < itr->node->n - 1) {
+ if (itr->i < itr->x->n - 1) {
itr->i++;
if (!past_right) {
goto continue_same_node;
@@ -938,10 +1720,10 @@ continue_same_node:
}
}
}
- while (itr->node) {
- mtpos_t loc_new = new_extent;
+ while (itr->x) {
+ MTPos loc_new = new_extent;
relative(itr->pos, &loc_new);
- mtpos_t limit = old_extent;
+ MTPos limit = old_extent;
relative(oldbase[itr->lvl], &limit);
@@ -951,16 +1733,16 @@ past_continue_same_node:
break;
}
- mtpos_t oldpos = rawkey(itr).pos;
+ MTPos oldpos = rawkey(itr).pos;
rawkey(itr).pos = loc_new;
moved = true;
- if (itr->node->level) {
+ if (itr->x->level) {
oldbase[itr->lvl + 1] = oldpos;
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
- marktree_itr_next_skip(b, itr, false, oldbase);
+ marktree_itr_next_skip(b, itr, false, false, oldbase);
} else {
- if (itr->i < itr->node->n - 1) {
+ if (itr->i < itr->x->n - 1) {
itr->i++;
goto past_continue_same_node;
} else {
@@ -970,7 +1752,7 @@ past_continue_same_node:
}
}
- while (itr->node) {
+ while (itr->x) {
unrelative(oldbase[itr->lvl], &rawkey(itr).pos);
int realrow = rawkey(itr).pos.row;
assert(realrow >= old_extent.row);
@@ -978,7 +1760,6 @@ past_continue_same_node:
if (realrow == old_extent.row) {
if (delta.col) {
rawkey(itr).pos.col += delta.col;
- moved = true;
}
} else {
if (same_line) {
@@ -994,22 +1775,78 @@ past_continue_same_node:
if (done) {
break;
}
- marktree_itr_next_skip(b, itr, true, NULL);
+ marktree_itr_next_skip(b, itr, true, false, NULL);
+ }
+
+ if (kv_size(damage)) {
+ // TODO(bfredl): a full sort is not really needed. we just need a "start" node to find
+ // its corresponding "end" node. Set up some dedicated hash for this later (c.f. the
+ // "grow only" variant of khash_t branch)
+ qsort((void *)&kv_A(damage, 0), kv_size(damage), sizeof(kv_A(damage, 0)),
+ damage_cmp);
+
+ for (size_t i = 0; i < kv_size(damage); i++) {
+ Damage d = kv_A(damage, i);
+ if (!(d.id & MARKTREE_END_FLAG)) { // start
+ if (i + 1 < kv_size(damage) && kv_A(damage, i + 1).id == (d.id | MARKTREE_END_FLAG)) {
+ Damage d2 = kv_A(damage, i + 1);
+
+ // pair
+ marktree_itr_set_node(b, itr, d.old, d.old_i);
+ marktree_itr_set_node(b, enditr, d2.old, d2.old_i);
+ marktree_intersect_pair(b, d.id, itr, enditr, true);
+ marktree_itr_set_node(b, itr, d.new, d.new_i);
+ marktree_itr_set_node(b, enditr, d2.new, d2.new_i);
+ marktree_intersect_pair(b, d.id, itr, enditr, false);
+
+ i++; // consume two items
+ continue;
+ }
+
+ // d is lone start, end didn't move
+ MarkTreeIter endpos[1];
+ marktree_lookup(b, d.id | MARKTREE_END_FLAG, endpos);
+ if (endpos->x) {
+ marktree_itr_set_node(b, itr, d.old, d.old_i);
+ *enditr = *endpos;
+ marktree_intersect_pair(b, d.id, itr, enditr, true);
+ marktree_itr_set_node(b, itr, d.new, d.new_i);
+ *enditr = *endpos;
+ marktree_intersect_pair(b, d.id, itr, enditr, false);
+ }
+ } else {
+ // d is lone end, start didn't move
+ MarkTreeIter startpos[1];
+ uint64_t start_id = d.id & ~MARKTREE_END_FLAG;
+
+ marktree_lookup(b, start_id, startpos);
+ if (startpos->x) {
+ *itr = *startpos;
+ marktree_itr_set_node(b, enditr, d.old, d.old_i);
+ marktree_intersect_pair(b, start_id, itr, enditr, true);
+ *itr = *startpos;
+ marktree_itr_set_node(b, enditr, d.new, d.new_i);
+ marktree_intersect_pair(b, start_id, itr, enditr, false);
+ }
+ }
+ }
}
+ kvi_destroy(damage);
+
return moved;
}
void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int extent_row,
colnr_T extent_col, int new_row, colnr_T new_col)
{
- mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col };
- mtpos_t end = size;
+ MTPos start = { start_row, start_col }, size = { extent_row, extent_col };
+ MTPos end = size;
unrelative(start, &end);
MarkTreeIter itr[1] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, NULL);
- kvec_t(mtkey_t) saved = KV_INITIAL_VALUE;
- while (itr->node) {
- mtkey_t k = marktree_itr_current(itr);
+ kvec_t(MTKey) saved = KV_INITIAL_VALUE;
+ while (itr->x) {
+ MTKey k = marktree_itr_current(itr);
if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col
&& mt_right(k))) {
break;
@@ -1020,57 +1857,101 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
}
marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0);
- mtpos_t new = { new_row, new_col };
+ MTPos new = { new_row, new_col };
marktree_splice(b, new.row, new.col,
0, 0, size.row, size.col);
for (size_t i = 0; i < kv_size(saved); i++) {
- mtkey_t item = kv_A(saved, i);
+ MTKey item = kv_A(saved, i);
unrelative(new, &item.pos);
marktree_put_key(b, item);
+ if (mt_paired(item)) {
+ // other end might be later in `saved`, this will safely bail out then
+ marktree_restore_pair(b, item);
+ }
}
kv_destroy(saved);
}
/// @param itr OPTIONAL. set itr to pos.
-mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
+MTKey marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
{
return marktree_lookup(b, mt_lookup_id(ns, id, end), itr);
}
+static uint64_t pseudo_index(MTNode *x, int i)
+{
+ int off = MT_LOG2_BRANCH * x->level;
+ uint64_t index = 0;
+
+ while (x) {
+ index |= (uint64_t)(i + 1) << off;
+ off += MT_LOG2_BRANCH;
+ i = x->p_idx;
+ x = x->parent;
+ }
+
+ return index;
+}
+
+/// @param itr OPTIONAL. set itr to pos.
+/// if sloppy, two keys at the same _leaf_ node has the same index
+static uint64_t pseudo_index_for_id(MarkTree *b, uint64_t id, bool sloppy)
+{
+ MTNode *n = id2node(b, id);
+ if (n == NULL) {
+ return 0; // a valid pseudo-index is never zero!
+ }
+
+ int i = 0;
+ if (n->level || !sloppy) {
+ for (i = 0; i < n->n; i++) {
+ if (mt_lookup_key(n->key[i]) == id) {
+ break;
+ }
+ }
+ assert(i < n->n);
+ if (n->level) {
+ i += 1; // internal key i comes after ptr[i]
+ }
+ }
+
+ return pseudo_index(n, i);
+}
+
/// @param itr OPTIONAL. set itr to pos.
-mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
+MTKey marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
{
- mtnode_t *n = pmap_get(uint64_t)(b->id2node, id);
+ MTNode *n = id2node(b, id);
if (n == NULL) {
if (itr) {
- itr->node = NULL;
+ itr->x = NULL;
}
return MT_INVALID_KEY;
}
int i = 0;
for (i = 0; i < n->n; i++) {
if (mt_lookup_key(n->key[i]) == id) {
- goto found;
+ return marktree_itr_set_node(b, itr, n, i);
}
}
+
abort();
-found: {}
- mtkey_t key = n->key[i];
+}
+
+MTKey marktree_itr_set_node(MarkTree *b, MarkTreeIter *itr, MTNode *n, int i)
+{
+ MTKey key = n->key[i];
if (itr) {
itr->i = i;
- itr->node = n;
+ itr->x = n;
itr->lvl = b->root->level - n->level;
}
while (n->parent != NULL) {
- mtnode_t *p = n->parent;
- for (i = 0; i < p->n + 1; i++) {
- if (p->ptr[i] == n) {
- goto found_node;
- }
- }
- abort();
-found_node:
+ MTNode *p = n->parent;
+ i = n->p_idx;
+ assert(p->ptr[i] == n);
+
if (itr) {
itr->s[b->root->level - p->level].i = i;
}
@@ -1085,14 +1966,14 @@ found_node:
return key;
}
-mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+MTPos marktree_get_altpos(MarkTree *b, MTKey mark, MarkTreeIter *itr)
{
return marktree_get_alt(b, mark, itr).pos;
}
-mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+MTKey marktree_get_alt(MarkTree *b, MTKey mark, MarkTreeIter *itr)
{
- mtkey_t end = MT_INVALID_KEY;
+ MTKey end = MT_INVALID_KEY;
if (mt_paired(mark)) {
end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr);
}
@@ -1101,8 +1982,8 @@ mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
{
- itr->pos = (mtpos_t){ 0, 0 };
- mtnode_t *x = b->root;
+ itr->pos = (MTPos){ 0, 0 };
+ MTNode *x = b->root;
for (int lvl = 0; lvl < itr->lvl; lvl++) {
itr->s[lvl].oldcol = itr->pos.col;
int i = itr->s[lvl].i;
@@ -1112,23 +1993,36 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
assert(x->level);
x = x->ptr[i];
}
- assert(x == itr->node);
+ assert(x == itr->x);
}
// for unit test
-void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity)
+void marktree_put_test(MarkTree *b, uint32_t ns, uint32_t id, int row, int col, bool right_gravity,
+ int end_row, int end_col, bool end_right)
{
- mtkey_t key = { { row, col }, UINT32_MAX, id, 0,
- mt_flags(right_gravity, 0), 0, NULL };
- marktree_put(b, key, -1, -1, false);
+ MTKey key = { { row, col }, ns, id, 0,
+ mt_flags(right_gravity, 0), 0, NULL };
+ marktree_put(b, key, end_row, end_col, end_right);
}
// for unit test
-bool mt_right_test(mtkey_t key)
+bool mt_right_test(MTKey key)
{
return mt_right(key);
}
+// for unit test
+void marktree_del_pair_test(MarkTree *b, uint32_t ns, uint32_t id)
+{
+ MarkTreeIter itr[1];
+ marktree_lookup_ns(b, ns, id, false, itr);
+
+ uint64_t other = marktree_del_itr(b, itr, false);
+ assert(other);
+ marktree_lookup(b, other, itr);
+ marktree_del_itr(b, itr, false);
+}
+
void marktree_check(MarkTree *b)
{
#ifndef NDEBUG
@@ -1139,7 +2033,7 @@ void marktree_check(MarkTree *b)
return;
}
- mtpos_t dummy;
+ MTPos dummy;
bool last_right = false;
size_t nkeys = marktree_check_node(b, b->root, &dummy, &last_right);
assert(b->n_keys == nkeys);
@@ -1151,7 +2045,7 @@ void marktree_check(MarkTree *b)
}
#ifndef NDEBUG
-size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right)
+size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right)
{
assert(x->n <= 2 * T - 1);
// TODO(bfredl): too strict if checking "in repair" post-delete tree.
@@ -1162,7 +2056,7 @@ size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_r
if (x->level) {
n_keys += marktree_check_node(b, x->ptr[i], last, last_right);
} else {
- *last = (mtpos_t) { 0, 0 };
+ *last = (MTPos) { 0, 0 };
}
if (i > 0) {
unrelative(x->key[i - 1].pos, last);
@@ -1182,6 +2076,7 @@ size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_r
for (int i = 0; i < x->n + 1; i++) {
assert(x->ptr[i]->parent == x);
+ assert(x->ptr[i]->p_idx == i);
assert(x->ptr[i]->level == x->level - 1);
// PARANOIA: check no double node ref
for (int j = 0; j < i; j++) {
@@ -1193,34 +2088,221 @@ size_t marktree_check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_r
}
return n_keys;
}
+
+bool marktree_check_intersections(MarkTree *b)
+{
+ if (!b->root) {
+ return true;
+ }
+ PMap(ptr_t) checked = MAP_INIT;
+
+ // 1. move x->intersect to checked[x] and reinit x->intersect
+ mt_recurse_nodes(b->root, &checked);
+
+ // 2. iterate over all marks. for each START mark of a pair,
+ // intersect the nodes between the pair
+ MarkTreeIter itr[1];
+ marktree_itr_first(b, itr);
+ while (true) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0) {
+ break;
+ }
+
+ if (mt_start(mark)) {
+ MarkTreeIter start_itr[1];
+ MarkTreeIter end_itr[1];
+ uint64_t end_id = mt_lookup_id(mark.ns, mark.id, true);
+ MTKey k = marktree_lookup(b, end_id, end_itr);
+ if (k.pos.row >= 0) {
+ *start_itr = *itr;
+ marktree_intersect_pair(b, mt_lookup_key(mark), start_itr, end_itr, false);
+ }
+ }
+
+ marktree_itr_next(b, itr);
+ }
+
+ // 3. for each node check if the recreated intersection
+ // matches the old checked[x] intersection.
+ bool status = mt_recurse_nodes_compare(b->root, &checked);
+
+ uint64_t *val;
+ map_foreach_value(&checked, val, {
+ xfree(val);
+ });
+ map_destroy(ptr_t, &checked);
+
+ return status;
+}
+
+void mt_recurse_nodes(MTNode *x, PMap(ptr_t) *checked)
+{
+ if (kv_size(x->intersect)) {
+ kvi_push(x->intersect, (uint64_t)-1); // sentinel
+ uint64_t *val;
+ if (x->intersect.items == x->intersect.init_array) {
+ val = xmemdup(x->intersect.items, x->intersect.size * sizeof(*x->intersect.items));
+ } else {
+ val = x->intersect.items;
+ }
+ pmap_put(ptr_t)(checked, x, val);
+ kvi_init(x->intersect);
+ }
+
+ if (x->level) {
+ for (int i = 0; i < x->n + 1; i++) {
+ mt_recurse_nodes(x->ptr[i], checked);
+ }
+ }
+}
+
+bool mt_recurse_nodes_compare(MTNode *x, PMap(ptr_t) *checked)
+{
+ uint64_t *ref = pmap_get(ptr_t)(checked, x);
+ if (ref != NULL) {
+ for (size_t i = 0;; i++) {
+ if (ref[i] == (uint64_t)-1) {
+ if (i != kv_size(x->intersect)) {
+ return false;
+ }
+
+ break;
+ } else {
+ if (kv_size(x->intersect) <= i || ref[i] != kv_A(x->intersect, i)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ if (kv_size(x->intersect)) {
+ return false;
+ }
+ }
+
+ if (x->level) {
+ for (int i = 0; i < x->n + 1; i++) {
+ if (!mt_recurse_nodes_compare(x->ptr[i], checked)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
#endif
-char *mt_inspect_rec(MarkTree *b)
+// TODO(bfredl): kv_print
+#define GA_PUT(x) ga_concat(ga, (char *)(x))
+#define GA_PRINT(fmt, ...) snprintf(buf, sizeof(buf), fmt, __VA_ARGS__); \
+ GA_PUT(buf);
+
+String mt_inspect(MarkTree *b, bool keys, bool dot)
{
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
- mtpos_t p = { 0, 0 };
- mt_inspect_node(b, &ga, b->root, p);
- return ga.ga_data;
+ garray_T ga[1];
+ ga_init(ga, (int)sizeof(char), 80);
+ MTPos p = { 0, 0 };
+ if (b->root) {
+ if (dot) {
+ GA_PUT("digraph D {\n\n");
+ mt_inspect_dotfile_node(b, ga, b->root, p, NULL);
+ GA_PUT("\n}");
+ } else {
+ mt_inspect_node(b, ga, keys, b->root, p);
+ }
+ }
+ return ga_take_string(ga);
}
-void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off)
+void mt_inspect_node(MarkTree *b, garray_T *ga, bool keys, MTNode *n, MTPos off)
{
static char buf[1024];
- ga_concat(ga, "[");
+ GA_PUT("[");
+ if (keys && kv_size(n->intersect)) {
+ for (size_t i = 0; i < kv_size(n->intersect); i++) {
+ GA_PUT(i == 0 ? "{" : ";");
+ // GA_PRINT("%"PRIu64, kv_A(n->intersect, i));
+ GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i)));
+ }
+ GA_PUT("},");
+ }
if (n->level) {
- mt_inspect_node(b, ga, n->ptr[0], off);
+ mt_inspect_node(b, ga, keys, n->ptr[0], off);
}
for (int i = 0; i < n->n; i++) {
- mtpos_t p = n->key[i].pos;
+ MTPos p = n->key[i].pos;
unrelative(off, &p);
- snprintf(buf, sizeof(buf), "%d/%d", p.row, p.col);
- ga_concat(ga, buf);
+ GA_PRINT("%d/%d", p.row, p.col);
+ if (keys) {
+ MTKey key = n->key[i];
+ GA_PUT(":");
+ if (mt_start(key)) {
+ GA_PUT("<");
+ }
+ // GA_PRINT("%"PRIu64, mt_lookup_id(key.ns, key.id, false));
+ GA_PRINT("%" PRIu32, key.id);
+ if (mt_end(key)) {
+ GA_PUT(">");
+ }
+ }
if (n->level) {
- mt_inspect_node(b, ga, n->ptr[i + 1], p);
+ mt_inspect_node(b, ga, keys, n->ptr[i + 1], p);
} else {
ga_concat(ga, ",");
}
}
ga_concat(ga, "]");
}
+
+void mt_inspect_dotfile_node(MarkTree *b, garray_T *ga, MTNode *n, MTPos off, char *parent)
+{
+ static char buf[1024];
+ char namebuf[64];
+ if (parent != NULL) {
+ snprintf(namebuf, sizeof namebuf, "%s_%c%d", parent, 'a' + n->level, n->p_idx);
+ } else {
+ snprintf(namebuf, sizeof namebuf, "Node");
+ }
+
+ GA_PRINT(" %s[shape=plaintext, label=<\n", namebuf);
+ GA_PUT(" <table border='0' cellborder='1' cellspacing='0'>\n");
+ if (kv_size(n->intersect)) {
+ GA_PUT(" <tr><td>");
+ for (size_t i = 0; i < kv_size(n->intersect); i++) {
+ if (i > 0) {
+ GA_PUT(", ");
+ }
+ GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i)));
+ }
+ GA_PUT("</td></tr>\n");
+ }
+
+ GA_PUT(" <tr><td>");
+ for (int i = 0; i < n->n; i++) {
+ MTKey k = n->key[i];
+ if (i > 0) {
+ GA_PUT(", ");
+ }
+ GA_PRINT("%d", k.id);
+ if (mt_paired(k)) {
+ GA_PUT(mt_end(k) ? "e" : "s");
+ }
+ }
+ GA_PUT("</td></tr>\n");
+ GA_PUT(" </table>\n");
+ GA_PUT(">];\n");
+ if (parent) {
+ GA_PRINT(" %s -> %s\n", parent, namebuf);
+ }
+ if (n->level) {
+ mt_inspect_dotfile_node(b, ga, n->ptr[0], off, namebuf);
+ }
+ for (int i = 0; i < n->n; i++) {
+ MTPos p = n->key[i].pos;
+ unrelative(off, &p);
+ if (n->level) {
+ mt_inspect_dotfile_node(b, ga, n->ptr[i + 1], p, namebuf);
+ }
+ }
+}