@oh-my-pi/pi-natives 9.3.1 → 9.6.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/README.md +10 -13
- package/native/pi_natives.darwin-arm64.node +0 -0
- package/native/pi_natives.darwin-x64.node +0 -0
- package/native/pi_natives.linux-arm64.node +0 -0
- package/native/pi_natives.linux-x64.node +0 -0
- package/native/pi_natives.win32-x64.node +0 -0
- package/package.json +6 -6
- package/src/find/types.ts +31 -0
- package/src/grep/index.ts +37 -295
- package/src/grep/types.ts +59 -19
- package/src/highlight/index.ts +5 -13
- package/src/html/index.ts +5 -35
- package/src/html/types.ts +1 -16
- package/src/image/index.ts +52 -73
- package/src/index.ts +20 -93
- package/src/native.ts +159 -0
- package/src/request-options.ts +94 -0
- package/src/text/index.ts +16 -24
- package/src/grep/file-reader.ts +0 -51
- package/src/grep/filters.ts +0 -77
- package/src/grep/worker.ts +0 -212
- package/src/html/worker.ts +0 -40
- package/src/image/types.ts +0 -52
- package/src/image/worker.ts +0 -152
- package/src/pool.ts +0 -362
- package/src/wasix.ts +0 -1745
- package/src/worker-resolver.ts +0 -9
- package/wasm/pi_natives.d.ts +0 -148
- package/wasm/pi_natives.js +0 -891
- package/wasm/pi_natives_bg.wasm +0 -0
- package/wasm/pi_natives_bg.wasm.d.ts +0 -32
package/src/grep/file-reader.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-platform file reader for grep.
|
|
3
|
-
*
|
|
4
|
-
* Uses mmap for files <= 4MB on platforms that support it,
|
|
5
|
-
* falls back to reading into a reusable buffer otherwise.
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from "node:fs/promises";
|
|
8
|
-
|
|
9
|
-
const MAX_MMAP_SIZE = 4 * 1024 * 1024; // 4MB
|
|
10
|
-
export class FileReader {
|
|
11
|
-
#buffer: Buffer | null = null;
|
|
12
|
-
constructor(private readonly maxSize: number = MAX_MMAP_SIZE) {}
|
|
13
|
-
#getBuffer(size: number): Buffer {
|
|
14
|
-
if (!this.#buffer) {
|
|
15
|
-
this.#buffer = Buffer.allocUnsafe(this.maxSize);
|
|
16
|
-
}
|
|
17
|
-
return this.#buffer.subarray(0, size);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async read(filePath: string): Promise<Uint8Array | null> {
|
|
21
|
-
let fileSize: number;
|
|
22
|
-
try {
|
|
23
|
-
const stat = await fs.stat(filePath);
|
|
24
|
-
fileSize = stat.size;
|
|
25
|
-
} catch {
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Skip files larger than buffer size (only search first 4MB worth)
|
|
30
|
-
const readSize = Math.min(fileSize, this.maxSize);
|
|
31
|
-
|
|
32
|
-
// Try mmap for small files (fast path on Linux/macOS)
|
|
33
|
-
if (fileSize <= this.maxSize) {
|
|
34
|
-
try {
|
|
35
|
-
return Bun.mmap(filePath);
|
|
36
|
-
} catch {
|
|
37
|
-
// mmap not supported (Windows) or failed, fall through to read
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Fall back to reading into buffer
|
|
42
|
-
try {
|
|
43
|
-
await using handle = await fs.open(filePath, "r");
|
|
44
|
-
const buffer = this.#getBuffer(readSize);
|
|
45
|
-
const { bytesRead } = await handle.read(buffer, 0, readSize, 0);
|
|
46
|
-
return buffer.subarray(0, bytesRead);
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/grep/filters.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
|
-
|
|
3
|
-
export interface TypeFilter {
|
|
4
|
-
extensions?: string[];
|
|
5
|
-
names?: string[];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const TYPE_ALIASES: Record<string, TypeFilter> = {
|
|
9
|
-
js: { extensions: ["js", "jsx", "mjs", "cjs"] },
|
|
10
|
-
javascript: { extensions: ["js", "jsx", "mjs", "cjs"] },
|
|
11
|
-
ts: { extensions: ["ts", "tsx", "mts", "cts"] },
|
|
12
|
-
typescript: { extensions: ["ts", "tsx", "mts", "cts"] },
|
|
13
|
-
json: { extensions: ["json", "jsonc", "json5"] },
|
|
14
|
-
yaml: { extensions: ["yaml", "yml"] },
|
|
15
|
-
yml: { extensions: ["yaml", "yml"] },
|
|
16
|
-
toml: { extensions: ["toml"] },
|
|
17
|
-
md: { extensions: ["md", "markdown", "mdx"] },
|
|
18
|
-
markdown: { extensions: ["md", "markdown", "mdx"] },
|
|
19
|
-
py: { extensions: ["py", "pyi"] },
|
|
20
|
-
python: { extensions: ["py", "pyi"] },
|
|
21
|
-
rs: { extensions: ["rs"] },
|
|
22
|
-
rust: { extensions: ["rs"] },
|
|
23
|
-
go: { extensions: ["go"] },
|
|
24
|
-
java: { extensions: ["java"] },
|
|
25
|
-
kt: { extensions: ["kt", "kts"] },
|
|
26
|
-
kotlin: { extensions: ["kt", "kts"] },
|
|
27
|
-
c: { extensions: ["c", "h"] },
|
|
28
|
-
cpp: { extensions: ["cpp", "cc", "cxx", "hpp", "hxx", "hh"] },
|
|
29
|
-
cxx: { extensions: ["cpp", "cc", "cxx", "hpp", "hxx", "hh"] },
|
|
30
|
-
cs: { extensions: ["cs", "csx"] },
|
|
31
|
-
csharp: { extensions: ["cs", "csx"] },
|
|
32
|
-
php: { extensions: ["php", "phtml"] },
|
|
33
|
-
rb: { extensions: ["rb", "rake", "gemspec"] },
|
|
34
|
-
ruby: { extensions: ["rb", "rake", "gemspec"] },
|
|
35
|
-
sh: { extensions: ["sh", "bash", "zsh", "fish"] },
|
|
36
|
-
bash: { extensions: ["sh", "bash", "zsh"] },
|
|
37
|
-
zsh: { extensions: ["zsh"] },
|
|
38
|
-
fish: { extensions: ["fish"] },
|
|
39
|
-
html: { extensions: ["html", "htm"] },
|
|
40
|
-
css: { extensions: ["css"] },
|
|
41
|
-
scss: { extensions: ["scss"] },
|
|
42
|
-
sass: { extensions: ["sass"] },
|
|
43
|
-
less: { extensions: ["less"] },
|
|
44
|
-
xml: { extensions: ["xml"] },
|
|
45
|
-
docker: { names: ["dockerfile"] },
|
|
46
|
-
dockerfile: { names: ["dockerfile"] },
|
|
47
|
-
make: { names: ["makefile"] },
|
|
48
|
-
makefile: { names: ["makefile"] },
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export function buildGlobPattern(glob?: string): string {
|
|
52
|
-
const trimmed = glob?.trim();
|
|
53
|
-
if (!trimmed) return "**/*";
|
|
54
|
-
const normalized = trimmed.replace(/\\/g, "/");
|
|
55
|
-
if (normalized.includes("/") || normalized.startsWith("**/")) return normalized;
|
|
56
|
-
return `**/${normalized}`;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function resolveTypeFilter(type?: string): TypeFilter | undefined {
|
|
60
|
-
if (!type) return undefined;
|
|
61
|
-
const trimmed = type.trim();
|
|
62
|
-
if (!trimmed) return undefined;
|
|
63
|
-
const normalized = trimmed.toLowerCase();
|
|
64
|
-
const withoutDot = normalized.startsWith(".") ? normalized.slice(1) : normalized;
|
|
65
|
-
return TYPE_ALIASES[withoutDot] ?? { extensions: [withoutDot] };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function matchesTypeFilter(filePath: string, filter?: TypeFilter): boolean {
|
|
69
|
-
if (!filter) return true;
|
|
70
|
-
const baseName = path.basename(filePath).toLowerCase();
|
|
71
|
-
if (filter.names?.some(name => name.toLowerCase() === baseName)) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
const ext = path.extname(baseName).slice(1).toLowerCase();
|
|
75
|
-
if (!ext) return false;
|
|
76
|
-
return filter.extensions?.includes(ext) ?? false;
|
|
77
|
-
}
|
package/src/grep/worker.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker script for running wasm-bindgen grep.
|
|
3
|
-
* Each worker loads its own WASM instance and processes requests.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from "node:fs/promises";
|
|
7
|
-
import * as path from "node:path";
|
|
8
|
-
import { globPaths } from "@oh-my-pi/pi-utils";
|
|
9
|
-
import { CompiledPattern } from "../../wasm/pi_natives";
|
|
10
|
-
import { FileReader } from "./file-reader";
|
|
11
|
-
import { buildGlobPattern, matchesTypeFilter, resolveTypeFilter } from "./filters";
|
|
12
|
-
import type { GrepMatch, GrepOptions, GrepResult, WasmSearchResult, WorkerRequest, WorkerResponse } from "./types";
|
|
13
|
-
|
|
14
|
-
function filterUndefined<T extends Record<string, unknown>>(obj: T): T {
|
|
15
|
-
const result = {} as T;
|
|
16
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
17
|
-
if (value !== undefined) {
|
|
18
|
-
(result as Record<string, unknown>)[key] = value;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return result;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function runGrep(request: GrepOptions): Promise<GrepResult> {
|
|
25
|
-
const searchPath = path.resolve(request.path);
|
|
26
|
-
const stat = await fs.stat(searchPath);
|
|
27
|
-
const isFile = stat.isFile();
|
|
28
|
-
|
|
29
|
-
using compiledPattern = new CompiledPattern(
|
|
30
|
-
filterUndefined({
|
|
31
|
-
pattern: request.pattern,
|
|
32
|
-
ignoreCase: request.ignoreCase,
|
|
33
|
-
multiline: request.multiline,
|
|
34
|
-
context: request.context,
|
|
35
|
-
maxColumns: request.maxColumns,
|
|
36
|
-
mode: request.mode === "content" || !request.mode ? "content" : "count",
|
|
37
|
-
}),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const matches: GrepMatch[] = [];
|
|
41
|
-
let totalMatches = 0;
|
|
42
|
-
let filesWithMatches = 0;
|
|
43
|
-
let filesSearched = 0;
|
|
44
|
-
let limitReached = false;
|
|
45
|
-
const maxCount = request.maxCount;
|
|
46
|
-
const globalOffset = request.offset ?? 0;
|
|
47
|
-
const typeFilter = resolveTypeFilter(request.type);
|
|
48
|
-
const globPattern = buildGlobPattern(request.glob);
|
|
49
|
-
|
|
50
|
-
const fileReader = new FileReader();
|
|
51
|
-
if (isFile) {
|
|
52
|
-
if (typeFilter && !matchesTypeFilter(searchPath, typeFilter)) {
|
|
53
|
-
return {
|
|
54
|
-
matches,
|
|
55
|
-
totalMatches,
|
|
56
|
-
filesWithMatches,
|
|
57
|
-
filesSearched,
|
|
58
|
-
limitReached: limitReached || undefined,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const content = await fileReader.read(searchPath);
|
|
63
|
-
if (!content) {
|
|
64
|
-
return {
|
|
65
|
-
matches,
|
|
66
|
-
totalMatches,
|
|
67
|
-
filesWithMatches,
|
|
68
|
-
filesSearched,
|
|
69
|
-
limitReached: limitReached || undefined,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
filesSearched = 1;
|
|
73
|
-
|
|
74
|
-
const result = compiledPattern.search_bytes(
|
|
75
|
-
content,
|
|
76
|
-
maxCount,
|
|
77
|
-
globalOffset > 0 ? globalOffset : undefined,
|
|
78
|
-
) as WasmSearchResult;
|
|
79
|
-
|
|
80
|
-
if (!result.error && result.matchCount > 0) {
|
|
81
|
-
filesWithMatches = 1;
|
|
82
|
-
totalMatches = result.matchCount;
|
|
83
|
-
|
|
84
|
-
if (request.mode === "content" || !request.mode) {
|
|
85
|
-
for (const m of result.matches) {
|
|
86
|
-
matches.push({
|
|
87
|
-
path: searchPath,
|
|
88
|
-
lineNumber: m.lineNumber,
|
|
89
|
-
line: m.line,
|
|
90
|
-
contextBefore: m.contextBefore?.length ? m.contextBefore : undefined,
|
|
91
|
-
contextAfter: m.contextAfter?.length ? m.contextAfter : undefined,
|
|
92
|
-
truncated: m.truncated || undefined,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
matches.push({
|
|
97
|
-
path: searchPath,
|
|
98
|
-
lineNumber: 0,
|
|
99
|
-
line: "",
|
|
100
|
-
matchCount: result.matchCount,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
limitReached = result.limitReached || (maxCount !== undefined && totalMatches >= maxCount);
|
|
105
|
-
}
|
|
106
|
-
} else {
|
|
107
|
-
const paths = await globPaths(globPattern, {
|
|
108
|
-
cwd: searchPath,
|
|
109
|
-
dot: request.hidden ?? true,
|
|
110
|
-
onlyFiles: true,
|
|
111
|
-
gitignore: true,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
for (const relativePath of paths) {
|
|
115
|
-
if (limitReached) break;
|
|
116
|
-
if (typeFilter && !matchesTypeFilter(relativePath, typeFilter)) {
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const normalizedPath = relativePath.replace(/\\/g, "/");
|
|
121
|
-
const fullPath = path.join(searchPath, normalizedPath);
|
|
122
|
-
|
|
123
|
-
const content = await fileReader.read(fullPath);
|
|
124
|
-
if (!content) continue;
|
|
125
|
-
|
|
126
|
-
filesSearched++;
|
|
127
|
-
|
|
128
|
-
if (!compiledPattern.has_match_bytes(content)) {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const fileOffset = globalOffset > 0 ? Math.max(globalOffset - totalMatches, 0) : 0;
|
|
133
|
-
const remaining = maxCount !== undefined ? Math.max(maxCount - totalMatches, 0) : undefined;
|
|
134
|
-
if (remaining === 0) {
|
|
135
|
-
limitReached = true;
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
const result = compiledPattern.search_bytes(
|
|
139
|
-
content,
|
|
140
|
-
remaining,
|
|
141
|
-
fileOffset > 0 ? fileOffset : undefined,
|
|
142
|
-
) as WasmSearchResult;
|
|
143
|
-
|
|
144
|
-
if (result.error) continue;
|
|
145
|
-
|
|
146
|
-
if (result.matchCount > 0) {
|
|
147
|
-
filesWithMatches++;
|
|
148
|
-
totalMatches += result.matchCount;
|
|
149
|
-
|
|
150
|
-
if (request.mode === "content" || !request.mode) {
|
|
151
|
-
for (const m of result.matches) {
|
|
152
|
-
matches.push({
|
|
153
|
-
path: normalizedPath,
|
|
154
|
-
lineNumber: m.lineNumber,
|
|
155
|
-
line: m.line,
|
|
156
|
-
contextBefore: m.contextBefore?.length ? m.contextBefore : undefined,
|
|
157
|
-
contextAfter: m.contextAfter?.length ? m.contextAfter : undefined,
|
|
158
|
-
truncated: m.truncated || undefined,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
} else {
|
|
162
|
-
matches.push({
|
|
163
|
-
path: normalizedPath,
|
|
164
|
-
lineNumber: 0,
|
|
165
|
-
line: "",
|
|
166
|
-
matchCount: result.matchCount,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (result.limitReached || (maxCount !== undefined && totalMatches >= maxCount)) {
|
|
171
|
-
limitReached = true;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return {
|
|
178
|
-
matches,
|
|
179
|
-
totalMatches,
|
|
180
|
-
filesWithMatches,
|
|
181
|
-
filesSearched,
|
|
182
|
-
limitReached: limitReached || undefined,
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
declare const self: Worker;
|
|
187
|
-
|
|
188
|
-
self.addEventListener("message", async (e: MessageEvent<WorkerRequest>) => {
|
|
189
|
-
const msg = e.data;
|
|
190
|
-
|
|
191
|
-
switch (msg.type) {
|
|
192
|
-
case "init":
|
|
193
|
-
self.postMessage({ type: "ready", id: msg.id } satisfies WorkerResponse);
|
|
194
|
-
break;
|
|
195
|
-
|
|
196
|
-
case "grep":
|
|
197
|
-
try {
|
|
198
|
-
const result = await runGrep(msg.request);
|
|
199
|
-
self.postMessage({ type: "result", id: msg.id, result } satisfies WorkerResponse);
|
|
200
|
-
} catch (err) {
|
|
201
|
-
self.postMessage({
|
|
202
|
-
type: "error",
|
|
203
|
-
id: msg.id,
|
|
204
|
-
error: err instanceof Error ? err.message : String(err),
|
|
205
|
-
} satisfies WorkerResponse);
|
|
206
|
-
}
|
|
207
|
-
break;
|
|
208
|
-
|
|
209
|
-
case "destroy":
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
});
|
package/src/html/worker.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker for HTML to Markdown conversion.
|
|
3
|
-
* Uses WASM for actual conversion.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { html_to_markdown } from "../../wasm/pi_natives";
|
|
7
|
-
import type { HtmlRequest, HtmlResponse } from "./types";
|
|
8
|
-
|
|
9
|
-
declare const self: Worker;
|
|
10
|
-
|
|
11
|
-
function respond(msg: HtmlResponse): void {
|
|
12
|
-
self.postMessage(msg);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
self.addEventListener("message", (e: MessageEvent<HtmlRequest>) => {
|
|
16
|
-
const msg = e.data;
|
|
17
|
-
|
|
18
|
-
switch (msg.type) {
|
|
19
|
-
case "init":
|
|
20
|
-
respond({ type: "ready", id: msg.id });
|
|
21
|
-
break;
|
|
22
|
-
|
|
23
|
-
case "destroy":
|
|
24
|
-
break;
|
|
25
|
-
|
|
26
|
-
case "convert": {
|
|
27
|
-
try {
|
|
28
|
-
const markdown = html_to_markdown(msg.html, msg.options);
|
|
29
|
-
respond({ type: "converted", id: msg.id, markdown });
|
|
30
|
-
} catch (err) {
|
|
31
|
-
respond({
|
|
32
|
-
type: "error",
|
|
33
|
-
id: msg.id,
|
|
34
|
-
error: err instanceof Error ? err.message : String(err),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
});
|
package/src/image/types.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Types for image worker communication.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export type ImageRequest =
|
|
6
|
-
| { type: "init"; id: number }
|
|
7
|
-
| { type: "destroy" }
|
|
8
|
-
| {
|
|
9
|
-
type: "load";
|
|
10
|
-
id: number;
|
|
11
|
-
/** Image bytes (transferred, not copied) */
|
|
12
|
-
bytes: Uint8Array;
|
|
13
|
-
}
|
|
14
|
-
| {
|
|
15
|
-
type: "resize";
|
|
16
|
-
id: number;
|
|
17
|
-
/** Handle returned from load */
|
|
18
|
-
handle: number;
|
|
19
|
-
width: number;
|
|
20
|
-
height: number;
|
|
21
|
-
filter: number;
|
|
22
|
-
}
|
|
23
|
-
| {
|
|
24
|
-
type: "get_dimensions";
|
|
25
|
-
id: number;
|
|
26
|
-
handle: number;
|
|
27
|
-
}
|
|
28
|
-
| {
|
|
29
|
-
type: "get_png";
|
|
30
|
-
id: number;
|
|
31
|
-
handle: number;
|
|
32
|
-
}
|
|
33
|
-
| {
|
|
34
|
-
type: "get_jpeg";
|
|
35
|
-
id: number;
|
|
36
|
-
handle: number;
|
|
37
|
-
quality: number;
|
|
38
|
-
}
|
|
39
|
-
| {
|
|
40
|
-
type: "free";
|
|
41
|
-
id: number;
|
|
42
|
-
handle: number;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type ImageResponse =
|
|
46
|
-
| { type: "ready"; id: number }
|
|
47
|
-
| { type: "error"; id: number; error: string }
|
|
48
|
-
| { type: "loaded"; id: number; handle: number; width: number; height: number }
|
|
49
|
-
| { type: "resized"; id: number; handle: number; width: number; height: number }
|
|
50
|
-
| { type: "dimensions"; id: number; width: number; height: number }
|
|
51
|
-
| { type: "bytes"; id: number; bytes: Uint8Array }
|
|
52
|
-
| { type: "freed"; id: number };
|
package/src/image/worker.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Worker for image processing operations.
|
|
3
|
-
* Uses WASM for actual processing, communicates via transferable buffers.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { PhotonImage, type SamplingFilter, resize as wasmResize } from "../../wasm/pi_natives";
|
|
7
|
-
import type { ImageRequest, ImageResponse } from "./types";
|
|
8
|
-
|
|
9
|
-
declare const self: Worker;
|
|
10
|
-
|
|
11
|
-
/** Map of handle -> PhotonImage */
|
|
12
|
-
const images = new Map<number, PhotonImage>();
|
|
13
|
-
let nextHandle = 1;
|
|
14
|
-
|
|
15
|
-
function respond(msg: ImageResponse, transfer?: ArrayBufferLike[]): void {
|
|
16
|
-
if (transfer) {
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
-
self.postMessage(msg, transfer as any);
|
|
19
|
-
} else {
|
|
20
|
-
self.postMessage(msg);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
self.addEventListener("message", (e: MessageEvent<ImageRequest>) => {
|
|
25
|
-
const msg = e.data;
|
|
26
|
-
|
|
27
|
-
switch (msg.type) {
|
|
28
|
-
case "init":
|
|
29
|
-
respond({ type: "ready", id: msg.id });
|
|
30
|
-
break;
|
|
31
|
-
|
|
32
|
-
case "destroy":
|
|
33
|
-
for (const img of images.values()) {
|
|
34
|
-
img.free();
|
|
35
|
-
}
|
|
36
|
-
images.clear();
|
|
37
|
-
break;
|
|
38
|
-
|
|
39
|
-
case "load": {
|
|
40
|
-
try {
|
|
41
|
-
const img = PhotonImage.new_from_byteslice(msg.bytes);
|
|
42
|
-
const handle = nextHandle++;
|
|
43
|
-
images.set(handle, img);
|
|
44
|
-
respond({
|
|
45
|
-
type: "loaded",
|
|
46
|
-
id: msg.id,
|
|
47
|
-
handle,
|
|
48
|
-
width: img.get_width(),
|
|
49
|
-
height: img.get_height(),
|
|
50
|
-
});
|
|
51
|
-
} catch (err) {
|
|
52
|
-
respond({
|
|
53
|
-
type: "error",
|
|
54
|
-
id: msg.id,
|
|
55
|
-
error: err instanceof Error ? err.message : String(err),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
case "resize": {
|
|
62
|
-
const img = images.get(msg.handle);
|
|
63
|
-
if (!img) {
|
|
64
|
-
respond({ type: "error", id: msg.id, error: "Invalid image handle" });
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
try {
|
|
68
|
-
const filter = msg.filter as SamplingFilter;
|
|
69
|
-
const resized = wasmResize(img, msg.width, msg.height, filter);
|
|
70
|
-
const handle = nextHandle++;
|
|
71
|
-
images.set(handle, resized);
|
|
72
|
-
respond({
|
|
73
|
-
type: "resized",
|
|
74
|
-
id: msg.id,
|
|
75
|
-
handle,
|
|
76
|
-
width: resized.get_width(),
|
|
77
|
-
height: resized.get_height(),
|
|
78
|
-
});
|
|
79
|
-
} catch (err) {
|
|
80
|
-
respond({
|
|
81
|
-
type: "error",
|
|
82
|
-
id: msg.id,
|
|
83
|
-
error: err instanceof Error ? err.message : String(err),
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
case "get_dimensions": {
|
|
90
|
-
const img = images.get(msg.handle);
|
|
91
|
-
if (!img) {
|
|
92
|
-
respond({ type: "error", id: msg.id, error: "Invalid image handle" });
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
respond({
|
|
96
|
-
type: "dimensions",
|
|
97
|
-
id: msg.id,
|
|
98
|
-
width: img.get_width(),
|
|
99
|
-
height: img.get_height(),
|
|
100
|
-
});
|
|
101
|
-
break;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
case "get_png": {
|
|
105
|
-
const img = images.get(msg.handle);
|
|
106
|
-
if (!img) {
|
|
107
|
-
respond({ type: "error", id: msg.id, error: "Invalid image handle" });
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const bytes = img.get_bytes();
|
|
112
|
-
respond({ type: "bytes", id: msg.id, bytes }, [bytes.buffer]);
|
|
113
|
-
} catch (err) {
|
|
114
|
-
respond({
|
|
115
|
-
type: "error",
|
|
116
|
-
id: msg.id,
|
|
117
|
-
error: err instanceof Error ? err.message : String(err),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
case "get_jpeg": {
|
|
124
|
-
const img = images.get(msg.handle);
|
|
125
|
-
if (!img) {
|
|
126
|
-
respond({ type: "error", id: msg.id, error: "Invalid image handle" });
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
const bytes = img.get_bytes_jpeg(msg.quality);
|
|
131
|
-
respond({ type: "bytes", id: msg.id, bytes }, [bytes.buffer]);
|
|
132
|
-
} catch (err) {
|
|
133
|
-
respond({
|
|
134
|
-
type: "error",
|
|
135
|
-
id: msg.id,
|
|
136
|
-
error: err instanceof Error ? err.message : String(err),
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
case "free": {
|
|
143
|
-
const img = images.get(msg.handle);
|
|
144
|
-
if (img) {
|
|
145
|
-
img.free();
|
|
146
|
-
images.delete(msg.handle);
|
|
147
|
-
}
|
|
148
|
-
respond({ type: "freed", id: msg.id });
|
|
149
|
-
break;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
});
|