Skip to content

Commit

Permalink
Refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
rpj committed Jul 19, 2020
1 parent 0ba5d13 commit db82ce6
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 156 deletions.
9 changes: 9 additions & 0 deletions constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const BackingStoreKey = 'backingStore';
const ChunkWidthKey = 'chunkWidth';
const KeyPrefixKey = 'keyPrefix';

module.exports = {
BackingStoreKey,
ChunkWidthKey,
KeyPrefixKey
};
106 changes: 106 additions & 0 deletions impl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const {
BackingStoreKey,
ChunkWidthKey,
KeyPrefixKey
} = require('./constants');

module.exports = class {
constructor(parentFacade) {
this.parent = parentFacade;
}

chunkCoords(x, y) {
const cWidth = this.parent[ChunkWidthKey];
return [Math.floor(Number(x) / cWidth), Math.floor(Number(y) / cWidth)];
}

bitPosition(chunkXY, x, y) {
const cWidth = this.parent[ChunkWidthKey];
return (Number(x) - (chunkXY[0] * cWidth)) + ((Number(y) - (chunkXY[1] * cWidth)) * cWidth);
}

key(bmType, cX, cY) {
return `${this.parent[KeyPrefixKey]}:${bmType}:${cX}:${cY}`;
}

async getSet(bmType, x, y, setVal = undefined) {
const chunk = this.chunkCoords(x, y);
const bitPos = this.bitPosition(chunk, x, y);
const [cX, cY] = chunk;

if (setVal === undefined) {
return this.parent[BackingStoreKey].getbit(this.key(bmType, cX, cY), bitPos);
} else {
return this.parent[BackingStoreKey].setbit(this.key(bmType, cX, cY), bitPos, setVal == true ? 1 : 0);
}
}

async allSetInBounds(bmType, fromX, fromY, toX, toY, strict = false) {
const rowWidth = this.parent[ChunkWidthKey];
const [fcX, fcY] = this.chunkCoords(fromX, fromY);
const [tcX, tcY] = this.chunkCoords(toX, toY);

let retList = [];
let bufferGetter = async (bgX, bgY) => this.parent[BackingStoreKey].getBuffer(this.key(bmType, bgX, bgY));

if (this.parent.isPipelineCapable) {
const plBuffer = {};

bufferGetter = async (bgX, bgY) => {
if (bgX in plBuffer) {
if (bgY in plBuffer[bgX]) {
return plBuffer[bgX][bgY];
}
}
};

const pipeline = this.parent[BackingStoreKey].pipeline();
for (let wcX = fcX; wcX <= tcX; wcX++) {
if (!(wcX in plBuffer)) {
plBuffer[wcX] = {};
}

for (let wcY = fcY; wcY <= tcY; wcY++) {
pipeline.getBuffer(this.key(bmType, wcX, wcY), function (err, result) {
if (err) {
throw new Error(`pipeline.getBuffer: shouldn't happen! ${err}`);
}

plBuffer[wcX][wcY] = result;
});
}
}

await pipeline.exec();
bufferGetter = async (bgX, bgY) => plBuffer[bgX][bgY];
}

for (let wcX = fcX; wcX <= tcX; wcX++) {
for (let wcY = fcY; wcY <= tcY; wcY++) {
const chunkBytes = await bufferGetter(wcX, wcY);

if (!chunkBytes || chunkBytes.length < 1) {
continue;
}

for (let cByte = 0; cByte < chunkBytes.length; cByte++) {
for (let bit = 0; bit < 8; bit++) {
if (chunkBytes[cByte] & (1 << bit)) {
let ix = (wcX * rowWidth) + (7 - bit) + ((cByte % (rowWidth / 8)) * 8);
let iy = ((((7 - bit) + (cByte * 8)) - ix + (wcX * rowWidth)) / rowWidth) + (wcY * rowWidth);
retList.push([ix, iy]);
}
}
}
}
}

// strict includes *only* coordinates within the specified bounding box, otherwise all coordinates
// within the *chunks* intersected by the specified bounding box are returned
if (strict) {
retList = retList.filter(x => x[0] >= fromX && x[1] >= fromY && x[0] <= toX && x[1] <= toY);
}

return retList;
}
};
164 changes: 8 additions & 156 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const BackingStoreKey = 'backingStore';
const ChunkWidthKey = 'chunkWidth';
const KeyPrefixKey = 'keyPrefix';
const { InMemoryStore } = require('./stores');
const SparseBitmapImpl = require('./impl');
const {
BackingStoreKey,
ChunkWidthKey,
KeyPrefixKey
} = require('./constants');

