Skip to content

Commit d668b49

Browse files
committed
Module field should only point to JS that only uses ES6 module syntax, otherwise it must remain as standard JS
1 parent d246088 commit d668b49

File tree

4 files changed

+352
-5
lines changed

4 files changed

+352
-5
lines changed
File renamed without changes.

dist/Counter.es.js

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
import BitSet from 'bitset.js';
2+
3+
/** @module Counter */
4+
5+
/**
6+
* Parameterises the bitmap tree contructors by the block size
7+
* The block size is the size of each bitmap
8+
* @param {number} blockSize
9+
* @returns {{Leaf: Leaf, Node: Node}}
10+
*/
11+
function setupBitMapConstructors(blockSize) {
12+
13+
// bitset library uses 32 bits numbers internally
14+
// it preemptively adds an extra number whan it detects it's full
15+
// this is why we use Uint8Array and minus 1 from the blocksize / 8
16+
// in order to get exactly the right size
17+
// because of the functions supplied by the bitset library
18+
// we invert the notions of set and unset where
19+
// set is 0 and unset is 1
20+
21+
/**
22+
* Creates a new bitmap sized according to the block size
23+
* @returns {BitSet}
24+
*/
25+
const createBitMap = function createBitMap() {
26+
return new BitSet(new Uint8Array(blockSize / 8 - 1)).flip(0, blockSize - 1);
27+
};
28+
29+
/**
30+
* Set a bit
31+
* @param {BitSet} bitMap
32+
* @param {number} i
33+
* @returns {BitSet}
34+
*/
35+
const setBit = function setBit(bitMap, i) {
36+
return bitMap.set(i, 0);
37+
};
38+
39+
/**
40+
* Unsets a bit
41+
* @param {BitSet} bitMap
42+
* @param {number} i
43+
* @returns {BitSet}
44+
*/
45+
const unsetBit = function unsetBit(bitMap, i) {
46+
return bitMap.set(i, 1);
47+
};
48+
49+
/**
50+
* Checks if the entire bitmap is set
51+
* @param {BitSet} bitMap
52+
* @returns {bool}
53+
*/
54+
const allSet = function allSet(bitMap) {
55+
return bitMap.isEmpty();
56+
};
57+
58+
/**
59+
* Checks if the entire bitmap is unset
60+
* @param {BitSet} bitMap
61+
* @returns {bool}
62+
*/
63+
const allUnset = function allUnset(bitMap) {
64+
return bitMap.cardinality() === blockSize;
65+
};
66+
67+
/**
68+
* Find first set algorithm
69+
* If null is returned, all items have been set
70+
* @param {BitSet} bitMap
71+
* @returns {number|null}
72+
*/
73+
const firstUnset = function firstUnset(bitMap) {
74+
let first = bitMap.ntz();
75+
if (first === Infinity) {
76+
first = null;
77+
}
78+
return first;
79+
};
80+
81+
/**
82+
* Class representing a lazy recursive bitmap tree
83+
* Only the leaf bitmaps correspond to counters
84+
* Interior bitmaps index their child bitmaps
85+
* If an interior bit is set, that means there's no free bits in the child bitmap
86+
* If an interior bit is not set, that means there's at least 1 free bit in the child bitmap
87+
*/
88+
class BitMapTree {
89+
90+
/**
91+
* Creates a BitMapTree, this is an abstract class
92+
* It is not meant to by directly instantiated
93+
* @param {number} begin
94+
* @param {number} depth
95+
*/
96+
constructor(begin, depth) {
97+
this.begin = begin;
98+
this.depth = depth;
99+
this.bitMap = createBitMap();
100+
}
101+
102+
/**
103+
* Sets a bit to allocated
104+
* @param {number} index
105+
*/
106+
set(index) {
107+
setBit(this.bitMap, index);
108+
}
109+
110+
/**
111+
* Unsets a bit so that is free
112+
* @param {number} index
113+
*/
114+
unset(index) {
115+
unsetBit(this.bitMap, index);
116+
}
117+
118+
}
119+
120+
/**
121+
* Class representing a Leaf of the recursive bitmap tree
122+
* This represents the base case of the lazy recursive bitmap tree
123+
* @extends BitMapTree
124+
*/
125+
class Leaf extends BitMapTree {
126+
127+
/**
128+
* Creates a Leaf
129+
* @param {number} begin
130+
*/
131+
constructor(begin) {
132+
super(begin, 0);
133+
}
134+
135+
/**
136+
* Allocates a counter and sets the corresponding bit for the bitmap
137+
* @param {function} callback
138+
*/
139+
allocate(callback) {
140+
let index = firstUnset(this.bitMap);
141+
if (index !== null) {
142+
setBit(this.bitMap, index);
143+
callback(this.begin + index, this.bitMap);
144+
} else {
145+
callback(null, null);
146+
}
147+
}
148+
149+
/**
150+
* Deallocates a counter and unsets the corresponding bit for the bitmap
151+
* @param {number} counter
152+
* @param {function} callback
153+
*/
154+
deallocate(counter, callback) {
155+
let index = Math.floor((counter - this.begin) / Math.pow(blockSize, this.depth));
156+
if (index >= 0 && index < blockSize) {
157+
unsetBit(this.bitMap, index);
158+
callback(this.bitMap);
159+
} else {
160+
callback(null);
161+
}
162+
}
163+
164+
}
165+
166+
/**
167+
* Class representing a Node of the recursive bitmap tree
168+
* @extends BitMapTree
169+
*/
170+
class Node extends BitMapTree {
171+
172+
/**
173+
* Creates a Node
174+
* @param {number} begin
175+
* @param {number} depth
176+
*/
177+
constructor(begin, depth) {
178+
super(begin, depth);
179+
this.bitMapTrees = [];
180+
}
181+
182+
/**
183+
* Pushes a child node or leaf to the terminal end
184+
* @param {Leaf|Node} child
185+
*/
186+
pushChild(child) {
187+
let index = this.bitMapTrees.push(child) - 1;
188+
if (allSet(child.bitMap)) setBit(this.bitMap, index);
189+
}
190+
191+
/**
192+
* Pops the terminal child node or leaf
193+
*/
194+
popChild() {
195+
if (this.bitMapTrees.length) {
196+
this.bitMapTrees.pop();
197+
}
198+
}
199+
200+
/**
201+
* Allocates a counter by allocating the corresponding child
202+
* Passes a continuation to the child allocate that will
203+
* set the current bitmap if the child bitmap is now all set
204+
* It will also lazily create the child if it doesn't already exist
205+
* @param {function} callback
206+
*/
207+
allocate(callback) {
208+
let index = firstUnset(this.bitMap);
209+
if (index === null) {
210+
callback(null, null);
211+
} else if (this.bitMapTrees[index]) {
212+
this.bitMapTrees[index].allocate((counter, bitMap) => {
213+
if (bitMap && allSet(bitMap)) {
214+
setBit(this.bitMap, index);
215+
}
216+
callback(counter, this.bitMap);
217+
});
218+
} else {
219+
let newBegin = this.begin;
220+
if (this.bitMapTrees.length) {
221+
newBegin = this.bitMapTrees[index - 1].begin + Math.pow(blockSize, this.depth);
222+
}
223+
let newDepth = this.depth - 1;
224+
let child;
225+
if (newDepth === 0) {
226+
child = new Leaf(newBegin);
227+
} else {
228+
child = new Node(newBegin, newDepth);
229+
}
230+
this.pushChild(child);
231+
child.allocate((counter, bitMap) => {
232+
if (bitMap && allSet(bitMap)) {
233+
setBit(this.bitMap, index);
234+
}
235+
callback(counter, this.bitMap);
236+
});
237+
}
238+
}
239+
240+
/**
241+
* Deallocates a counter by deallocating the corresponding child
242+
* Passes a continuation to the child deallocate that will
243+
* unset the current bitmap if the child bitmap was previously all set
244+
* It will also attempt to shrink the tree if the child is the terminal child
245+
* and it is all unset
246+
* @param {number} counter
247+
* @param {function} callback
248+
*/
249+
deallocate(counter, callback) {
250+
let index = Math.floor((counter - this.begin) / Math.pow(blockSize, this.depth));
251+
if (this.bitMapTrees[index]) {
252+
let allSetPrior = allSet(this.bitMapTrees[index].bitMap);
253+
this.bitMapTrees[index].deallocate(counter, bitMap => {
254+
if (bitMap && allSetPrior) {
255+
unsetBit(this.bitMap, index);
256+
}
257+
if (this.bitMapTrees.length - 1 === index && allUnset(bitMap)) {
258+
this.popChild();
259+
}
260+
callback(this.bitMap);
261+
});
262+
} else {
263+
callback(null);
264+
}
265+
}
266+
267+
}
268+
269+
return {
270+
Leaf: Leaf,
271+
Node: Node
272+
};
273+
}
274+
275+
/**
276+
* Class representing allocatable and deallocatable counters
277+
* Counters are allocated in sequential manner, this applies to deallocated counters
278+
* Once a counter is deallocated, it will be reused on the next allocation
279+
*/
280+
class Counter {
281+
282+
/**
283+
* Creates a counter instance
284+
* @param {number} [begin] - Defaults to 0
285+
* @param {number} [blockSize] - Must be a multiple of 32, defaults to 32
286+
* @throws {TypeError} - Will throw if blockSize is not a multiple of 32
287+
*/
288+
constructor(begin, blockSize) {
289+
if (typeof begin === 'undefined') begin = 0;
290+
if (blockSize && blockSize % 32 !== 0) {
291+
throw TypeError('Blocksize for BitMapTree must be a multiple of 32');
292+
} else {
293+
// JavaScript doesn't yet have 64 bit numbers so we default to 32
294+
blockSize = 32;
295+
}
296+
this._begin = begin;
297+
this._bitMapConst = setupBitMapConstructors(blockSize);
298+
this._bitMapTree = new this._bitMapConst.Leaf(0);
299+
}
300+
301+
/**
302+
* Allocates a counter sequentially
303+
* @returns {number}
304+
*/
305+
allocate() {
306+
let resultCounter;
307+
this._bitMapTree.allocate((counter, bitMap) => {
308+
resultCounter = counter;
309+
});
310+
if (resultCounter !== null) {
311+
return this._begin + resultCounter;
312+
} else {
313+
let newRoot = new this._bitMapConst.Node(this._bitMapTree.begin, this._bitMapTree.depth + 1);
314+
newRoot.pushChild(this._bitMapTree);
315+
this._bitMapTree = newRoot;
316+
return this.allocate();
317+
}
318+
}
319+
320+
/**
321+
* Deallocates a number, it makes it available for reuse
322+
* @param {number} counter
323+
*/
324+
deallocate(counter) {
325+
this._bitMapTree.deallocate(counter - this._begin, function () {});
326+
}
327+
328+
}
329+
330+
export default Counter;

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
"type": "git",
1313
"url": "https://github.com/MatrixAI/js-resource-counter.git"
1414
},
15-
"module": "lib/Counter.js",
16-
"main": "dist/Counter.js",
15+
"main": "dist/Counter.cjs.js",
16+
"module": "dist/Counter.es.js",
1717
"browser": "dist/Counter-browser.js",
1818
"scripts": {
1919
"test": "ava -v",

rollup.config.js

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,24 @@ import babel from 'rollup-plugin-babel';
66
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
77

88
export default [
9+
{
10+
entry: 'lib/Counter.js',
11+
format: 'es',
12+
external: Object.keys(packageJson.dependencies),
13+
plugins: [
14+
babel({
15+
babelrc: false,
16+
exclude: 'node_modules/**',
17+
presets: [['env', {
18+
modules: false,
19+
targets: {
20+
node: '6.0.0'
21+
}
22+
}]]
23+
})
24+
],
25+
dest: 'dist/Counter.es.js'
26+
},
927
{
1028
entry: 'lib/Counter.js',
1129
format: 'cjs',
@@ -22,7 +40,7 @@ export default [
2240
}]]
2341
})
2442
],
25-
dest: 'dist/Counter.js'
43+
dest: 'dist/Counter.cjs.js'
2644
},
2745
{
2846
entry: 'lib/Counter.js',
@@ -36,8 +54,7 @@ export default [
3654
modules: false,
3755
targets: {
3856
"browsers": ["last 2 versions"]
39-
},
40-
debug: true
57+
}
4158
}]]
4259
}),
4360
resolve({

0 commit comments

Comments
 (0)