Skip to content

Commit

Permalink
feat(wasm-api): major update ObjectIndex
Browse files Browse the repository at this point in the history
- add ObjectIndexOpts ctor options
- add IDGen for internal ID generation/recycling
- add iterators
- rename existing methods
  • Loading branch information
postspectacular committed Aug 1, 2022
1 parent bd7905a commit 4547f1f
Show file tree
Hide file tree
Showing 3 changed files with 190 additions and 113 deletions.
183 changes: 93 additions & 90 deletions packages/wasm-api/package.json
Original file line number Diff line number Diff line change
@@ -1,92 +1,95 @@
{
"name": "@thi.ng/wasm-api",
"version": "0.1.0",
"description": "Modular, extensible API bridge and generic glue code between JS & WebAssembly",
"type": "module",
"module": "./index.js",
"typings": "./index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/thi-ng/umbrella.git"
},
"homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/wasm-api#readme",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/postspectacular"
},
{
"type": "patreon",
"url": "https://patreon.com/thing_umbrella"
}
],
"author": "Karsten Schmidt <k+npm@thi.ng>",
"license": "Apache-2.0",
"scripts": {
"build": "yarn clean && tsc --declaration",
"clean": "rimraf '*.js' '*.d.ts' '*.map' doc",
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
"doc:readme": "yarn doc:stats && tools:readme",
"doc:stats": "tools:module-stats",
"pub": "yarn npm publish --access public",
"test": "testament test"
},
"dependencies": {
"@thi.ng/api": "^8.3.9",
"@thi.ng/errors": "^2.1.9",
"@thi.ng/hex": "^2.1.9",
"@thi.ng/logger": "^1.1.9"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.25.0",
"@thi.ng/testament": "^0.2.10",
"rimraf": "^3.0.2",
"tools": "workspace:^",
"typedoc": "^0.22.17",
"typescript": "^4.7.4"
},
"keywords": [
"api",
"memory",
"typescript",
"wasm",
"webassembly",
"wrapper",
"ziglang"
],
"publishConfig": {
"access": "public"
},
"browser": {
"process": false,
"setTimeout": false
},
"engines": {
"node": ">=14"
},
"files": [
"*.js",
"*.d.ts",
"*.zig"
],
"exports": {
".": {
"default": "./index.js"
},
"./api": {
"default": "./api.js"
},
"./bridge": {
"default": "./bridge.js"
},
"./object-index": {
"default": "./object-index.js"
}
},
"thi.ng": {
"status": "alpha",
"year": 2022
}
"name": "@thi.ng/wasm-api",
"version": "0.1.0",
"description": "Modular, extensible API bridge and generic glue code between JS & WebAssembly",
"type": "module",
"module": "./index.js",
"typings": "./index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/thi-ng/umbrella.git"
},
"homepage": "https://github.com/thi-ng/umbrella/tree/master/packages/wasm-api#readme",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/postspectacular"
},
{
"type": "patreon",
"url": "https://patreon.com/thing_umbrella"
}
],
"author": "Karsten Schmidt <k+npm@thi.ng>",
"license": "Apache-2.0",
"scripts": {
"build": "yarn clean && tsc --declaration",
"clean": "rimraf '*.js' '*.d.ts' '*.map' doc",
"doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
"doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
"doc:readme": "yarn doc:stats && tools:readme",
"doc:stats": "tools:module-stats",
"pub": "yarn npm publish --access public",
"test": "testament test"
},
"dependencies": {
"@thi.ng/api": "^8.3.9",
"@thi.ng/errors": "^2.1.9",
"@thi.ng/hex": "^2.1.9",
"@thi.ng/idgen": "^2.1.9",
"@thi.ng/logger": "^1.1.9"
},
"devDependencies": {
"@microsoft/api-extractor": "^7.25.0",
"@thi.ng/testament": "^0.2.10",
"rimraf": "^3.0.2",
"tools": "workspace:^",
"typedoc": "^0.22.17",
"typescript": "^4.7.4"
},
"keywords": [
"api",
"id",
"logger",
"memory",
"typescript",
"wasm",
"webassembly",
"wrapper",
"ziglang"
],
"publishConfig": {
"access": "public"
},
"browser": {
"process": false,
"setTimeout": false
},
"engines": {
"node": ">=14"
},
"files": [
"*.js",
"*.d.ts",
"*.zig"
],
"exports": {
".": {
"default": "./index.js"
},
"./api": {
"default": "./api.js"
},
"./bridge": {
"default": "./bridge.js"
},
"./object-index": {
"default": "./object-index.js"
}
},
"thi.ng": {
"status": "alpha",
"year": 2022
}
}
119 changes: 96 additions & 23 deletions packages/wasm-api/src/object-index.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,116 @@
import type { Predicate } from "@thi.ng/api";
import type { Predicate, Range1_32 } from "@thi.ng/api";
import { assert } from "@thi.ng/errors/assert";
import { IDGen } from "@thi.ng/idgen";
import type { ILogger } from "@thi.ng/logger";

