diff --git a/lib/remote-bitfield.js b/lib/remote-bitfield.js index 24b3e0f5..70c1d188 100644 --- a/lib/remote-bitfield.js +++ b/lib/remote-bitfield.js @@ -51,11 +51,16 @@ class RemoteBitfieldPage { insert(start, bitfield) { this.bitfield.set(bitfield, start / 32) - this.segment.refresh() } clear(start, bitfield) { quickbit.clear(this.bitfield, { field: bitfield, offset: start }) + + // The sparse index is chunked in 128-bit ranges, so update the chunks touched by this clear. + let i = Math.floor(start / 128) + const n = i + Math.ceil((bitfield.byteLength * 8) / 128) + + while (i <= n) this.tree.update(this.offset * 8 + i++ * 128) } } @@ -283,6 +288,7 @@ module.exports = class RemoteBitfield { let j = start & (BITS_PER_PAGE - 1) let i = (start - j) / BITS_PER_PAGE + const refresh = new Set() while (length > 0) { let p = this._pages.get(i) @@ -299,6 +305,7 @@ module.exports = class RemoteBitfield { const range = end - j p.insert(j, bitfield.subarray(0, range / 32)) + refresh.add(p.segment) bitfield = bitfield.subarray(range / 32) @@ -307,6 +314,8 @@ module.exports = class RemoteBitfield { length -= range } + for (const segment of refresh) segment.refresh() + return true } diff --git a/test/remote-bitfield.js b/test/remote-bitfield.js index f1d84fd8..819fdd84 100644 --- a/test/remote-bitfield.js +++ b/test/remote-bitfield.js @@ -1,5 +1,6 @@ const test = require('brittle') const b4a = require('b4a') +const quickbit = require('../lib/compat').quickbit const RemoteBitfield = require('../lib/remote-bitfield') const { create, replicate } = require('./helpers') @@ -27,6 +28,47 @@ test('remote bitfield - set range to false', function (t) { t.is(b.findFirst(true, 0), -1) }) +test('remote bitfield - batch refreshes insert indexes once per segment', function (t) { + const from = quickbit.Index.from + let sparseIndexes = 0 + + quickbit.Index.from = function (fieldOrChunks, byteLength) { + if (Array.isArray(fieldOrChunks)) sparseIndexes++ + return from.call(this, fieldOrChunks, byteLength) + } + + t.teardown(function () { + quickbit.Index.from = from + }) + + const b = new RemoteBitfield() + const bitfield = new Uint32Array(RemoteBitfield.BITS_PER_SEGMENT / 32) + bitfield.fill(0xffffffff) + + b.insert(0, bitfield) + + t.is(sparseIndexes, 2) // one segment init, one refresh + t.is(b.findFirst(true, 0), 0) + t.is(b.findFirst(false, 0), RemoteBitfield.BITS_PER_SEGMENT) +}) + +test('remote bitfield - clear refreshes indexes for firstUnset', function (t) { + const b = new RemoteBitfield() + const bitfield = new Uint32Array(RemoteBitfield.BITS_PER_PAGE / 32) + bitfield.fill(0xffffffff) + + b.insert(0, bitfield) + + const half = new Uint32Array(RemoteBitfield.BITS_PER_PAGE / 32) + half.fill(0xffffffff, 0, half.length / 2) + + b.clear(0, half) + + t.is(b.get(0), false) + t.is(b.findFirst(false, 0), 0) + t.is(b.findFirst(true, 0), RemoteBitfield.BITS_PER_PAGE / 2) +}) + test('set last bits in segment and findFirst', function (t) { const b = new RemoteBitfield()