@lakutata/ffi 0.0.0-stage → 3.1.5

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.
Files changed (129) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +163 -2
  3. package/README.zh-CN.md +140 -0
  4. package/dist/cjs/components/NativeLibrary.d.ts +229 -0
  5. package/dist/cjs/components/NativeLibrary.js +346 -0
  6. package/dist/cjs/decorators/Library.d.ts +61 -0
  7. package/dist/cjs/decorators/Library.js +44 -0
  8. package/dist/cjs/decorators/Native.d.ts +69 -0
  9. package/dist/cjs/decorators/Native.js +90 -0
  10. package/dist/cjs/exceptions/NativeDeclarationException.d.ts +12 -0
  11. package/dist/cjs/exceptions/NativeDeclarationException.js +19 -0
  12. package/dist/cjs/exceptions/NativeLibraryClosedException.d.ts +12 -0
  13. package/dist/cjs/exceptions/NativeLibraryClosedException.js +19 -0
  14. package/dist/cjs/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
  15. package/dist/cjs/exceptions/NativeLibraryNotFoundException.js +18 -0
  16. package/dist/cjs/exceptions/NativeMemoryException.d.ts +11 -0
  17. package/dist/cjs/exceptions/NativeMemoryException.js +18 -0
  18. package/dist/cjs/exports/FFI.d.ts +12 -0
  19. package/dist/cjs/exports/FFI.js +40 -0
  20. package/dist/cjs/exports/Generator.d.ts +2 -0
  21. package/dist/cjs/exports/Generator.js +10 -0
  22. package/dist/cjs/lib/Memory.d.ts +188 -0
  23. package/dist/cjs/lib/Memory.js +227 -0
  24. package/dist/cjs/lib/Runtime.d.ts +98 -0
  25. package/dist/cjs/lib/Runtime.js +209 -0
  26. package/dist/cjs/lib/backend/Functions.d.ts +65 -0
  27. package/dist/cjs/lib/backend/Functions.js +214 -0
  28. package/dist/cjs/lib/backend/KoffiTypes.d.ts +53 -0
  29. package/dist/cjs/lib/backend/KoffiTypes.js +426 -0
  30. package/dist/cjs/lib/declarations/Builtins.d.ts +33 -0
  31. package/dist/cjs/lib/declarations/Builtins.js +179 -0
  32. package/dist/cjs/lib/declarations/Layout.d.ts +41 -0
  33. package/dist/cjs/lib/declarations/Layout.js +180 -0
  34. package/dist/cjs/lib/declarations/Lexer.d.ts +40 -0
  35. package/dist/cjs/lib/declarations/Lexer.js +325 -0
  36. package/dist/cjs/lib/declarations/Parser.d.ts +90 -0
  37. package/dist/cjs/lib/declarations/Parser.js +1425 -0
  38. package/dist/cjs/lib/declarations/Platform.d.ts +50 -0
  39. package/dist/cjs/lib/declarations/Platform.js +27 -0
  40. package/dist/cjs/lib/declarations/Types.d.ts +173 -0
  41. package/dist/cjs/lib/declarations/Types.js +179 -0
  42. package/dist/cjs/lib/generator/CText.d.ts +14 -0
  43. package/dist/cjs/lib/generator/CText.js +133 -0
  44. package/dist/cjs/lib/generator/Generator.d.ts +136 -0
  45. package/dist/cjs/lib/generator/Generator.js +395 -0
  46. package/dist/cjs/lib/generator/Preprocessor.d.ts +60 -0
  47. package/dist/cjs/lib/generator/Preprocessor.js +94 -0
  48. package/dist/cjs/lib/generator/TypeScriptTypes.d.ts +35 -0
  49. package/dist/cjs/lib/generator/TypeScriptTypes.js +153 -0
  50. package/dist/cjs/lib/library/NativeCallback.d.ts +44 -0
  51. package/dist/cjs/lib/library/NativeCallback.js +66 -0
  52. package/dist/cjs/lib/library/NativeLibraryHandle.d.ts +268 -0
  53. package/dist/cjs/lib/library/NativeLibraryHandle.js +540 -0
  54. package/dist/cjs/lib/library/Resolve.d.ts +49 -0
  55. package/dist/cjs/lib/library/Resolve.js +130 -0
  56. package/dist/cjs/lib/thread/NativeThreads.d.ts +80 -0
  57. package/dist/cjs/lib/thread/NativeThreads.js +384 -0
  58. package/dist/cjs/lib/thread/NativeWorker.d.ts +6 -0
  59. package/dist/cjs/lib/thread/NativeWorker.js +96 -0
  60. package/dist/cjs/lib/thread/NativeWorkerCode.d.ts +5 -0
  61. package/dist/cjs/lib/thread/NativeWorkerCode.js +4 -0
  62. package/dist/cjs/lib/thread/PackageRequire.d.ts +6 -0
  63. package/dist/cjs/lib/thread/PackageRequire.js +4 -0
  64. package/dist/cjs/lib/thread/ThreadProtocol.d.ts +77 -0
  65. package/dist/cjs/lib/thread/ThreadProtocol.js +2 -0
  66. package/dist/cjs/package.json +1 -0
  67. package/dist/esm/components/NativeLibrary.js +338 -0
  68. package/dist/esm/decorators/Library.js +40 -0
  69. package/dist/esm/decorators/Native.js +85 -0
  70. package/dist/esm/exceptions/NativeDeclarationException.js +15 -0
  71. package/dist/esm/exceptions/NativeLibraryClosedException.js +15 -0
  72. package/dist/esm/exceptions/NativeLibraryNotFoundException.js +14 -0
  73. package/dist/esm/exceptions/NativeMemoryException.js +14 -0
  74. package/dist/esm/exports/FFI.js +10 -0
  75. package/dist/esm/exports/Generator.js +2 -0
  76. package/dist/esm/lib/Memory.js +210 -0
  77. package/dist/esm/lib/Runtime.js +192 -0
  78. package/dist/esm/lib/backend/Functions.js +206 -0
  79. package/dist/esm/lib/backend/KoffiTypes.js +418 -0
  80. package/dist/esm/lib/declarations/Builtins.js +174 -0
  81. package/dist/esm/lib/declarations/Layout.js +175 -0
  82. package/dist/esm/lib/declarations/Lexer.js +321 -0
  83. package/dist/esm/lib/declarations/Parser.js +1421 -0
  84. package/dist/esm/lib/declarations/Platform.js +23 -0
  85. package/dist/esm/lib/declarations/Types.js +170 -0
  86. package/dist/esm/lib/generator/CText.js +129 -0
  87. package/dist/esm/lib/generator/Generator.js +388 -0
  88. package/dist/esm/lib/generator/Preprocessor.js +87 -0
  89. package/dist/esm/lib/generator/TypeScriptTypes.js +149 -0
  90. package/dist/esm/lib/library/NativeCallback.js +59 -0
  91. package/dist/esm/lib/library/NativeLibraryHandle.js +532 -0
  92. package/dist/esm/lib/library/Resolve.js +122 -0
  93. package/dist/esm/lib/thread/NativeThreads.js +377 -0
  94. package/dist/esm/lib/thread/NativeWorker.js +93 -0
  95. package/dist/esm/lib/thread/NativeWorkerCode.js +1 -0
  96. package/dist/esm/lib/thread/PackageRequire.js +2 -0
  97. package/dist/esm/lib/thread/ThreadProtocol.js +1 -0
  98. package/dist/types/components/NativeLibrary.d.ts +229 -0
  99. package/dist/types/decorators/Library.d.ts +61 -0
  100. package/dist/types/decorators/Native.d.ts +69 -0
  101. package/dist/types/exceptions/NativeDeclarationException.d.ts +12 -0
  102. package/dist/types/exceptions/NativeLibraryClosedException.d.ts +12 -0
  103. package/dist/types/exceptions/NativeLibraryNotFoundException.d.ts +11 -0
  104. package/dist/types/exceptions/NativeMemoryException.d.ts +11 -0
  105. package/dist/types/exports/FFI.d.ts +12 -0
  106. package/dist/types/exports/Generator.d.ts +2 -0
  107. package/dist/types/lib/Memory.d.ts +188 -0
  108. package/dist/types/lib/Runtime.d.ts +98 -0
  109. package/dist/types/lib/backend/Functions.d.ts +65 -0
  110. package/dist/types/lib/backend/KoffiTypes.d.ts +53 -0
  111. package/dist/types/lib/declarations/Builtins.d.ts +33 -0
  112. package/dist/types/lib/declarations/Layout.d.ts +41 -0
  113. package/dist/types/lib/declarations/Lexer.d.ts +40 -0
  114. package/dist/types/lib/declarations/Parser.d.ts +90 -0
  115. package/dist/types/lib/declarations/Platform.d.ts +50 -0
  116. package/dist/types/lib/declarations/Types.d.ts +173 -0
  117. package/dist/types/lib/generator/CText.d.ts +14 -0
  118. package/dist/types/lib/generator/Generator.d.ts +136 -0
  119. package/dist/types/lib/generator/Preprocessor.d.ts +60 -0
  120. package/dist/types/lib/generator/TypeScriptTypes.d.ts +35 -0
  121. package/dist/types/lib/library/NativeCallback.d.ts +44 -0
  122. package/dist/types/lib/library/NativeLibraryHandle.d.ts +268 -0
  123. package/dist/types/lib/library/Resolve.d.ts +49 -0
  124. package/dist/types/lib/thread/NativeThreads.d.ts +80 -0
  125. package/dist/types/lib/thread/NativeWorker.d.ts +6 -0
  126. package/dist/types/lib/thread/NativeWorkerCode.d.ts +5 -0
  127. package/dist/types/lib/thread/PackageRequire.d.ts +6 -0
  128. package/dist/types/lib/thread/ThreadProtocol.d.ts +77 -0
  129. package/package.json +85 -4
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fileNames = fileNames;
7
+ exports.libraryNames = libraryNames;
8
+ exports.loadLibrary = loadLibrary;
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const koffi_1 = __importDefault(require("koffi"));
12
+ const NativeLibraryNotFoundException_js_1 = require("../../exceptions/NativeLibraryNotFoundException.js");
13
+ /**
14
+ * The file of the dynamic loader of musl, which holds its C library (Alpine has no libc.so.6)
15
+ */
16
+ function muslLoader() {
17
+ try {
18
+ const loader = (0, node_fs_1.readdirSync)('/lib').find((name) => /^ld-musl-.*\.so\.1$/.test(name));
19
+ return loader ? `/lib/${loader}` : undefined;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ /**
26
+ * The file of the C library (c) or of its mathematical functions (m) on a system
27
+ * @param name
28
+ * @param os
29
+ */
30
+ function systemLibrary(name, os) {
31
+ switch (os) {
32
+ case 'darwin':
33
+ return '/usr/lib/libSystem.B.dylib';
34
+ case 'win32':
35
+ return 'ucrtbase.dll';
36
+ case 'linux':
37
+ return muslLoader() ?? (name === 'c' ? 'libc.so.6' : 'libm.so.6');
38
+ case 'freebsd':
39
+ return name === 'c' ? 'libc.so.7' : 'libm.so.5';
40
+ default:
41
+ return name === 'c' ? 'libc.so' : 'libm.so';
42
+ }
43
+ }
44
+ /**
45
+ * The file names a name gives on a system: the name itself when it has a directory or an extension, else the names the
46
+ * system gives libraries
47
+ * @param name
48
+ * @param os
49
+ */
50
+ function fileNames(name, os = process.platform) {
51
+ if (name === 'c' || name === 'm')
52
+ return [systemLibrary(name, os)];
53
+ if (/[/\\]/.test(name) || /\.(dylib|dll|so)(\.\d+)*$/i.test(name))
54
+ return [name];
55
+ switch (os) {
56
+ case 'darwin':
57
+ return [`lib${name}.dylib`, `${name}.dylib`];
58
+ case 'win32':
59
+ return [`${name}.dll`, `lib${name}.dll`];
60
+ default:
61
+ return [`lib${name}.so`, `${name}.so`];
62
+ }
63
+ }
64
+ /**
65
+ * The names of a library on a system
66
+ * @param names
67
+ * @param os
68
+ */
69
+ function libraryNames(names, os = process.platform) {
70
+ if (typeof names === 'string')
71
+ return fileNames(names, os);
72
+ const given = names[os] ?? names.default;
73
+ if (given === undefined)
74
+ throw new NativeLibraryNotFoundException_js_1.NativeLibraryNotFoundException(`The library has no name for ${os} (give one for ${os}, or a default)`);
75
+ return (typeof given === 'string' ? [given] : given).flatMap((name) => fileNames(name, os));
76
+ }
77
+ /**
78
+ * Load a library: the file given; else each name in the search directories, in the directory of a packaged
79
+ * application, then where the system looks for libraries (dlopen, LoadLibrary)
80
+ * @param names
81
+ * @param options
82
+ * @returns The library and the file it was loaded from.
83
+ */
84
+ function loadLibrary(names, options = {}) {
85
+ const attempts = [];
86
+ const attempt = (file) => {
87
+ try {
88
+ return koffi_1.default.load(file);
89
+ }
90
+ catch (error) {
91
+ attempts.push(`${file}: ${error.message}`);
92
+ return undefined;
93
+ }
94
+ };
95
+ if (options.path) {
96
+ const file = node_path_1.default.resolve(options.path);
97
+ const library = attempt(file);
98
+ if (library)
99
+ return { library: library, file: file };
100
+ throw new NativeLibraryNotFoundException_js_1.NativeLibraryNotFoundException(`The library cannot be loaded:\n${attempts.join('\n')}`);
101
+ }
102
+ const directories = [...options.search ?? [], ...options.packaged ? [options.packaged] : []].map((directory) => node_path_1.default.resolve(directory));
103
+ const candidates = libraryNames(names);
104
+ for (const candidate of candidates) {
105
+ //A name with a directory: that file only
106
+ if (/[/\\]/.test(candidate)) {
107
+ const library = attempt(candidate);
108
+ if (library)
109
+ return { library: library, file: candidate };
110
+ continue;
111
+ }
112
+ for (const directory of directories) {
113
+ const file = node_path_1.default.join(directory, candidate);
114
+ if (!(0, node_fs_1.existsSync)(file)) {
115
+ attempts.push(`${file}: not found`);
116
+ continue;
117
+ }
118
+ const library = attempt(file);
119
+ if (library)
120
+ return { library: library, file: file };
121
+ }
122
+ }
123
+ for (const candidate of candidates.filter((name) => !/[/\\]/.test(name))) {
124
+ const library = attempt(candidate);
125
+ if (library)
126
+ return { library: library, file: candidate };
127
+ }
128
+ const versioned = process.platform === 'linux' && candidates.some((name) => name.endsWith('.so')) ? '\nOn Linux, the libraries of the system are often installed with their version only (libsqlite3.so.0): give that name.' : '';
129
+ throw new NativeLibraryNotFoundException_js_1.NativeLibraryNotFoundException(`The library ${candidates.join(' or ')} cannot be loaded:\n${attempts.join('\n')}${versioned}`);
130
+ }
@@ -0,0 +1,80 @@
1
+ import type { FunctionDeclaration } from '../declarations/Types.js';
2
+ import type { ThreadFunctionOptions } from './ThreadProtocol.js';
3
+ /**
4
+ * A function called in the threads: how a thread binds it
5
+ */
6
+ export interface ThreadFunction {
7
+ /**
8
+ * Its prototype, or the name of a declared function
9
+ */
10
+ readonly declaration: string;
11
+ readonly named: boolean;
12
+ readonly options: ThreadFunctionOptions;
13
+ /**
14
+ * Its declaration, read by the thread of the library: which arguments are written by the function, which are
15
+ * callbacks
16
+ */
17
+ readonly type: FunctionDeclaration;
18
+ }
19
+ /**
20
+ * The result of a call in a thread: its value, and the errno of the thread after it
21
+ */
22
+ export interface ThreadResult {
23
+ readonly value: unknown;
24
+ readonly errno: number;
25
+ readonly windowsError?: number;
26
+ }
27
+ /**
28
+ * The worker threads of a library: they load it, and run its asynchronous calls (synchronously, each in its thread);
29
+ * with one thread, in the order they are made. The arguments are copied to the thread and the ones the function writes
30
+ * copied back; a JS function given as a callback is registered for the call in the thread of the library, which runs it
31
+ * when the library calls it
32
+ */
33
+ export declare class NativeThreads {
34
+ #private;
35
+ /**
36
+ * Start the threads of a library.
37
+ * @param file The file of the library, as loaded by this thread.
38
+ * @param declarations Its C declarations.
39
+ * @param count The number of threads.
40
+ * @param crt On Windows, the C library whose errno is read after each call.
41
+ */
42
+ constructor(file: string, declarations: string, count: number, crt?: string);
43
+ /**
44
+ * Settled once every thread has loaded the library: rejected with the error of a thread that could not.
45
+ */
46
+ get ready(): Promise<void>;
47
+ /**
48
+ * The number of threads.
49
+ */
50
+ get count(): number;
51
+ /**
52
+ * Add declarations to the ones of the threads (and of the threads started later).
53
+ * @param declarations The declarations.
54
+ */
55
+ declare(declarations: string): void;
56
+ /**
57
+ * Bind a function in the threads now rather than at its first call: when the library is made, while the threads
58
+ * start (a thread started later binds it at its first call in it).
59
+ * @param fn The function.
60
+ */
61
+ bind(fn: ThreadFunction): void;
62
+ /**
63
+ * Call a function in the least busy thread (with one thread, after the calls made before).
64
+ * @param fn The function.
65
+ * @param args Its arguments.
66
+ * @returns Its value and the errno of the thread after it.
67
+ */
68
+ call(fn: ThreadFunction, args: unknown[]): Promise<ThreadResult>;
69
+ /**
70
+ * Stop a thread as if it had ended on its own (for the tests): its calls fail, the next call starts a new one.
71
+ * @param index The index of the thread.
72
+ */
73
+ stopThread(index: number): Promise<void>;
74
+ /**
75
+ * Close the threads: each unloads the library and ends; the ones still running after a while are stopped. A thread
76
+ * still running a call (its native code did not return) is stopped at once, the call rejected: the thread ends
77
+ * once the native code returns.
78
+ */
79
+ close(): Promise<void>;
80
+ }
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.NativeThreads = void 0;
7
+ const node_worker_threads_1 = require("node:worker_threads");
8
+ const koffi_1 = __importDefault(require("koffi"));
9
+ const NativeDeclarationException_js_1 = require("../../exceptions/NativeDeclarationException.js");
10
+ const NativeLibraryClosedException_js_1 = require("../../exceptions/NativeLibraryClosedException.js");
11
+ const NativeLibraryNotFoundException_js_1 = require("../../exceptions/NativeLibraryNotFoundException.js");
12
+ const NativeMemoryException_js_1 = require("../../exceptions/NativeMemoryException.js");
13
+ const Functions_js_1 = require("../backend/Functions.js");
14
+ const Runtime_js_1 = require("../Runtime.js");
15
+ const NativeWorkerCode_js_1 = require("./NativeWorkerCode.js");
16
+ const PackageRequire_js_1 = require("./PackageRequire.js");
17
+ /**
18
+ * How long the threads have to close once asked, in milliseconds
19
+ */
20
+ const CLOSE_TIMEOUT = 5000;
21
+ /**
22
+ * An error of a thread, made again with its class
23
+ * @param error
24
+ */
25
+ function errorOf(error) {
26
+ const classes = {
27
+ NativeDeclarationException: NativeDeclarationException_js_1.NativeDeclarationException,
28
+ NativeLibraryClosedException: NativeLibraryClosedException_js_1.NativeLibraryClosedException,
29
+ NativeLibraryNotFoundException: NativeLibraryNotFoundException_js_1.NativeLibraryNotFoundException,
30
+ NativeMemoryException: NativeMemoryException_js_1.NativeMemoryException,
31
+ TypeError: TypeError,
32
+ RangeError: RangeError
33
+ };
34
+ const made = new (classes[error.name] ?? Error)(error.message);
35
+ if (!classes[error.name])
36
+ made.name = error.name;
37
+ return made;
38
+ }
39
+ /**
40
+ * The value a callback returns to the library when it throws: zero, false, a null pointer
41
+ * @param type
42
+ */
43
+ function zeroOf(type) {
44
+ if (type.kind === 'primitive')
45
+ return type.name === 'void' ? undefined : type.name === 'bool' ? false : 0;
46
+ if (type.kind === 'enum')
47
+ return 0;
48
+ if (type.kind === 'pointer')
49
+ return null;
50
+ return {};
51
+ }
52
+ /**
53
+ * Whether a parameter points to memory the function may write: not const, or marked _Out_ or _Inout_
54
+ * @param parameter
55
+ */
56
+ function written(parameter) {
57
+ if (!parameter)
58
+ return true;
59
+ if (parameter.direction !== 'in')
60
+ return true;
61
+ return parameter.type.kind === 'pointer' && !parameter.type.constTarget;
62
+ }
63
+ /**
64
+ * The worker threads of a library: they load it, and run its asynchronous calls (synchronously, each in its thread);
65
+ * with one thread, in the order they are made. The arguments are copied to the thread and the ones the function writes
66
+ * copied back; a JS function given as a callback is registered for the call in the thread of the library, which runs it
67
+ * when the library calls it
68
+ */
69
+ class NativeThreads {
70
+ #data;
71
+ #count;
72
+ #slots;
73
+ #declarations = [];
74
+ #nextFunction = 1;
75
+ #nextCall = 1;
76
+ #closed = false;
77
+ /**
78
+ * Start the threads of a library.
79
+ * @param file The file of the library, as loaded by this thread.
80
+ * @param declarations Its C declarations.
81
+ * @param count The number of threads.
82
+ * @param crt On Windows, the C library whose errno is read after each call.
83
+ */
84
+ constructor(file, declarations, count, crt) {
85
+ if (!NativeWorkerCode_js_1.nativeWorkerCode || !PackageRequire_js_1.packageRequire)
86
+ throw new Error('The code of the worker threads of the libraries is not built (scripts/worker.mjs)');
87
+ this.#data = { koffi: PackageRequire_js_1.packageRequire.resolve('koffi'), file: file, declarations: declarations, ...crt ? { crt: crt } : {} };
88
+ this.#count = count;
89
+ this.#slots = Array.from({ length: count }, () => this.#start());
90
+ }
91
+ /**
92
+ * Settled once every thread has loaded the library: rejected with the error of a thread that could not.
93
+ */
94
+ get ready() {
95
+ return Promise.all(this.#slots.map((slot) => slot?.ready ?? Promise.resolve())).then(() => undefined);
96
+ }
97
+ /**
98
+ * The number of threads.
99
+ */
100
+ get count() {
101
+ return this.#count;
102
+ }
103
+ /**
104
+ * Add declarations to the ones of the threads (and of the threads started later).
105
+ * @param declarations The declarations.
106
+ */
107
+ declare(declarations) {
108
+ this.#declarations.push(declarations);
109
+ for (const slot of this.#slots)
110
+ if (slot && !slot.exited)
111
+ this.#post(slot, { type: 'declare', declarations: declarations });
112
+ }
113
+ /**
114
+ * Bind a function in the threads now rather than at its first call: when the library is made, while the threads
115
+ * start (a thread started later binds it at its first call in it).
116
+ * @param fn The function.
117
+ */
118
+ bind(fn) {
119
+ if (this.#closed)
120
+ return;
121
+ for (const slot of this.#slots)
122
+ if (slot && !slot.exited)
123
+ this.#bound(slot, fn);
124
+ }
125
+ /**
126
+ * Call a function in the least busy thread (with one thread, after the calls made before).
127
+ * @param fn The function.
128
+ * @param args Its arguments.
129
+ * @returns Its value and the errno of the thread after it.
130
+ */
131
+ call(fn, args) {
132
+ if (this.#closed)
133
+ return Promise.reject(new NativeLibraryClosedException_js_1.NativeLibraryClosedException(`The library ${this.#data.file} is closed`));
134
+ const slot = this.#leastBusy();
135
+ const id = this.#bound(slot, fn);
136
+ const type = fn.type.type;
137
+ const passed = args.slice();
138
+ const back = [];
139
+ const views = new Map();
140
+ const transfer = [];
141
+ const callbacks = [];
142
+ let callbackError;
143
+ for (let index = 0; index < passed.length; index++) {
144
+ const value = passed[index];
145
+ const parameter = type.parameters[index];
146
+ //A JS function given as a callback: registered for the call, run in this thread when the library calls it
147
+ if (typeof value === 'function') {
148
+ const target = parameter?.type.kind === 'pointer' ? parameter.type.to : undefined;
149
+ if (!target || target.kind !== 'function')
150
+ return this.#refuse(callbacks, new TypeError(`The argument ${index} of ${fn.type.name} is a function, but its parameter is not a pointer to a function`));
151
+ const prototype = target;
152
+ const zero = zeroOf(prototype.result);
153
+ const safe = (...callbackArgs) => {
154
+ try {
155
+ return value(...callbackArgs);
156
+ }
157
+ catch (error) {
158
+ callbackError ??= { error: error };
159
+ return zero;
160
+ }
161
+ };
162
+ const pointer = koffi_1.default.register((0, Functions_js_1.convertedCallback)(prototype, Runtime_js_1.TYPES, safe), koffi_1.default.pointer(Runtime_js_1.TYPES.prototype(prototype)));
163
+ callbacks.push(pointer);
164
+ passed[index] = pointer;
165
+ continue;
166
+ }
167
+ //A buffer: its own bytes only, copied and transferred (a Buffer of the pool would copy all its pool); copied
168
+ //back after the call when the function may write it
169
+ if (ArrayBuffer.isView(value)) {
170
+ const bytes = new Uint8Array(value.byteLength);
171
+ bytes.set(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
172
+ //A Buffer as its bytes (koffi takes them as it takes the Buffer)
173
+ const copy = Buffer.isBuffer(value) || value instanceof Uint8Array ? bytes : value instanceof DataView ? new DataView(bytes.buffer) : new value.constructor(bytes.buffer);
174
+ passed[index] = copy;
175
+ transfer.push(bytes.buffer);
176
+ if (written(parameter)) {
177
+ back.push(index);
178
+ views.set(index, value);
179
+ }
180
+ continue;
181
+ }
182
+ //An output parameter: an array of one element, an object filled by the function
183
+ if (parameter && parameter.direction !== 'in' && value && typeof value === 'object')
184
+ back.push(index);
185
+ }
186
+ const callId = this.#nextCall++;
187
+ return new Promise((resolve, reject) => {
188
+ const release = () => {
189
+ for (const pointer of callbacks)
190
+ koffi_1.default.unregister(pointer);
191
+ };
192
+ slot.calls.set(callId, {
193
+ resolve: resolve,
194
+ reject: (error) => {
195
+ release();
196
+ reject(error);
197
+ },
198
+ abandon: reject,
199
+ settle: (message) => {
200
+ release();
201
+ //The arguments the function wrote, given back to the caller
202
+ back.forEach((index, at) => {
203
+ const given = args[index];
204
+ const returned = message.back[at];
205
+ const view = views.get(index);
206
+ if (view && ArrayBuffer.isView(returned))
207
+ new Uint8Array(view.buffer, view.byteOffset, view.byteLength).set(new Uint8Array(returned.buffer, returned.byteOffset, returned.byteLength));
208
+ else if (Array.isArray(given) && Array.isArray(returned))
209
+ given.splice(0, given.length, ...returned);
210
+ else if (given && typeof given === 'object' && returned && typeof returned === 'object')
211
+ Object.assign(given, returned);
212
+ });
213
+ if (callbackError)
214
+ return reject(callbackError.error);
215
+ if (message.error)
216
+ return reject(errorOf(message.error));
217
+ resolve({ value: message.value, errno: message.errno, ...message.windowsError !== undefined ? { windowsError: message.windowsError } : {} });
218
+ }
219
+ });
220
+ try {
221
+ this.#post(slot, { type: 'call', id: callId, function: id, args: passed, back: back }, transfer);
222
+ }
223
+ catch (error) {
224
+ slot.calls.delete(callId);
225
+ release();
226
+ reject(error);
227
+ }
228
+ });
229
+ }
230
+ /**
231
+ * Stop a thread as if it had ended on its own (for the tests): its calls fail, the next call starts a new one.
232
+ * @param index The index of the thread.
233
+ */
234
+ async stopThread(index) {
235
+ await this.#slots[index]?.worker.terminate();
236
+ }
237
+ /**
238
+ * Close the threads: each unloads the library and ends; the ones still running after a while are stopped. A thread
239
+ * still running a call (its native code did not return) is stopped at once, the call rejected: the thread ends
240
+ * once the native code returns.
241
+ */
242
+ async close() {
243
+ if (this.#closed)
244
+ return;
245
+ this.#closed = true;
246
+ await Promise.all(this.#slots.map(async (slot) => {
247
+ if (!slot || slot.exited)
248
+ return;
249
+ if (slot.calls.size) {
250
+ const error = new NativeLibraryClosedException_js_1.NativeLibraryClosedException(`The library ${this.#data.file} is closed while a call runs in its thread`);
251
+ for (const call of slot.calls.values())
252
+ call.abandon(error);
253
+ slot.calls.clear();
254
+ this.#stop(slot);
255
+ return;
256
+ }
257
+ const exited = new Promise((resolve) => {
258
+ slot.worker.once('exit', () => resolve());
259
+ });
260
+ //A thread still loading the library gets it once loaded: its port keeps the messages until it listens
261
+ this.#post(slot, { type: 'close' });
262
+ let timer;
263
+ const timeout = new Promise((resolve) => {
264
+ timer = setTimeout(() => resolve(true), CLOSE_TIMEOUT);
265
+ });
266
+ const late = await Promise.race([exited, timeout]);
267
+ clearTimeout(timer);
268
+ if (late)
269
+ this.#stop(slot);
270
+ }));
271
+ }
272
+ /**
273
+ * A thread stopped, not awaited: a thread running native code ends only once it returns
274
+ * @param slot
275
+ */
276
+ #stop(slot) {
277
+ slot.worker.terminate().catch(() => undefined);
278
+ }
279
+ /**
280
+ * A call refused before it is sent: its callbacks released
281
+ * @param callbacks
282
+ * @param error
283
+ */
284
+ #refuse(callbacks, error) {
285
+ for (const pointer of callbacks)
286
+ koffi_1.default.unregister(pointer);
287
+ return Promise.reject(error);
288
+ }
289
+ /**
290
+ * The identifier of a function in a thread, which binds it once
291
+ * @param slot
292
+ * @param fn
293
+ */
294
+ #bound(slot, fn) {
295
+ let id = slot.bound.get(fn);
296
+ if (id === undefined) {
297
+ id = this.#nextFunction++;
298
+ slot.bound.set(fn, id);
299
+ this.#post(slot, { type: 'bind', id: id, declaration: fn.declaration, named: fn.named, options: fn.options });
300
+ }
301
+ return id;
302
+ }
303
+ /**
304
+ * The thread with the fewest calls waiting, started again if it ended
305
+ */
306
+ #leastBusy() {
307
+ for (let index = 0; index < this.#slots.length; index++) {
308
+ if (!this.#slots[index] || this.#slots[index].exited)
309
+ this.#slots[index] = this.#start();
310
+ }
311
+ let chosen = this.#slots[0];
312
+ for (const slot of this.#slots)
313
+ if (slot.calls.size < chosen.calls.size)
314
+ chosen = slot;
315
+ return chosen;
316
+ }
317
+ /**
318
+ * A message to a thread
319
+ * @param slot
320
+ * @param message
321
+ * @param transfer
322
+ */
323
+ #post(slot, message, transfer = []) {
324
+ slot.worker.postMessage(message, transfer);
325
+ //Kept alive while calls wait in it
326
+ if (message.type === 'call')
327
+ slot.worker.ref();
328
+ }
329
+ /**
330
+ * A thread started: it loads the library, then serves its calls
331
+ */
332
+ #start() {
333
+ const worker = new node_worker_threads_1.Worker(NativeWorkerCode_js_1.nativeWorkerCode, { eval: true, workerData: { ...this.#data, declarations: [this.#data.declarations, ...this.#declarations].join('\n') } });
334
+ let readied = () => undefined;
335
+ let failed = () => undefined;
336
+ const ready = new Promise((resolve, reject) => {
337
+ readied = resolve;
338
+ failed = reject;
339
+ });
340
+ //Not unhandled when nothing awaits it: the calls report the failure
341
+ ready.catch(() => undefined);
342
+ const slot = { worker: worker, ready: ready, calls: new Map(), bound: new Map(), exited: false };
343
+ //A thread keeps the process alive while it starts and while calls wait in it, not once idle
344
+ const refresh = () => {
345
+ if (!slot.calls.size)
346
+ worker.unref();
347
+ };
348
+ worker.on('message', (message) => {
349
+ switch (message.type) {
350
+ case 'ready':
351
+ readied();
352
+ refresh();
353
+ return;
354
+ case 'failed':
355
+ slot.error = errorOf(message.error);
356
+ failed(slot.error);
357
+ refresh();
358
+ return;
359
+ case 'result': {
360
+ const call = slot.calls.get(message.id);
361
+ slot.calls.delete(message.id);
362
+ refresh();
363
+ call?.settle(message);
364
+ return;
365
+ }
366
+ case 'closed':
367
+ return;
368
+ }
369
+ });
370
+ const end = (error) => {
371
+ if (slot.exited)
372
+ return;
373
+ slot.exited = true;
374
+ failed(error);
375
+ for (const call of slot.calls.values())
376
+ call.reject(slot.error ?? error);
377
+ slot.calls.clear();
378
+ };
379
+ worker.on('error', (error) => end(error));
380
+ worker.on('exit', (code) => end(new Error(`The worker thread of the library ${this.#data.file} ended (exit code ${code})`)));
381
+ return slot;
382
+ }
383
+ }
384
+ exports.NativeThreads = NativeThreads;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Serve the calls of a library in this worker thread: it loads the library, binds the functions it is asked to bind
3
+ * (synchronous: the thread is the one waiting), calls them in the order of the messages, and sends back their results,
4
+ * the arguments asked back (the output parameters, the buffers written) and its errno after each call
5
+ */
6
+ export declare function runNativeWorker(): void;