export interface ObjectIndexOpts {
/**
* Human-readable name for index (used for logging, if any)
*/
name: string;
/**
* Optional logger instance
*/
logger?: ILogger;
/**
* Number of bits for IDs, [1..32] range.
*
* @defaultValue 32
*/
bits?: Range1_32;
}

export class ObjectIndex<T> {
constructor(
public name: string,
public items: T[] = [],
public logger?: ILogger
) {}

add(x: T): number {
const id = this.items.length - 1;
public readonly name: string;
public logger?: ILogger;
protected idgen: IDGen;
protected items: T[] = [];

constructor(opts: ObjectIndexOpts) {
this.name = opts.name;
this.logger = opts.logger;
this.idgen = new IDGen(opts.bits || 32, 0);
}

keys() {
return this.idgen[Symbol.iterator]();
}

*values() {
for (let id of this.idgen) {
yield this.items[id];
}
}

/**
* Indexes given `item` and assigns it to the next available ID (which might
* be a previously freed ID) and returns it.
*
* @param item
*/
add(item: T): number {
const id = this.idgen.next();
this.logger && this.logger.debug(`adding ${this.name} ID: ${id}`);
this.items[id] = x;
this.items[id] = item;
return id;
}

removeID(id: number) {
if (this.items[id] !== undefined) {
/**
* Returns true if the given `id` is valid/active.
*
* @param id
*/
has(id: number) {
return this.idgen.has(id);
}

/**
* First checks if given `id` is valid and if so frees it (for recycling)
* and deletes its corresponding item. If `ensure` is true (default), throws
* an error if the ID is invalid (otherwise returns false for invalid IDs).
*
* @param id
* @param ensure
*/
delete(id: number, ensure = true) {
if (this.idgen.has(id)) {
this.logger && this.logger.debug(`deleting ${this.name} ID: ${id}`);
this.idgen.free(id);
delete this.items[id];
return true;
}
assert(!ensure, `can't delete missing ${this.name} ID: ${id}`);
return false;
}

getID(id: number): T;
getID(id: number, ensure: true): T;
getID(id: number, ensure: false): T | undefined;
getID(id: number, ensure = true) {
const obj = this.items[id];
/**
* First checks if given `id` is valid and if so returns corresponding item.
* If `ensure` is true (default), throws an error if the ID is invalid
* (otherwise returns undefined for invalid IDs)
*
* @param id
*/
get(id: number): T;
get(id: number, ensure: true): T;
get(id: number, ensure: false): T | undefined;
get(id: number, ensure = true) {
ensure &&
assert(obj !== undefined, `missing ${this.name} for ID: ${id}`);
return obj;
assert(this.idgen.has(id), `missing ${this.name} for ID: ${id}`);
return this.items[id];
}

findID(pred: Predicate<T>, ensure = true) {
const id = this.items.findIndex(pred);
ensure && assert(id >= 0, `can't find ${this.name}`);
return id;
/**
* Applies given predicate to all active items and returns ID of first
* matching. If `ensure` is true (default), throws an error if the `pred`
* didn't match anything (otherwise returns undefined).
*
* @param pred
* @param ensure
*/
find(pred: Predicate<T>, ensure = true) {
for (let id of this.idgen) {
if (pred(this.items[id])) return id;
}
assert(!ensure, `given predicate matched no ${this.name}`);
}
}
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4739,6 +4739,7 @@ __metadata:
"@thi.ng/api": ^8.3.9
"@thi.ng/errors": ^2.1.9
"@thi.ng/hex": ^2.1.9
"@thi.ng/idgen": ^2.1.9
"@thi.ng/logger": ^1.1.9
"@thi.ng/testament": ^0.2.10
rimraf: ^3.0.2
Expand Down

0 comments on commit 4547f1f

Please sign in to comment.