diff --git a/README.md b/README.md index ed23e9e..6dace54 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ As more contacts are added to the "far" k-bucket and it reaches its capacity, it * [kBucket.metadata](#kbucketmetadata) * [kBucket.remove(id)](#kbucketremoveid) * [kBucket.toArray()](#kbuckettoarray) + * [kBucket.toIterable()](#kbuckettoiterable) * [Event 'added'](#event-added) * [Event 'ping'](#event-ping) * [Event 'removed'](#event-removed) @@ -205,6 +206,12 @@ Removes `contact` with the provided `id`. Traverses the tree, putting all the contacts into one array. +#### kBucket.toIterable() + + * Return: _Iterable_ All of the contacts in the tree, as an iterable + +Traverses the tree, yielding contacts as they are encountered. + #### kBucket._determineNode(node, id [, bitIndex = 0]) _**CAUTION: reserved for internal use**_ diff --git a/index.js b/index.js index f41d4fe..7f6b017 100644 --- a/index.js +++ b/index.js @@ -399,6 +399,22 @@ class KBucket extends EventEmitter { return result } + /** + * Similar to `toArray()` but instead of buffering everything up into an + * array before returning it, yield contacts as they are encountered while + * walking the tree + */ + * toIterable () { + for (const nodes = [ this.root ]; nodes.length > 0;) { + const node = nodes.pop() + if (node.contacts === null) { + nodes.push(node.right, node.left) + } else { + yield * node.contacts + } + } + } + /** * Updates the contact selected by the arbiter. * If the selection is our old contact and the candidate is some new contact diff --git a/test/toIterable.js b/test/toIterable.js new file mode 100644 index 0000000..011998e --- /dev/null +++ b/test/toIterable.js @@ -0,0 +1,41 @@ +'use strict' +var test = require('tape') +var KBucket = require('../') + +function collect (iterable) { + var out = [] + + for (const thing of iterable) { + out.push(thing) + } + + return out +} + +test('toIterable should return empty iterable if no contacts', function (t) { + var kBucket = new KBucket() + t.same(collect(kBucket.toIterable()).length, 0) + t.end() +}) + +test('toIterable should return all contacts in an iterable arranged from low to high buckets', function (t) { + t.plan(22) + var kBucket = new KBucket({ localNodeId: Buffer.from([ 0x00, 0x00 ]) }) + var expectedIds = [] + for (var i = 0; i < kBucket.numberOfNodesPerKBucket; ++i) { + kBucket.add({ id: Buffer.from([ 0x80, i ]) }) // make sure all go into "far away" bucket + expectedIds.push(0x80 * 256 + i) + } + // cause a split to happen + kBucket.add({ id: Buffer.from([ 0x00, 0x80, i - 1 ]) }) + // console.log(require('util').inspect(kBucket, {depth: null})) + var contacts = collect(kBucket.toArray()) + // console.log(require('util').inspect(contacts, {depth: null})) + t.same(contacts.length, kBucket.numberOfNodesPerKBucket + 1) + t.same(parseInt(contacts[0].id.toString('hex'), 16), 0x80 * 256 + i - 1) + contacts.shift() // get rid of low bucket contact + for (i = 0; i < kBucket.numberOfNodesPerKBucket; ++i) { + t.same(parseInt(contacts[i].id.toString('hex'), 16), expectedIds[i]) + } + t.end() +})