@thi.ng/idgen
Version:
Generator of opaque numeric identifiers with optional support for ID versioning and efficient re-use
180 lines (179 loc) • 4.55 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __decorateClass = (decorators, target, key, kind) => {
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
for (var i = decorators.length - 1, decorator; i >= 0; i--)
if (decorator = decorators[i])
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
if (kind && result) __defProp(target, key, result);
return result;
};
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
import { assert } from "@thi.ng/errors/assert";
import { EVENT_ADDED, EVENT_REMOVED } from "./api.js";
let IDGen = class {
ids;
nextID;
_freeID;
start;
num;
_capacity;
mask;
vmask;
shift;
constructor(bits = 32, vbits = 32 - bits, cap = 2 ** bits, start = 0) {
const maxCap = 2 ** bits;
assert(bits > 0 && bits + vbits <= 32, "wrong total bit size [1..32]");
assert(
cap <= maxCap,
`requested capacity too large for bit size (max. ${maxCap})`
);
this.ids = [];
this.nextID = start;
this.start = start;
this._capacity = cap;
this.num = 0;
this.mask = maxCap - 1;
this.vmask = (1 << vbits) - 1;
this.shift = bits;
this._freeID = -1;
}
/**
* Extract actual ID (without version bits).
*
* @param id -
*/
id(id) {
return id & this.mask;
}
/**
* Extract version from ID
*
* @param id -
*/
version(id) {
return id >>> this.shift & this.vmask;
}
get capacity() {
return this._capacity;
}
/**
* Attempts to set new capacity to given value. Capacity can only be
* increased and the operation is only supported for unversioned
* instances (i.e. vbits = 0).
*/
set capacity(newCap) {
assert(!this.vmask, "can't change capacity w/ versioning enabled");
if (newCap >= this.mask + 1) {
const bits = Math.ceil(Math.log2(newCap));
assert(
bits > 0 && bits <= 32,
"wrong bit size for new capacity [1..32]"
);
this._capacity = newCap;
this.mask = 2 ** bits - 1;
this.shift = bits;
} else {
throw new Error("can't reduce capacity");
}
}
/**
* Number of remaining available IDs.
*/
get available() {
return this._capacity - this.num - this.start;
}
/**
* Number of currently used IDs.
*/
get used() {
return this.num;
}
/**
* Next available free ID.
*/
get freeID() {
return this._freeID;
}
*[Symbol.iterator]() {
const { ids, mask } = this;
for (let i = this.nextID; i-- > 0; ) {
const id = ids[i];
if ((id & mask) === i) yield id;
}
}
/**
* Frees all existing IDs and resets counter to original start ID.
*/
clear() {
this.ids.length = 0;
this.nextID = this.start;
this.num = 0;
this._freeID = -1;
}
/**
* Returns next available ID or throws error (assertion) if no further IDs
* are currently available. Emits {@link EVENT_ADDED} if successful.
*/
next() {
let id;
if (this._freeID !== -1) {
id = this._freeID;
const rawID = id & this.mask;
this._freeID = this.ids[rawID];
this.ids[rawID] = id;
} else {
assert(this.nextID < this._capacity, "max capacity reached");
id = this.nextID++;
this.ids[id] = id;
}
this.num++;
this.notify({ id: EVENT_ADDED, target: this, value: id });
return id;
}
/**
* Marks given ID as available again and increases its version (if
* versioning is enabled). Emits {@link EVENT_REMOVED} if successful.
*
* @param id -
*/
free(id) {
if (!this.has(id)) return false;
this.ids[id & this.mask] = this._freeID;
this._freeID = this.nextVersion(id);
this.num--;
this.notify({ id: EVENT_REMOVED, target: this, value: id });
return true;
}
/**
* Returns true iff the given ID is valid and currently used.
*
* @param id -
*/
has(id) {
const rawID = id & this.mask;
return id >= 0 && rawID < this.nextID && this.ids[rawID] === id;
}
// @ts-ignore: mixin
// prettier-ignore
addListener(id, fn, scope) {
}
// @ts-ignore: mixin
// prettier-ignore
removeListener(id, fn, scope) {
}
// @ts-ignore: mixin
notify(event) {
}
nextVersion(id) {
return (id & this.mask | (this.version(id) + 1 & this.vmask) << this.shift) >>> 0;
}
};
IDGen = __decorateClass([
INotifyMixin
], IDGen);
const idgen = (bits, vbits, cap, start) => new IDGen(bits, vbits, cap, start);
export {
IDGen,
idgen
};