Skip to content

Commit c64fb72

Browse files
committed
Implement WeakNodeApiMultiHost generator
1 parent a0163f5 commit c64fb72

File tree

4 files changed

+240
-2
lines changed

4 files changed

+240
-2
lines changed

packages/weak-node-api/.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
/build-tests/
66
/*.xcframework
77
/*.android.node
8-
/generated/weak_node_api.cpp
9-
/generated/weak_node_api.hpp
8+
/generated/
109

1110
# Copied from node-api-headers by scripts/copy-node-api-headers.ts
1211
/include/

packages/weak-node-api/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ set(GENERATED_SOURCE_DIR "generated")
1313
target_sources(${PROJECT_NAME}
1414
PUBLIC
1515
${GENERATED_SOURCE_DIR}/weak_node_api.cpp
16+
${GENERATED_SOURCE_DIR}/weak_node_api_multi_host.cpp
1617
PUBLIC FILE_SET HEADERS
1718
BASE_DIRS ${GENERATED_SOURCE_DIR} ${INCLUDE_DIR} FILES
1819
${GENERATED_SOURCE_DIR}/weak_node_api.hpp
20+
${GENERATED_SOURCE_DIR}/weak_node_api_multi_host.hpp
1921
${INCLUDE_DIR}/js_native_api_types.h
2022
${INCLUDE_DIR}/js_native_api.h
2123
${INCLUDE_DIR}/node_api_types.h

packages/weak-node-api/scripts/generate-weak-node-api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from "../src/node-api-functions.js";
1010

1111
import * as weakNodeApiGenerator from "./generators/weak-node-api.js";
12+
import * as multiHostGenerator from "./generators/multi-host.js";
1213

1314
export const OUTPUT_PATH = path.join(import.meta.dirname, "../generated");
1415

@@ -51,6 +52,16 @@ async function run() {
5152
fileName: "weak_node_api.cpp",
5253
generator: weakNodeApiGenerator.generateSource,
5354
});
55+
await generateFile({
56+
functions,
57+
fileName: "weak_node_api_multi_host.hpp",
58+
generator: multiHostGenerator.generateHeader,
59+
});
60+
await generateFile({
61+
functions,
62+
fileName: "weak_node_api_multi_host.cpp",
63+
generator: multiHostGenerator.generateSource,
64+
});
5465
}
5566

5667
run().catch((err) => {
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import type { FunctionDecl } from "../../src/node-api-functions.js";
2+
import { generateFunction } from "./shared.js";
3+
4+
const ARGUMENT_NAMES_PR_FUNCTION: Record<string, undefined | string[]> = {
5+
napi_create_threadsafe_function: [
6+
"env",
7+
"func",
8+
"async_resource",
9+
"async_resource_name",
10+
"max_queue_size",
11+
"initial_thread_count",
12+
"thread_finalize_data",
13+
"thread_finalize_cb",
14+
"context",
15+
"call_js_cb",
16+
"result",
17+
],
18+
napi_add_async_cleanup_hook: ["env", "hook", "arg", "remove_handle"],
19+
napi_fatal_error: ["location", "location_len", "message", "message_len"],
20+
};
21+
22+
export function generateFunctionDecl(fn: FunctionDecl) {
23+
return generateFunction({ ...fn, static: true });
24+
}
25+
26+
/**
27+
* Generates source code for a version script for the given Node API version.
28+
*/
29+
export function generateHeader(functions: FunctionDecl[]) {
30+
return `
31+
#pragma once
32+
33+
#include <memory>
34+
#include <type_traits>
35+
#include <vector>
36+
37+
#include <node_api.h>
38+
#include "weak_node_api.hpp"
39+
40+
struct WeakNodeApiMultiHost : WeakNodeApiHost {
41+
template <typename T> struct Wrapped {
42+
static_assert(std::is_same<T, napi_env>::value ||
43+
std::is_same<T, napi_threadsafe_function>::value ||
44+
std::is_same<T, napi_async_cleanup_hook_handle>::value,
45+
"T must be either napi_env, node_api_basic_env, napi_threadsafe_function or napi_async_cleanup_hook_handle");
46+
T value;
47+
std::weak_ptr<WeakNodeApiHost> host;
48+
WeakNodeApiMultiHost *multi_host;
49+
};
50+
51+
napi_env wrap(napi_env value, std::weak_ptr<WeakNodeApiHost>);
52+
napi_threadsafe_function wrap(napi_threadsafe_function value, std::weak_ptr<WeakNodeApiHost>);
53+
napi_async_cleanup_hook_handle wrap(napi_async_cleanup_hook_handle value, std::weak_ptr<WeakNodeApiHost>);
54+
55+
WeakNodeApiMultiHost(
56+
void napi_module_register(napi_module *),
57+
void napi_fatal_error(const char *, size_t, const char *, size_t)
58+
);
59+
60+
private:
61+
std::vector<std::variant<std::unique_ptr<Wrapped<napi_env>>, std::unique_ptr<Wrapped<napi_threadsafe_function>>, std::unique_ptr<Wrapped<napi_async_cleanup_hook_handle>>>> wrapped_values;
62+
63+
public:
64+
65+
${functions.map(generateFunctionDecl).join("\n")}
66+
};
67+
`;
68+
}
69+
70+
function generateFunctionImpl(fn: FunctionDecl) {
71+
const { name, argumentTypes, returnType } = fn;
72+
const [firstArgument] = argumentTypes;
73+
const argumentNames =
74+
ARGUMENT_NAMES_PR_FUNCTION[name] ??
75+
argumentTypes.map((_, index) => `arg${index}`);
76+
if (name === "napi_fatal_error") {
77+
// Providing a default implementation
78+
return generateFunction({
79+
...fn,
80+
namespace: "WeakNodeApiMultiHost",
81+
argumentNames,
82+
body: `
83+
if (location && location_len) {
84+
fprintf(stderr, "Fatal Node-API error: %.*s %.*s",
85+
static_cast<int>(location_len),
86+
location,
87+
static_cast<int>(message_len),
88+
message
89+
);
90+
} else {
91+
fprintf(stderr, "Fatal Node-API error: %.*s", static_cast<int>(message_len), message);
92+
}
93+
abort();
94+
`,
95+
});
96+
} else if (name === "napi_module_register") {
97+
// Providing a default implementation
98+
return generateFunction({
99+
...fn,
100+
namespace: "WeakNodeApiMultiHost",
101+
argumentNames: [""],
102+
body: `
103+
fprintf(stderr, "napi_module_register is not implemented for this WeakNodeApiMultiHost");
104+
abort();
105+
`,
106+
});
107+
} else if (
108+
[
109+
"napi_env",
110+
"node_api_basic_env",
111+
// TODO: Wrap these on creation
112+
"napi_threadsafe_function",
113+
"napi_async_cleanup_hook_handle",
114+
].includes(firstArgument)
115+
) {
116+
const joinedArguments = argumentTypes
117+
.map((_, index) =>
118+
index === 0 ? "wrapped->value" : argumentNames[index],
119+
)
120+
.join(", ");
121+
122+
function generateCall() {
123+
if (name === "napi_create_threadsafe_function") {
124+
return `
125+
auto status = host->${name}(${joinedArguments});
126+
if (status == napi_status::napi_ok) {
127+
*${argumentNames[10]} = wrapped->multi_host->wrap(*${argumentNames[10]}, wrapped->host);
128+
}
129+
return status;
130+
`;
131+
} else if (name === "napi_add_async_cleanup_hook") {
132+
return `
133+
auto status = host->${name}(${joinedArguments});
134+
if (status == napi_status::napi_ok) {
135+
*${argumentNames[3]} = wrapped->multi_host->wrap(*${argumentNames[3]}, wrapped->host);
136+
}
137+
return status;
138+
`;
139+
} else {
140+
return `
141+
${returnType === "void" ? "" : "return"} host->${name}(${joinedArguments});
142+
`;
143+
}
144+
}
145+
146+
return generateFunction({
147+
...fn,
148+
namespace: "WeakNodeApiMultiHost",
149+
argumentNames,
150+
body: `
151+
auto wrapped = reinterpret_cast<Wrapped<${firstArgument}>*>(${argumentNames[0]});
152+
if (auto host = wrapped->host.lock()) {
153+
if (host->${name} == nullptr) {
154+
fprintf(stderr, "Node-API function '${name}' called on a host which doesn't provide an implementation\\n");
155+
return napi_status::napi_generic_failure;
156+
}
157+
${generateCall()}
158+
} else {
159+
fprintf(stderr, "Node-API function '${name}' called after host was destroyed.\\n");
160+
return napi_status::napi_generic_failure;
161+
}
162+
`,
163+
});
164+
} else {
165+
throw new Error(`Unexpected signature for '${name}' Node-API function`);
166+
}
167+
}
168+
169+
/**
170+
* Generates source code for a version script for the given Node API version.
171+
*/
172+
export function generateSource(functions: FunctionDecl[]) {
173+
return `
174+
#include "weak_node_api_multi_host.hpp"
175+
176+
WeakNodeApiMultiHost::WeakNodeApiMultiHost(
177+
void napi_module_register(napi_module *),
178+
void napi_fatal_error(const char *, size_t, const char *, size_t)
179+
)
180+
: WeakNodeApiHost({
181+
${functions
182+
.map(({ name }) => {
183+
if (
184+
name === "napi_module_register" ||
185+
name === "napi_fatal_error"
186+
) {
187+
// We take functions not taking a wrap-able argument via the constructor and call them directly
188+
return `.${name} = ${name} == nullptr ? WeakNodeApiMultiHost::${name} : ${name},`;
189+
} else {
190+
return `.${name} = WeakNodeApiMultiHost::${name},`;
191+
}
192+
})
193+
.join("\n")}
194+
}), wrapped_values{} {};
195+
196+
// TODO: Find a better way to delete these along the way
197+
198+
napi_env WeakNodeApiMultiHost::wrap(napi_env value,
199+
std::weak_ptr<WeakNodeApiHost> host) {
200+
auto ptr = std::make_unique<Wrapped<napi_env>>(value, host, this);
201+
auto raw_ptr = ptr.get();
202+
wrapped_values.push_back(std::move(ptr));
203+
return reinterpret_cast<napi_env>(raw_ptr);
204+
}
205+
206+
napi_threadsafe_function
207+
WeakNodeApiMultiHost::wrap(napi_threadsafe_function value,
208+
std::weak_ptr<WeakNodeApiHost> host) {
209+
auto ptr = std::make_unique<Wrapped<napi_threadsafe_function>>(value, host, this);
210+
auto raw_ptr = ptr.get();
211+
wrapped_values.push_back(std::move(ptr));
212+
return reinterpret_cast<napi_threadsafe_function>(raw_ptr);
213+
}
214+
215+
napi_async_cleanup_hook_handle
216+
WeakNodeApiMultiHost::wrap(napi_async_cleanup_hook_handle value,
217+
std::weak_ptr<WeakNodeApiHost> host) {
218+
auto ptr = std::make_unique<Wrapped<napi_async_cleanup_hook_handle>>(value, host, this);
219+
auto raw_ptr = ptr.get();
220+
wrapped_values.push_back(std::move(ptr));
221+
return reinterpret_cast<napi_async_cleanup_hook_handle>(raw_ptr);
222+
}
223+
224+
${functions.map(generateFunctionImpl).join("\n")}
225+
`;
226+
}

0 commit comments

Comments
 (0)