moonlight-triangulation-1.4.0.2: ffi/bindings/typescript/src/moonlight.ts
import { failure, success, type Result } from "./failure.js";
import { flattenLoops, flattenPoints, produceHandle } from "./internal/call.js";
import {
ABI_VERSION,
ML_STATUS_OK,
loadNativeApi,
} from "./internal/native.generated.js";
import type { RuntimeContext } from "./internal/resource.js";
import { createMesh, type Mesh } from "./mesh.js";
import { createStructuringElement, type StructuringElement } from "./morphology.js";
import { createRegion, type Region } from "./region.js";
import type { Point, PolygonComponent } from "./values.js";
import { decodeAbiStatus } from "./wire.generated.js";
export type NativeLibrarySource = {
readonly kind: "path";
readonly path: string;
};
export interface Moonlight {
readonly abiVersion: number;
delaunay(points: readonly Point[]): Result<Mesh>;
region(components: readonly PolygonComponent[]): Result<Region>;
structuringElement(points: readonly Point[]): Result<StructuringElement>;
}
export function loadMoonlight(source: NativeLibrarySource): Result<Moonlight> {
if (source.path.length === 0) {
return failure({
kind: "native-library-unavailable",
path: source.path,
message: "native library path must not be empty",
});
}
try {
const native = loadNativeApi(source.path);
const initializationStatus = native.ml_runtime_initialize();
if (initializationStatus !== ML_STATUS_OK) {
const status = decodeAbiStatus(initializationStatus);
return status === undefined
? failure({ kind: "unknown-wire-value", vocabulary: "status", value: initializationStatus })
: failure({ kind: "runtime-initialization-refused", status });
}
const observedVersion = native.ml_abi_version();
if (observedVersion !== ABI_VERSION) {
return failure({
kind: "abi-version-mismatch",
expected: ABI_VERSION,
observed: observedVersion,
});
}
const context: RuntimeContext = { native, identity: Symbol("moonlight runtime") };
return success(new MoonlightRuntime(context));
} catch (cause: unknown) {
return failure({
kind: "native-library-unavailable",
path: source.path,
message: cause instanceof Error ? cause.message : String(cause),
});
}
}
class MoonlightRuntime implements Moonlight {
readonly abiVersion = ABI_VERSION;
readonly #context: RuntimeContext;
constructor(context: RuntimeContext) {
this.#context = context;
}
delaunay(points: readonly Point[]): Result<Mesh> {
const coordinates = flattenPoints(points);
const produced = produceHandle((output, obstruction) =>
this.#context.native.ml_delaunay_f64(
coordinates,
coordinates.length / 2,
output,
obstruction,
),
);
return produced.ok ? success(createMesh(this.#context, produced.value)) : produced;
}
region(components: readonly PolygonComponent[]): Result<Region> {
const loops = components.flatMap(({ outer, holes = [] }) => [outer, ...holes]);
const loopPointCounts = BigUint64Array.from(loops.map((loop) => BigInt(loop.length)));
const componentLoopCounts = BigUint64Array.from(
components.map(({ holes = [] }) => BigInt(holes.length + 1)),
);
const coordinates = flattenLoops(loops);
const produced = produceHandle((output, obstruction) =>
this.#context.native.ml_region_create_f64(
coordinates,
coordinates.length / 2,
loopPointCounts,
loopPointCounts.length,
componentLoopCounts,
componentLoopCounts.length,
output,
obstruction,
),
);
return produced.ok ? success(createRegion(this.#context, produced.value)) : produced;
}
structuringElement(points: readonly Point[]): Result<StructuringElement> {
const coordinates = flattenPoints(points);
const produced = produceHandle((output, obstruction) =>
this.#context.native.ml_structuring_element_create_f64(
coordinates,
coordinates.length / 2,
output,
obstruction,
),
);
return produced.ok
? success(createStructuringElement(this.#context, produced.value))
: produced;
}
}