const Defaults = {
[ChunkWidthKey]: 128,
Expand All @@ -16,159 +20,6 @@ const LimitChecks = {
[ChunkWidthKey]: (x) => x >= Defaults.Limits[ChunkWidthKey].min && (x % 8) === 0
};

// a very simple, unoptimized store provided as an example implementation & last-chance default
class InMemoryStore {
constructor() {
this.store = {};
}

getbit(key, bitPosition) {
if (key in this.store) {
const byteIdx = Math.floor(bitPosition / 8);
const innerPos = (bitPosition % 8);
const bitMask = 0x80 >> innerPos;
if (byteIdx in this.store[key]) {
return (this.store[key][byteIdx] & bitMask) >> (7 - innerPos);
}
}

return 0;
}

setbit(key, bitPosition, value) {
if (!(key in this.store)) {
this.store[key] = [];
}

const byteIdx = Math.floor(bitPosition / 8);
const bitMask = 0x80 >> (bitPosition % 8);

// lazily initialize everything up to and including byteIdx, if it isn't already initialized
if (!(byteIdx in this.store[key])) {
for (let bi = 0; bi <= byteIdx; bi++) {
if (!(bi in this.store[key])) {
this.store[key][bi] = 0;
}
}
}

if (value == true) {
this.store[key][byteIdx] |= bitMask;
} else {
this.store[key][byteIdx] &= ~bitMask;
}
}

getBuffer(key) {
if (!(key in this.store)) {
this.store[key] = [];
}

return Buffer.from(this.store[key]);
}
};

class SparseBitmapImpl {
constructor(parentFacade) {
this.parent = parentFacade;
}

chunkCoords(x, y) {
const cWidth = this.parent[ChunkWidthKey];
return [Math.floor(Number(x) / cWidth), Math.floor(Number(y) / cWidth)];
}

bitPosition(chunkXY, x, y) {
const cWidth = this.parent[ChunkWidthKey];
return (Number(x) - (chunkXY[0] * cWidth)) + ((Number(y) - (chunkXY[1] * cWidth)) * cWidth);
}

key(bmType, cX, cY) {
return `${this.parent[KeyPrefixKey]}:${bmType}:${cX}:${cY}`;
}

async getSet(bmType, x, y, setVal = undefined) {
const chunk = this.chunkCoords(x, y);
const bitPos = this.bitPosition(chunk, x, y);
const [cX, cY] = chunk;

if (setVal === undefined) {
return this.parent[BackingStoreKey].getbit(this.key(bmType, cX, cY), bitPos);
} else {
return this.parent[BackingStoreKey].setbit(this.key(bmType, cX, cY), bitPos, setVal == true ? 1 : 0);
}
};

async allSetInBounds(bmType, fromX, fromY, toX, toY, strict = false) {
const rowWidth = this.parent[ChunkWidthKey];
const [fcX, fcY] = this.chunkCoords(fromX, fromY);
const [tcX, tcY] = this.chunkCoords(toX, toY);

let retList = [];
let bufferGetter = async (bgX, bgY) => this.parent[BackingStoreKey].getBuffer(this.key(bmType, bgX, bgY));

if (this.parent.isPipelineCapable) {
const plBuffer = {};

bufferGetter = async (bgX, bgY) => {
if (bgX in plBuffer) {
if (bgY in plBuffer[bgX]) {
return plBuffer[bgX][bgY];
}
}
};

const pipeline = this.parent[BackingStoreKey].pipeline();
for (let wcX = fcX; wcX <= tcX; wcX++) {
if (!(wcX in plBuffer)) {
plBuffer[wcX] = {};
}

for (let wcY = fcY; wcY <= tcY; wcY++) {
pipeline.getBuffer(this.key(bmType, wcX, wcY), function (err, result) {
if (err) {
throw new Error(`pipeline.getBuffer: shouldn't happen! ${err}`);
}

plBuffer[wcX][wcY] = result;
});
}
}

await pipeline.exec();
bufferGetter = async (bgX, bgY) => plBuffer[bgX][bgY];
}

for (let wcX = fcX; wcX <= tcX; wcX++) {
for (let wcY = fcY; wcY <= tcY; wcY++) {
const chunkBytes = await bufferGetter(wcX, wcY);

if (!chunkBytes || chunkBytes.length < 1) {
continue;
}

for (let cByte = 0; cByte < chunkBytes.length; cByte++) {
for (let bit = 0; bit < 8; bit++) {
if (chunkBytes[cByte] & (1 << bit)) {
let ix = (wcX * rowWidth) + (7 - bit) + ((cByte % (rowWidth / 8)) * 8);
let iy = ((((7 - bit) + (cByte * 8)) - ix + (wcX * rowWidth)) / rowWidth) + (wcY * rowWidth);
retList.push([ix, iy]);
}
}
}
}
}

// strict includes *only* coordinates within the specified bounding box, otherwise all coordinates
// within the *chunks* intersected by the specified bounding box are returned
if (strict) {
retList = retList.filter(x => x[0] >= fromX && x[1] >= fromY && x[0] <= toX && x[1] <= toY);
}

return retList;
};
}

class SparseBitmap {
constructor(options = {}) {
if (!(ChunkWidthKey in options)) {
Expand Down Expand Up @@ -266,5 +117,6 @@ module.exports = {
InMemoryStore,
BackingStoreKey,
ChunkWidthKey,
KeyPrefixKey,
Defaults
};
51 changes: 51 additions & 0 deletions stores/in-memory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// a very simple, unoptimized store provided as an example implementation & last-chance default
module.exports = class {
constructor() {
this.store = {};
}

getbit(key, bitPosition) {
if (key in this.store) {
const byteIdx = Math.floor(bitPosition / 8);
const innerPos = (bitPosition % 8);
const bitMask = 0x80 >> innerPos;
if (byteIdx in this.store[key]) {
return (this.store[key][byteIdx] & bitMask) >> (7 - innerPos);
}
}

return 0;
}

setbit(key, bitPosition, value) {
if (!(key in this.store)) {
this.store[key] = [];
}

const byteIdx = Math.floor(bitPosition / 8);
const bitMask = 0x80 >> (bitPosition % 8);

// lazily initialize everything up to and including byteIdx, if it isn't already initialized
if (!(byteIdx in this.store[key])) {
for (let bi = 0; bi <= byteIdx; bi++) {
if (!(bi in this.store[key])) {
this.store[key][bi] = 0;
}
}
}

if (value == true) {
this.store[key][byteIdx] |= bitMask;
} else {
this.store[key][byteIdx] &= ~bitMask;
}
}

getBuffer(key) {
if (!(key in this.store)) {
this.store[key] = [];
}

return Buffer.from(this.store[key]);
}
}
5 changes: 5 additions & 0 deletions stores/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const InMemoryStore = require('./in-memory');

module.exports = {
InMemoryStore
}

0 comments on commit db82ce6

Please sign in to comment.