@computekit/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +820 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +270 -0
- package/dist/index.d.ts +270 -0
- package/dist/index.js +800 -0
- package/dist/index.js.map +1 -0
- package/dist/types-AaH5nWxG.d.cts +143 -0
- package/dist/types-AaH5nWxG.d.ts +143 -0
- package/dist/worker.cjs +148 -0
- package/dist/worker.cjs.map +1 -0
- package/dist/worker.d.cts +23 -0
- package/dist/worker.d.ts +23 -0
- package/dist/worker.js +143 -0
- package/dist/worker.js.map +1 -0
- package/package.json +61 -0
- package/src/cli.ts +246 -0
- package/src/index.test.ts +93 -0
- package/src/index.ts +232 -0
- package/src/pool.ts +591 -0
- package/src/types.ts +229 -0
- package/src/utils.ts +305 -0
- package/src/wasm.ts +205 -0
- package/src/worker/index.ts +11 -0
- package/src/worker/runtime.ts +149 -0
package/src/wasm.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComputeKit WASM Loader
|
|
3
|
+
* Utilities for loading and managing WebAssembly modules
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WasmModuleConfig } from './types';
|
|
7
|
+
import { createLogger, LRUCache } from './utils';
|
|
8
|
+
|
|
9
|
+
const logger = createLogger('ComputeKit:WASM');
|
|
10
|
+
|
|
11
|
+
/** Cached WASM modules */
|
|
12
|
+
const moduleCache = new LRUCache<string, WebAssembly.Module>(10);
|
|
13
|
+
|
|
14
|
+
/** Cached WASM instances */
|
|
15
|
+
const instanceCache = new LRUCache<string, WebAssembly.Instance>(10);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load a WASM module from various sources
|
|
19
|
+
*/
|
|
20
|
+
export async function loadWasmModule(
|
|
21
|
+
source: string | ArrayBuffer | Uint8Array
|
|
22
|
+
): Promise<WebAssembly.Module> {
|
|
23
|
+
// Check cache for string sources
|
|
24
|
+
if (typeof source === 'string') {
|
|
25
|
+
const cached = moduleCache.get(source);
|
|
26
|
+
if (cached) {
|
|
27
|
+
logger.debug('Using cached WASM module:', source);
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let bytes: ArrayBuffer | Uint8Array;
|
|
33
|
+
|
|
34
|
+
if (typeof source === 'string') {
|
|
35
|
+
if (source.startsWith('data:')) {
|
|
36
|
+
// Base64 encoded WASM
|
|
37
|
+
const base64 = source.split(',')[1];
|
|
38
|
+
bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
39
|
+
} else {
|
|
40
|
+
// URL to WASM file
|
|
41
|
+
logger.debug('Fetching WASM from:', source);
|
|
42
|
+
const response = await fetch(source);
|
|
43
|
+
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new Error(`Failed to fetch WASM: ${response.statusText}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Use streaming compilation if available
|
|
49
|
+
if (WebAssembly.compileStreaming) {
|
|
50
|
+
const module = await WebAssembly.compileStreaming(response);
|
|
51
|
+
moduleCache.set(source, module);
|
|
52
|
+
return module;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
bytes = await response.arrayBuffer();
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
bytes = source;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Compile the module
|
|
62
|
+
const module = await WebAssembly.compile(bytes as BufferSource);
|
|
63
|
+
|
|
64
|
+
if (typeof source === 'string') {
|
|
65
|
+
moduleCache.set(source, module);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return module;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Instantiate a WASM module with imports
|
|
73
|
+
*/
|
|
74
|
+
export async function instantiateWasm(
|
|
75
|
+
module: WebAssembly.Module,
|
|
76
|
+
imports: WebAssembly.Imports = {}
|
|
77
|
+
): Promise<WebAssembly.Instance> {
|
|
78
|
+
return WebAssembly.instantiate(module, imports);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Load and instantiate a WASM module in one step
|
|
83
|
+
*/
|
|
84
|
+
export async function loadAndInstantiate(
|
|
85
|
+
config: WasmModuleConfig
|
|
86
|
+
): Promise<{ module: WebAssembly.Module; instance: WebAssembly.Instance }> {
|
|
87
|
+
const { source, imports = {}, memory } = config;
|
|
88
|
+
|
|
89
|
+
// Create memory if specified
|
|
90
|
+
const wasmImports: WebAssembly.Imports = { ...imports };
|
|
91
|
+
if (memory) {
|
|
92
|
+
wasmImports.env = {
|
|
93
|
+
...wasmImports.env,
|
|
94
|
+
memory: new WebAssembly.Memory({
|
|
95
|
+
initial: memory.initial,
|
|
96
|
+
maximum: memory.maximum,
|
|
97
|
+
shared: memory.shared,
|
|
98
|
+
}),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const module = await loadWasmModule(source);
|
|
103
|
+
const instance = await instantiateWasm(module, wasmImports);
|
|
104
|
+
|
|
105
|
+
return { module, instance };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Create a WASM module from AssemblyScript-compiled bytes
|
|
110
|
+
*/
|
|
111
|
+
export async function loadAssemblyScript(
|
|
112
|
+
source: string | ArrayBuffer,
|
|
113
|
+
imports: WebAssembly.Imports = {}
|
|
114
|
+
): Promise<{
|
|
115
|
+
module: WebAssembly.Module;
|
|
116
|
+
instance: WebAssembly.Instance;
|
|
117
|
+
exports: Record<string, unknown>;
|
|
118
|
+
}> {
|
|
119
|
+
// Default AssemblyScript imports
|
|
120
|
+
const defaultImports: WebAssembly.Imports = {
|
|
121
|
+
env: {
|
|
122
|
+
abort: (_message: number, fileName: number, line: number, column: number) => {
|
|
123
|
+
console.error(`AssemblyScript abort at ${fileName}:${line}:${column}`);
|
|
124
|
+
},
|
|
125
|
+
seed: () => Date.now(),
|
|
126
|
+
...((imports.env as object) || {}),
|
|
127
|
+
},
|
|
128
|
+
...imports,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const { module, instance } = await loadAndInstantiate({
|
|
132
|
+
source,
|
|
133
|
+
imports: defaultImports,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
module,
|
|
138
|
+
instance,
|
|
139
|
+
exports: instance.exports as Record<string, unknown>,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Helper to wrap WASM exports for easier use
|
|
145
|
+
*/
|
|
146
|
+
export function wrapWasmExports<T extends Record<string, unknown>>(
|
|
147
|
+
instance: WebAssembly.Instance
|
|
148
|
+
): T {
|
|
149
|
+
return instance.exports as T;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Create a typed array view into WASM memory
|
|
154
|
+
*/
|
|
155
|
+
export function getMemoryView<T extends ArrayBufferView>(
|
|
156
|
+
memory: WebAssembly.Memory,
|
|
157
|
+
ArrayType: new (buffer: ArrayBuffer, byteOffset?: number, length?: number) => T,
|
|
158
|
+
offset: number = 0,
|
|
159
|
+
length?: number
|
|
160
|
+
): T {
|
|
161
|
+
return new ArrayType(memory.buffer, offset, length);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Copy data to WASM memory
|
|
166
|
+
*/
|
|
167
|
+
export function copyToWasmMemory(
|
|
168
|
+
memory: WebAssembly.Memory,
|
|
169
|
+
data: ArrayBufferView,
|
|
170
|
+
offset: number
|
|
171
|
+
): void {
|
|
172
|
+
const view = new Uint8Array(memory.buffer);
|
|
173
|
+
const source = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
174
|
+
view.set(source, offset);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Copy data from WASM memory
|
|
179
|
+
*/
|
|
180
|
+
export function copyFromWasmMemory(
|
|
181
|
+
memory: WebAssembly.Memory,
|
|
182
|
+
offset: number,
|
|
183
|
+
length: number
|
|
184
|
+
): Uint8Array {
|
|
185
|
+
const view = new Uint8Array(memory.buffer, offset, length);
|
|
186
|
+
return new Uint8Array(view); // Copy to detach from WASM memory
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Clear module caches
|
|
191
|
+
*/
|
|
192
|
+
export function clearWasmCache(): void {
|
|
193
|
+
moduleCache.clear();
|
|
194
|
+
instanceCache.clear();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get cache statistics
|
|
199
|
+
*/
|
|
200
|
+
export function getWasmCacheStats(): { modules: number; instances: number } {
|
|
201
|
+
return {
|
|
202
|
+
modules: moduleCache.size,
|
|
203
|
+
instances: instanceCache.size,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComputeKit Worker Runtime
|
|
3
|
+
* Code that runs inside Web Workers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
WorkerMessage,
|
|
8
|
+
ExecutePayload,
|
|
9
|
+
ResultPayload,
|
|
10
|
+
ErrorPayload,
|
|
11
|
+
ComputeProgress,
|
|
12
|
+
} from '../types';
|
|
13
|
+
|
|
14
|
+
import { generateId, findTransferables } from '../utils';
|
|
15
|
+
|
|
16
|
+
/** Registry of compute functions available in the worker */
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
18
|
+
const functionRegistry = new Map<string, Function>();
|
|
19
|
+
|
|
20
|
+
/** Current task context for progress reporting */
|
|
21
|
+
let currentTaskId: string | null = null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Report progress from within a compute function
|
|
25
|
+
*/
|
|
26
|
+
export function reportProgress(progress: Partial<ComputeProgress>): void {
|
|
27
|
+
if (!currentTaskId) {
|
|
28
|
+
console.warn('reportProgress called outside of compute function');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const message: WorkerMessage = {
|
|
33
|
+
id: generateId(),
|
|
34
|
+
type: 'progress',
|
|
35
|
+
payload: {
|
|
36
|
+
taskId: currentTaskId,
|
|
37
|
+
progress: {
|
|
38
|
+
percent: progress.percent ?? 0,
|
|
39
|
+
phase: progress.phase,
|
|
40
|
+
estimatedTimeRemaining: progress.estimatedTimeRemaining,
|
|
41
|
+
data: progress.data,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
self.postMessage(message);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Register a compute function in the worker
|
|
52
|
+
*/
|
|
53
|
+
export function registerFunction(
|
|
54
|
+
name: string,
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
56
|
+
fn: Function
|
|
57
|
+
): void {
|
|
58
|
+
functionRegistry.set(name, fn);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Execute a registered function
|
|
63
|
+
*/
|
|
64
|
+
async function executeFunction(functionName: string, input: unknown): Promise<unknown> {
|
|
65
|
+
const fn = functionRegistry.get(functionName);
|
|
66
|
+
|
|
67
|
+
if (!fn) {
|
|
68
|
+
throw new Error(`Function "${functionName}" not found in worker`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return fn(input);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Handle incoming messages from the main thread
|
|
76
|
+
*/
|
|
77
|
+
async function handleMessage(event: MessageEvent<WorkerMessage>): Promise<void> {
|
|
78
|
+
const { id, type, payload } = event.data;
|
|
79
|
+
|
|
80
|
+
if (type === 'execute') {
|
|
81
|
+
const { functionName, input } = payload as ExecutePayload;
|
|
82
|
+
const startTime = performance.now();
|
|
83
|
+
|
|
84
|
+
// Set current task for progress reporting
|
|
85
|
+
currentTaskId = id;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const result = await executeFunction(functionName, input);
|
|
89
|
+
const duration = performance.now() - startTime;
|
|
90
|
+
|
|
91
|
+
// Find transferable objects in result
|
|
92
|
+
const transfer = findTransferables(result);
|
|
93
|
+
|
|
94
|
+
const response: WorkerMessage<ResultPayload> = {
|
|
95
|
+
id,
|
|
96
|
+
type: 'result',
|
|
97
|
+
payload: {
|
|
98
|
+
data: result,
|
|
99
|
+
duration,
|
|
100
|
+
},
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
self.postMessage(response, transfer as Transferable[]);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
107
|
+
|
|
108
|
+
const response: WorkerMessage<ErrorPayload> = {
|
|
109
|
+
id,
|
|
110
|
+
type: 'error',
|
|
111
|
+
payload: {
|
|
112
|
+
message: error.message,
|
|
113
|
+
stack: error.stack,
|
|
114
|
+
},
|
|
115
|
+
timestamp: Date.now(),
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
self.postMessage(response);
|
|
119
|
+
} finally {
|
|
120
|
+
currentTaskId = null;
|
|
121
|
+
}
|
|
122
|
+
} else if (type === 'init') {
|
|
123
|
+
// Handle initialization if needed
|
|
124
|
+
const response: WorkerMessage = {
|
|
125
|
+
id,
|
|
126
|
+
type: 'ready',
|
|
127
|
+
timestamp: Date.now(),
|
|
128
|
+
};
|
|
129
|
+
self.postMessage(response);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Initialize the worker runtime
|
|
135
|
+
*/
|
|
136
|
+
export function initWorkerRuntime(): void {
|
|
137
|
+
self.onmessage = handleMessage;
|
|
138
|
+
|
|
139
|
+
// Signal that worker is ready
|
|
140
|
+
const readyMessage: WorkerMessage = {
|
|
141
|
+
id: generateId(),
|
|
142
|
+
type: 'ready',
|
|
143
|
+
timestamp: Date.now(),
|
|
144
|
+
};
|
|
145
|
+
self.postMessage(readyMessage);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Export for use in worker entry point
|
|
149
|
+
export { functionRegistry };
|