@amxts/core 0.1.0 → 0.2.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 +18 -23
- package/README.ru.md +19 -22
- package/as/amxts.d.ts +63 -3
- package/as/constants.ts +684 -116
- package/as/entities.ts +1905 -1364
- package/as/events.ts +1694 -905
- package/as/facade.ts +1240 -220
- package/as/fetch.ts +1519 -0
- package/as/flags.ts +159 -145
- package/as/hlds.ts +1071 -0
- package/as/hooks.ts +5226 -5379
- package/as/kit.ts +15 -5
- package/as/lib/check.ts +1 -1
- package/as/natives.ts +2658 -2499
- package/as/remote.ts +39 -15
- package/as/tsconfig.json +6 -2
- package/as/vector.ts +12 -9
- package/includes/order.txt +22 -29
- package/includes/sources.json +0 -8
- package/node_modules/assemblyscript/std/assembly/index.d.ts +20 -11
- package/package.json +29 -7
- package/runtime/deps/assemblyscript/dist/asc.js +86 -16
- package/runtime/deps/assemblyscript/dist/assemblyscript.js +29 -29
- package/runtime/licenses/MPL-2.0.txt +373 -0
- package/runtime/licenses/README.md +5 -1
- package/runtime/licenses/curl-COPYING +22 -0
- package/runtime/licenses/libssh2-COPYING +43 -0
- package/runtime/licenses/mbedTLS-LICENSE +553 -0
- package/runtime/natives.txt +108 -105
- package/runtime/patches/assemblyscript-0.28.20-amxts.patch +752 -296
- package/runtime/patches/wamr-2.4.5-amxts.patch +148 -0
- package/scripts/apply-docs.ts +2 -2
- package/scripts/auto-imports.ts +2 -2
- package/scripts/build-identity.ts +115 -0
- package/scripts/build-wasm.ts +101 -50
- package/scripts/check.ts +17 -3
- package/scripts/client-messages.ts +246 -0
- package/scripts/compile-pool.ts +128 -0
- package/scripts/compile-worker.ts +23 -0
- package/scripts/compile.ts +156 -59
- package/scripts/docker-server.ts +13 -3
- package/scripts/docs/as/amxts.ts +86 -0
- package/scripts/docs/as/facade.ts +688 -188
- package/scripts/docs/as/fetch.ts +805 -0
- package/scripts/docs/entities.ts +43 -144
- package/scripts/docs/events.ts +15 -26
- package/scripts/docs/game.ts +68 -22
- package/scripts/docs/messages.ts +330 -16
- package/scripts/editor-api.ts +101 -0
- package/scripts/hlds-events.ts +370 -0
- package/scripts/includes.ts +12 -10
- package/scripts/player-fields.ts +16 -15
- package/scripts/prebuilt.ts +65 -23
- package/scripts/prepare.ts +39 -28
- package/scripts/project.ts +120 -20
- package/scripts/reapi-events.ts +98 -0
- package/scripts/shared-modules.ts +85 -23
- package/scripts/source-map.ts +242 -0
- package/scripts/system.ts +1 -1
- package/scripts/typed-commands.ts +263 -0
- package/scripts/typed-configs.ts +23 -12
- package/scripts/ui.ts +3 -1
- package/scripts/upgrade-menus.ts +274 -0
- package/scripts/upgrade-names.ts +384 -0
- package/scripts/upgrade.ts +830 -0
- package/src/cli-api.mjs +37 -4
- package/src/system.mjs +74 -3
- package/src/testing/compile.ts +8 -5
- package/src/testing/natives.ts +199 -5
- package/src/testing/network.ts +531 -0
- package/src/testing/server.ts +565 -47
- package/src/testing/tables.ts +25 -0
- package/as/modules/http.ts +0 -184
- package/scripts/docs/as/modules/http.ts +0 -66
|
@@ -162,6 +162,60 @@ index 771ef87..7bdb74d 100644
|
|
|
162
162
|
module->float_plt_count++;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c
|
|
166
|
+
index d2621fb..47178c4 100644
|
|
167
|
+
--- a/core/iwasm/aot/aot_runtime.c
|
|
168
|
+
+++ b/core/iwasm/aot/aot_runtime.c
|
|
169
|
+
@@ -2682,9 +2682,9 @@ aot_call_function(WASMExecEnv *exec_env, AOTFunctionInstance *function,
|
|
170
|
+
}
|
|
171
|
+
#endif
|
|
172
|
+
#if WASM_ENABLE_DUMP_CALL_STACK != 0
|
|
173
|
+
- if (aot_create_call_stack(exec_env)) {
|
|
174
|
+
- aot_dump_call_stack(exec_env, true, NULL, 0);
|
|
175
|
+
- }
|
|
176
|
+
+ /* amxts: the host reads the frames (wasm_runtime_take_trap_frames)
|
|
177
|
+
+ and prints them its own way, rather than to stdout here */
|
|
178
|
+
+ aot_create_call_stack(exec_env);
|
|
179
|
+
#endif
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@@ -2755,9 +2755,9 @@ aot_call_function(WASMExecEnv *exec_env, AOTFunctionInstance *function,
|
|
183
|
+
}
|
|
184
|
+
#endif
|
|
185
|
+
#if WASM_ENABLE_DUMP_CALL_STACK != 0
|
|
186
|
+
- if (aot_create_call_stack(exec_env)) {
|
|
187
|
+
- aot_dump_call_stack(exec_env, true, NULL, 0);
|
|
188
|
+
- }
|
|
189
|
+
+ /* amxts: the host reads the frames (wasm_runtime_take_trap_frames)
|
|
190
|
+
+ and prints them its own way, rather than to stdout here */
|
|
191
|
+
+ aot_create_call_stack(exec_env);
|
|
192
|
+
#endif
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@@ -3414,6 +3414,11 @@ aot_call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 table_elem_idx,
|
|
196
|
+
#endif
|
|
197
|
+
ret = invoke_native_internal(exec_env, func_ptr, func_type, signature,
|
|
198
|
+
attachment, argv1, argc, argv);
|
|
199
|
+
+#if WASM_ENABLE_DUMP_CALL_STACK != 0
|
|
200
|
+
+ /* amxts: the frames of a trap, as aot_call_function keeps them */
|
|
201
|
+
+ if (!ret)
|
|
202
|
+
+ aot_create_call_stack(exec_env);
|
|
203
|
+
+#endif
|
|
204
|
+
#if WASM_ENABLE_AOT_STACK_FRAME != 0
|
|
205
|
+
/* Free all frames allocated, note that some frames
|
|
206
|
+
may be allocated in AOT code and haven't been
|
|
207
|
+
@@ -3474,6 +3479,11 @@ aot_call_indirect(WASMExecEnv *exec_env, uint32 tbl_idx, uint32 table_elem_idx,
|
|
208
|
+
#endif
|
|
209
|
+
ret = invoke_native_internal(exec_env, func_ptr, func_type, signature,
|
|
210
|
+
attachment, argv, argc, argv);
|
|
211
|
+
+#if WASM_ENABLE_DUMP_CALL_STACK != 0
|
|
212
|
+
+ /* amxts: the frames of a trap, as aot_call_function keeps them */
|
|
213
|
+
+ if (!ret)
|
|
214
|
+
+ aot_create_call_stack(exec_env);
|
|
215
|
+
+#endif
|
|
216
|
+
#if WASM_ENABLE_AOT_STACK_FRAME != 0
|
|
217
|
+
/* Free all frames allocated, note that some frames
|
|
218
|
+
may be allocated in AOT code and haven't been
|
|
165
219
|
diff --git a/core/iwasm/aot/arch/aot_reloc_x86_32.c b/core/iwasm/aot/arch/aot_reloc_x86_32.c
|
|
166
220
|
index f7f2421..89ba8b9 100644
|
|
167
221
|
--- a/core/iwasm/aot/arch/aot_reloc_x86_32.c
|
|
@@ -218,6 +272,100 @@ index 060bb2c..28c182d 100644
|
|
|
218
272
|
return NULL;
|
|
219
273
|
}
|
|
220
274
|
else
|
|
275
|
+
diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c
|
|
276
|
+
index 943b46f..e93f867 100644
|
|
277
|
+
--- a/core/iwasm/common/wasm_runtime_common.c
|
|
278
|
+
+++ b/core/iwasm/common/wasm_runtime_common.c
|
|
279
|
+
@@ -6867,6 +6867,32 @@ wasm_runtime_dump_call_stack_to_buf(wasm_exec_env_t exec_env, char *buf,
|
|
280
|
+
|
|
281
|
+
return 0;
|
|
282
|
+
}
|
|
283
|
+
+
|
|
284
|
+
+/* amxts: the frames the last trap left, innermost first, and forgets them */
|
|
285
|
+
+uint32
|
|
286
|
+
+wasm_runtime_take_trap_frames(WASMModuleInstanceCommon *module_inst,
|
|
287
|
+
+ WASMCApiFrame *buffer, uint32 length)
|
|
288
|
+
+{
|
|
289
|
+
+ Vector *frames = ((WASMModuleInstance *)module_inst)->frames;
|
|
290
|
+
+ uint32 count = 0, i, size;
|
|
291
|
+
+
|
|
292
|
+
+ if (!frames)
|
|
293
|
+
+ return 0;
|
|
294
|
+
+ size = (uint32)bh_vector_size(frames);
|
|
295
|
+
+ for (i = 0; i < size; i++) {
|
|
296
|
+
+ WASMCApiFrame frame;
|
|
297
|
+
+ if (!bh_vector_get(frames, i, &frame))
|
|
298
|
+
+ break;
|
|
299
|
+
+ if (frame.lp)
|
|
300
|
+
+ wasm_runtime_free(frame.lp);
|
|
301
|
+
+ if (count < length) {
|
|
302
|
+
+ frame.lp = NULL;
|
|
303
|
+
+ buffer[count++] = frame;
|
|
304
|
+
+ }
|
|
305
|
+
+ }
|
|
306
|
+
+ bh_vector_destroy(frames);
|
|
307
|
+
+ return count;
|
|
308
|
+
+}
|
|
309
|
+
#endif /* end of WASM_ENABLE_DUMP_CALL_STACK */
|
|
310
|
+
|
|
311
|
+
#if WASM_ENABLE_STATIC_PGO != 0
|
|
312
|
+
diff --git a/core/iwasm/compilation/aot_compiler.c b/core/iwasm/compilation/aot_compiler.c
|
|
313
|
+
index 82f70ca..ea501fb 100644
|
|
314
|
+
--- a/core/iwasm/compilation/aot_compiler.c
|
|
315
|
+
+++ b/core/iwasm/compilation/aot_compiler.c
|
|
316
|
+
@@ -1027,6 +1027,15 @@ aot_compile_func(AOTCompContext *comp_ctx, uint32 func_index)
|
|
317
|
+
comp_ctx->builder,
|
|
318
|
+
func_ctx->block_stack.block_list_head->llvm_entry_block);
|
|
319
|
+
|
|
320
|
+
+ /* amxts: the frame first - the frame's own bounds check below can trap,
|
|
321
|
+
+ and a trap's ip (trap-ip) is read from it; before, that was the frame
|
|
322
|
+
+ of the function compiled last, freed, and wamrc crashed */
|
|
323
|
+
+ if (comp_ctx->aux_stack_frame_type) {
|
|
324
|
+
+ if (!init_comp_frame(comp_ctx, func_ctx, func_index)) {
|
|
325
|
+
+ return false;
|
|
326
|
+
+ }
|
|
327
|
+
+ comp_ctx->aot_frame->frame_ip = frame_ip;
|
|
328
|
+
+ }
|
|
329
|
+
if (comp_ctx->aux_stack_frame_type
|
|
330
|
+
&& comp_ctx->call_stack_features.frame_per_function) {
|
|
331
|
+
INT_CONST(func_index_ref,
|
|
332
|
+
@@ -1037,11 +1046,6 @@ aot_compile_func(AOTCompContext *comp_ctx, uint32 func_index)
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
- if (comp_ctx->aux_stack_frame_type) {
|
|
337
|
+
- if (!init_comp_frame(comp_ctx, func_ctx, func_index)) {
|
|
338
|
+
- return false;
|
|
339
|
+
- }
|
|
340
|
+
- }
|
|
341
|
+
|
|
342
|
+
while (frame_ip < frame_ip_end) {
|
|
343
|
+
opcode = *frame_ip++;
|
|
344
|
+
diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h
|
|
345
|
+
index 81efb8f..2d80cf2 100644
|
|
346
|
+
--- a/core/iwasm/include/wasm_export.h
|
|
347
|
+
+++ b/core/iwasm/include/wasm_export.h
|
|
348
|
+
@@ -2075,6 +2075,20 @@ WASM_RUNTIME_API_EXTERN uint32_t
|
|
349
|
+
wasm_runtime_dump_call_stack_to_buf(wasm_exec_env_t exec_env, char *buf,
|
|
350
|
+
uint32_t len);
|
|
351
|
+
|
|
352
|
+
+/**
|
|
353
|
+
+ * amxts: the frames of the last trap - a call that failed - innermost first,
|
|
354
|
+
+ * and forgets them, so a later failure that left none reads none.
|
|
355
|
+
+ *
|
|
356
|
+
+ * @param module_inst the module instance the call failed in
|
|
357
|
+
+ * @param buffer where the frames go
|
|
358
|
+
+ * @param length how many fit
|
|
359
|
+
+ *
|
|
360
|
+
+ * @return how many were written
|
|
361
|
+
+ */
|
|
362
|
+
+WASM_RUNTIME_API_EXTERN uint32_t
|
|
363
|
+
+wasm_runtime_take_trap_frames(wasm_module_inst_t module_inst,
|
|
364
|
+
+ WASMCApiFrame *buffer, uint32_t length);
|
|
365
|
+
+
|
|
366
|
+
/**
|
|
367
|
+
* Get the size required to store the LLVM PGO profile data
|
|
368
|
+
*
|
|
221
369
|
diff --git a/wamr-compiler/CMakeLists.txt b/wamr-compiler/CMakeLists.txt
|
|
222
370
|
index 8ee61ca..5a496cd 100644
|
|
223
371
|
--- a/wamr-compiler/CMakeLists.txt
|
package/scripts/apply-docs.ts
CHANGED
|
@@ -360,7 +360,7 @@ export interface DocSource {
|
|
|
360
360
|
root: string;
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
-
/** The core's hand-written API: what
|
|
363
|
+
/** The core's hand-written API: what `@amxts/core` and the globals give a plugin. */
|
|
364
364
|
export const CORE_API = [
|
|
365
365
|
'as/facade.ts',
|
|
366
366
|
'as/promise.ts',
|
|
@@ -371,7 +371,7 @@ export const CORE_API = [
|
|
|
371
371
|
'as/os.ts',
|
|
372
372
|
'as/amxts.d.ts',
|
|
373
373
|
'as/lib/check.ts',
|
|
374
|
-
'as/
|
|
374
|
+
'as/fetch.ts',
|
|
375
375
|
];
|
|
376
376
|
|
|
377
377
|
export function docsFileOf(root: string, file: string): string {
|
package/scripts/auto-imports.ts
CHANGED
|
@@ -70,9 +70,9 @@ export const HOOD = new Set([
|
|
|
70
70
|
// Cells and raw handlers.
|
|
71
71
|
'Call CellArray CellBuffer Handler WideHandler NoArgument arg argc argString argText cell cellFloat cells cellsToString cmd cmdWide floatCell handled hook hostIndex nativeFn noOrigin out paint playerIds publicFor putCell readText ret setArg setArgText stringToCells swapTeam text TEXT_MAX',
|
|
72
72
|
// The kit's.
|
|
73
|
-
'caller cellArrayRows cellsText colorTags createCellArray defineModule destroyCellArray menuColors PawnCall PawnFunction pushCellArrayRow showMenu textCells',
|
|
73
|
+
'caller callingPlugin cellArrayRows cellsText colorTags createCellArray defineModule destroyCellArray menuColors onPluginStop PawnCall PawnFunction pushCellArrayRow request RequestErrorKind RequestOptions RequestResult showMenu textCells',
|
|
74
74
|
// The generated listener and flag tables.
|
|
75
|
-
'addGameListener addServerListener removeGameListener removeServerListener GameAnswerMap GameEventMap ServerEventMap HookEntry HookEvent EntvarFlags FlagFamily FlagList flagList FlagStore MemberFlags ACCESS BUTTON DAMAGE EFFECT ENTITY_FLAG HIDE_HUD PHYSICS_FLAG WEAPON_STATE',
|
|
75
|
+
'addGameListener addServerListener removeGameListener removeServerListener GameAnswerMap GameEventMap ServerEventMap ServerMessageMap protocolMessageNames HookEntry HookEvent EntvarFlags FlagFamily FlagList flagList FlagStore MemberFlags ACCESS BUTTON DAMAGE EFFECT ENTITY_FLAG HIDE_HUD PHYSICS_FLAG WEAPON_STATE',
|
|
76
76
|
].flatMap(group => group.split(' ')));
|
|
77
77
|
|
|
78
78
|
// ---------------------------------------------------------------- the table
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// The build a module and the compiler beside it on a server are of: the
|
|
2
|
+
// version and the commit, as a release's manifest names them -
|
|
3
|
+
// `0.2.0+1bf291c0ab`, SemVer's build metadata.
|
|
4
|
+
//
|
|
5
|
+
// `bun run generate` writes it into the module (runtime/src/embedded.h), and
|
|
6
|
+
// `bun run serverkit` builds amxts-compile with the one read back from there,
|
|
7
|
+
// so the two agree whenever they come from one build. Before it compiles a
|
|
8
|
+
// plugin's source, the module asks the compiler for its build
|
|
9
|
+
// (`amxts-compile --version`) and refuses one of another: the module writes
|
|
10
|
+
// the API a plugin imports, and a compiler of another release reads it with
|
|
11
|
+
// another AssemblyScript, which fails with errors that say nothing of why.
|
|
12
|
+
//
|
|
13
|
+
// A plugin carries an identity of its own: the ABI it was compiled against,
|
|
14
|
+
// which the module checks before it loads the plugin (abiIdentity, below).
|
|
15
|
+
import { spawnSync } from 'node:child_process';
|
|
16
|
+
import { createHash } from 'node:crypto';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import process from 'node:process';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import pkg from '../package.json';
|
|
22
|
+
import * as tracked from './tracked-fs';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The version the core builds as: its package.json's, or AMXTS_AS_VERSION's.
|
|
26
|
+
* `bun run publish:local --as 0.2.0` stages the packages as another version
|
|
27
|
+
* than the checkout's; the modules it compiles for them, and a module built
|
|
28
|
+
* for a server that runs their plugins, are of that version only with it set.
|
|
29
|
+
*/
|
|
30
|
+
export function coreVersion(): string {
|
|
31
|
+
return process.env.AMXTS_AS_VERSION || pkg.version;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** This checkout's build: the version, and the commit when git knows it. */
|
|
35
|
+
export function buildIdentity(): string {
|
|
36
|
+
const git = spawnSync('git', ['rev-parse', '--short=10', 'HEAD'], { encoding: 'utf-8' });
|
|
37
|
+
const commit = git.status === 0 ? git.stdout.trim() : '';
|
|
38
|
+
return commit ? `${coreVersion()}+${commit}` : coreVersion();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A string the generated module carries (`#define <name> "..."`), or null before `bun run generate`. */
|
|
42
|
+
function moduleDefine(name: string): string | null {
|
|
43
|
+
const header = './runtime/src/embedded.h';
|
|
44
|
+
if (!existsSync(header)) return null;
|
|
45
|
+
return new RegExp(`^#define ${name} "([^"]*)"$`, 'm').exec(readFileSync(header, 'utf-8'))?.[1] ?? null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The build the generated module carries, or null before `bun run generate`. */
|
|
49
|
+
export const moduleBuild = () => moduleDefine('AMXTS_BUILD');
|
|
50
|
+
|
|
51
|
+
/** The ABI the generated module loads plugins of, or null before `bun run generate`. */
|
|
52
|
+
export const moduleAbi = () => moduleDefine('AMXTS_ABI');
|
|
53
|
+
|
|
54
|
+
/** The `--define`s that build amxts-compile as of `build`, stamping plugins with `abi`. */
|
|
55
|
+
export function buildDefines(build: string, abi: string): string[] {
|
|
56
|
+
return [`--define=AMXTS_BUILD=${JSON.stringify(build)}`, `--define=AMXTS_ABI=${JSON.stringify(abi)}`];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------- the ABI
|
|
60
|
+
//
|
|
61
|
+
// What a plugin and the module have to agree on: the imports the hood
|
|
62
|
+
// declares (`@external("env", ...)` in as/, by name and by the types they
|
|
63
|
+
// take and give) and the natives table wamrc compiles direct calls against
|
|
64
|
+
// (runtime/natives.txt). A plugin of another ABI calls an import the module
|
|
65
|
+
// does not have, or has with another signature, and the server crashes; so
|
|
66
|
+
// the module loads no plugin whose ABI is not its own.
|
|
67
|
+
//
|
|
68
|
+
// It is the version and a hash of those - `0.2.0+abi.1a2b3c4d`. Not the
|
|
69
|
+
// commit: a module from a release and the plugins built with the core from
|
|
70
|
+
// npm, or a module package's prebuilt plugin, are of one release but not
|
|
71
|
+
// built from one checkout. The version tells releases apart; the hash, builds
|
|
72
|
+
// of one version whose imports differ. The same release rebuilt is the same
|
|
73
|
+
// ABI, and a change that is only the facade's own code needs no rebuilt
|
|
74
|
+
// plugins.
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Raised by hand when the module and the facade change how they talk without
|
|
78
|
+
* an import changing its name or its types: what a cell of an event means,
|
|
79
|
+
* an export the module calls, what names a plugin in a shared module's call
|
|
80
|
+
* (2: its run, not its index).
|
|
81
|
+
*/
|
|
82
|
+
const ABI_REVISION = 2;
|
|
83
|
+
|
|
84
|
+
/** The custom section of a plugin that holds its ABI: wamrc copies it into the .aot. */
|
|
85
|
+
export const ABI_SECTION = 'amxts.abi';
|
|
86
|
+
|
|
87
|
+
const CORE = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
88
|
+
|
|
89
|
+
// One import: `@external("env", "ent_get") declare function _entGet(id: i32, offset: i32): i32;`.
|
|
90
|
+
const IMPORT = /@external\("env",\s*"([^"]+)"\)\s*(?:export\s+)?declare\s+function\s+\w+\s*\(([^)]*)\)\s*(?::([^;]+))?;/g;
|
|
91
|
+
|
|
92
|
+
/** The imports a file of the hood declares: `ent_get(i32,i32)i32`, the parameters' names left out. */
|
|
93
|
+
export function importsOf(code: string): string[] {
|
|
94
|
+
return [...code.matchAll(IMPORT)].map(([, name, params, result]) => {
|
|
95
|
+
const types = params.split(',').map(param => param.slice(param.indexOf(':') + 1).trim()).filter(Boolean);
|
|
96
|
+
return `${name}(${types.join(',')})${result?.trim() ?? 'void'}`;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
declare const AMXTS_ABI: string;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The ABI this core compiles plugins for, which the module it builds loads.
|
|
104
|
+
* amxts-compile on a server has the module's own built in; anywhere else it
|
|
105
|
+
* is read off the hood's files, through the tracked reads, so a cached
|
|
106
|
+
* compile is made again when they change.
|
|
107
|
+
*/
|
|
108
|
+
export function abiIdentity(): string {
|
|
109
|
+
if (typeof AMXTS_ABI === 'string') return AMXTS_ABI;
|
|
110
|
+
const plugins = join(CORE, 'as');
|
|
111
|
+
const imports = tracked.readdirSync(plugins).filter(file => file.endsWith('.ts')).flatMap(file => importsOf(tracked.readFileSync(join(plugins, file), 'utf8')));
|
|
112
|
+
const natives = tracked.readFileSync(join(CORE, 'runtime/natives.txt'), 'utf8').split(/\r?\n/).filter(Boolean);
|
|
113
|
+
const hash = createHash('sha256').update([`revision ${ABI_REVISION}`, ...imports.sort(), ...natives.sort()].join('\n')).digest('hex');
|
|
114
|
+
return `${coreVersion()}+abi.${hash.slice(0, 8)}`;
|
|
115
|
+
}
|
package/scripts/build-wasm.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from './compile';
|
|
2
|
-
import type {
|
|
3
|
-
import type { ModulePackage } from './project';
|
|
2
|
+
import type { ModulePackage, Sources } from './project';
|
|
4
3
|
import { createSocket } from 'node:dgram';
|
|
5
4
|
// Builds a project's plugins into .aot files the server can load, and
|
|
6
5
|
// optionally copies them there. The project is the current folder: its
|
|
@@ -32,22 +31,24 @@ import { createSocket } from 'node:dgram';
|
|
|
32
31
|
// Two compilers, in order: asc turns AssemblyScript into wasm, wamrc turns
|
|
33
32
|
// wasm into i386 machine code - in the object format of the server's system,
|
|
34
33
|
// Windows or Linux, whichever this machine is (scripts/system.ts). wamrc is given runtime/natives.txt so that a
|
|
35
|
-
// call into a host native compiles to a direct call
|
|
34
|
+
// call into a host native compiles to a direct call, far cheaper than WAMR's
|
|
35
|
+
// generic one. Both
|
|
36
36
|
// must come from the same WAMR release as the module, or the loader reports
|
|
37
37
|
// "unknown binary version" (CONTRIBUTING.md builds both from one checkout).
|
|
38
38
|
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, utimesSync, watch, writeFileSync } from 'node:fs';
|
|
39
39
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
40
40
|
import { includePath } from './compile';
|
|
41
|
+
import { compileAll } from './compile-pool';
|
|
41
42
|
import { followConsoles, projectContainers, runCommand } from './docker-server';
|
|
42
43
|
import { FileContents, sourceIn, sourcesIn } from './file-contents';
|
|
43
44
|
import { serverIncludes } from './includes';
|
|
44
45
|
import { pluginCache } from './plugin-cache';
|
|
45
46
|
import { includeName } from './plugin-natives';
|
|
46
|
-
import { fromRegistry, prebuiltOf } from './prebuilt';
|
|
47
|
+
import { fromRegistry, prebuiltOf, prebuiltSurface } from './prebuilt';
|
|
47
48
|
import { CORE_DIR, CORE_PLUGINS, loadProject, modulesInUse, pluginList, projectPlugins, sourcesFor } from './project';
|
|
48
49
|
import { sharedModulesBuild } from './shared-modules';
|
|
49
|
-
import { describeSystem, serverSystem, SYSTEM_NAME, WAMRC_PACKAGE, wamrcPath } from './system';
|
|
50
|
-
import { c, log, progress, since } from './ui';
|
|
50
|
+
import { describeSystem, serverFolder, serverSystem, SYSTEM_NAME, WAMRC_PACKAGE, wamrcPath } from './system';
|
|
51
|
+
import { c, live, log, progress, since } from './ui';
|
|
51
52
|
|
|
52
53
|
function fail(message: string, hint?: string): void {
|
|
53
54
|
log.error(message.trim());
|
|
@@ -78,8 +79,17 @@ const asc_ = process.env.AMXTS_ASC ?? join(CORE_DIR, 'runtime/deps/assemblyscrip
|
|
|
78
79
|
const signatures = process.env.AMXTS_NATIVES ?? join(CORE_DIR, 'runtime/natives.txt');
|
|
79
80
|
// Where --deploy copies the built plugins: the amxts folder inside a server's
|
|
80
81
|
// addons. It is per-machine, so it comes from the environment rather than from
|
|
81
|
-
// this file, and .env keeps it out of everyone else's way.
|
|
82
|
-
|
|
82
|
+
// this file, and .env keeps it out of everyone else's way. AMXTS_SERVER may
|
|
83
|
+
// also name a folder above it - the server's own, cstrike, cstrike/addons
|
|
84
|
+
// (src/system.mjs).
|
|
85
|
+
const serverSetting = process.env.AMXTS_SERVER ?? '';
|
|
86
|
+
let serverDir = '';
|
|
87
|
+
try {
|
|
88
|
+
serverDir = serverFolder(serverSetting);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
fail((error as Error).message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
83
93
|
|
|
84
94
|
// The module's server command that starts every plugin over from disk.
|
|
85
95
|
const RELOAD_COMMAND = 'amxts_reload';
|
|
@@ -159,22 +169,23 @@ for (const plugin of ownPlugins()) {
|
|
|
159
169
|
}
|
|
160
170
|
}
|
|
161
171
|
|
|
162
|
-
/**
|
|
163
|
-
|
|
164
|
-
|
|
172
|
+
/**
|
|
173
|
+
* The owners of the modules the plugins use, in load order. A save's
|
|
174
|
+
* rebuild reads the sources once (`sources`), for this and what it affects.
|
|
175
|
+
*/
|
|
176
|
+
function moduleOwners(sources = sourcesFor(CORE_PLUGINS)): Target[] {
|
|
165
177
|
return modulesInUse(sources, ownPlugins().map(plugin => plugin.source)).map(pkg => ({ source: sources.ownerSource(pkg), name: pkg.short, pkg }));
|
|
166
178
|
}
|
|
167
179
|
|
|
168
180
|
/** The modules' owners, in load order, then the project's plugins. */
|
|
169
|
-
function allPlugins(): Target[] {
|
|
170
|
-
return [...moduleOwners(), ...ownPlugins()];
|
|
181
|
+
function allPlugins(sources: Sources): Target[] {
|
|
182
|
+
return [...moduleOwners(sources), ...ownPlugins()];
|
|
171
183
|
}
|
|
172
184
|
|
|
173
185
|
/** The plugins a change to these files has to rebuild: every one importing them. */
|
|
174
|
-
function affected(files: string[]): Target[] {
|
|
175
|
-
const sources = sourcesFor(CORE_PLUGINS);
|
|
186
|
+
function affected(files: string[], sources: Sources): Target[] {
|
|
176
187
|
const changed = files.map(file => resolve(file));
|
|
177
|
-
return allPlugins().filter((plugin) => {
|
|
188
|
+
return allPlugins(sources).filter((plugin) => {
|
|
178
189
|
const reached = [...sources.reach(join(CORE_PLUGINS, sources.entry(plugin.source)))].map(place => sources.real(place) ?? place);
|
|
179
190
|
return changed.some(file => reached.includes(file));
|
|
180
191
|
});
|
|
@@ -211,22 +222,30 @@ const includes = new Map<string, string>();
|
|
|
211
222
|
* wrote. Named before the first build as after it, or a dev started again
|
|
212
223
|
* would compile everything again.
|
|
213
224
|
*/
|
|
214
|
-
const
|
|
225
|
+
const cacheIncludes = [
|
|
215
226
|
'amxts',
|
|
216
227
|
...[...project.modules.map(pkg => pkg.short), ...ownPlugins().map(plugin => plugin.name)].map(includeName),
|
|
217
228
|
...project.modules.flatMap(pkg => (pkg.include ? [basename(pkg.include).replace(/\.inc$/, '')] : [])),
|
|
218
229
|
...readdirSync(outDir).filter(file => file.endsWith('.inc')).map(file => file.replace(/\.inc$/, '')),
|
|
219
|
-
]
|
|
230
|
+
];
|
|
231
|
+
const cache = pluginCache(project.dir, cacheIncludes);
|
|
220
232
|
|
|
221
233
|
/**
|
|
222
234
|
* Builds these plugins: a module that comes compiled for this project is
|
|
223
235
|
* put in place as it came (scripts/prebuilt.ts), one an earlier build kept,
|
|
224
|
-
* from the same files, likewise; the rest are compiled
|
|
225
|
-
*
|
|
226
|
-
*
|
|
236
|
+
* from the same files, likewise; the rest are compiled, several at once when
|
|
237
|
+
* there is room (scripts/compile-pool.ts). The first build of a run says when
|
|
238
|
+
* it has a module to compile. Returns the .aot names, or null after printing
|
|
239
|
+
* the errors.
|
|
227
240
|
*/
|
|
228
|
-
async function compile(plugins: Target[], first: boolean): Promise<string[] | null> {
|
|
229
|
-
|
|
241
|
+
async function compile(plugins: Target[], first: boolean, sources: Sources): Promise<string[] | null> {
|
|
242
|
+
// Why a module is not taken as it came, said once.
|
|
243
|
+
const said = new Set<string>();
|
|
244
|
+
const say = (why: string, instead: string) => {
|
|
245
|
+
if (said.has(why)) return;
|
|
246
|
+
said.add(why);
|
|
247
|
+
log.info(`${why} - ${instead}`);
|
|
248
|
+
};
|
|
230
249
|
const planned = plugins.map((each) => {
|
|
231
250
|
const plugin: Plugin = {
|
|
232
251
|
source: each.source,
|
|
@@ -240,31 +259,53 @@ async function compile(plugins: Target[], first: boolean): Promise<string[] | nu
|
|
|
240
259
|
quick,
|
|
241
260
|
};
|
|
242
261
|
const prebuilt = each.pkg ? prebuiltOf(each.pkg, target.system, sources) : null;
|
|
243
|
-
if (prebuilt && 'why' in prebuilt)
|
|
262
|
+
if (prebuilt && 'why' in prebuilt) say(prebuilt.why, 'compiling it here');
|
|
244
263
|
if (prebuilt && 'aot' in prebuilt) return { ...each, plugin, how: 'prebuilt', natives: cache.take(plugin, prebuilt) };
|
|
245
264
|
return { ...each, plugin, how: 'unchanged', natives: cache.reuse(plugin) };
|
|
246
265
|
});
|
|
266
|
+
// The surface a plugin compiles a module's proxy from (scripts/shared-modules.ts) comes with it too.
|
|
267
|
+
for (const each of first ? plugins : []) {
|
|
268
|
+
const shipped = each.pkg ? prebuiltSurface(each.pkg, sources) : null;
|
|
269
|
+
if (shipped && 'why' in shipped) say(shipped.why, 'analysing it here');
|
|
270
|
+
}
|
|
247
271
|
const fresh = planned.filter(each => !each.natives);
|
|
248
272
|
if (first && fresh.some(each => each.pkg)) {
|
|
249
273
|
log.info('The first build compiles the modules to machine code: it takes about a minute.');
|
|
250
274
|
}
|
|
251
|
-
// What comes prebuilt or unchanged is taken quietly: only what is compiled
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
275
|
+
// What comes prebuilt or unchanged is taken quietly: only what is compiled
|
|
276
|
+
// shows - a line for each as it is done, and as it starts: in a terminal
|
|
277
|
+
// one line, rewritten, says what is being compiled now.
|
|
278
|
+
const running = new Map<number, number>();
|
|
279
|
+
let step: ReturnType<typeof progress> | null = null;
|
|
280
|
+
const counted = (index: number) => c.dim(`${index + 1}/${fresh.length}`);
|
|
281
|
+
const redraw = (done?: string) => {
|
|
282
|
+
step?.end(done);
|
|
283
|
+
const names = [...running.keys()].map(index => c.bold(fresh[index].name)).join(', ');
|
|
284
|
+
step = running.size ? progress(`compiling ${names} ${counted(Math.max(...running.keys()))}`) : null;
|
|
285
|
+
};
|
|
286
|
+
const compiled = await compileAll(fresh.map(each => each.plugin), {
|
|
287
|
+
dir: project.dir,
|
|
288
|
+
includes: cacheIncludes,
|
|
289
|
+
here: (plugin, natives) => cache.compile(plugin, natives),
|
|
290
|
+
started(index) {
|
|
291
|
+
running.set(index, performance.now());
|
|
292
|
+
if (live) redraw();
|
|
293
|
+
else progress(`compiling ${c.bold(fresh[index].name)} ${counted(index)}`);
|
|
294
|
+
},
|
|
295
|
+
finished(index, { problem }) {
|
|
296
|
+
const done = problem ? undefined : `${c.bold(fresh[index].name)} ${c.dim(`· ${since(running.get(index)!)}`)}`;
|
|
297
|
+
running.delete(index);
|
|
298
|
+
if (live) redraw(done);
|
|
299
|
+
else if (done) log.success(done);
|
|
300
|
+
},
|
|
301
|
+
}).finally(() => step?.end());
|
|
302
|
+
|
|
303
|
+
const failures = compiled.flatMap((each, index) => (each?.problem ? [`${c.bold(fresh[index].name)} does not compile:\n${readable(each.problem)}\n`] : []));
|
|
304
|
+
if (failures.length) {
|
|
305
|
+
fail(failures.join('\n'), keepWatching ? 'The server keeps the last good build.' : undefined);
|
|
306
|
+
return null;
|
|
267
307
|
}
|
|
308
|
+
for (const [index, each] of fresh.entries()) each.natives = compiled[index]!.natives;
|
|
268
309
|
|
|
269
310
|
for (const each of planned) {
|
|
270
311
|
includes.set(`${each.name}.aot`, includePath(each.plugin.output, each.natives!));
|
|
@@ -401,7 +442,10 @@ async function deployAndReload(built: string[], names: string): Promise<string>
|
|
|
401
442
|
return `deployed ${names} - the module reloads them on its own`;
|
|
402
443
|
}
|
|
403
444
|
|
|
404
|
-
|
|
445
|
+
// Only what changed starts over; a new plugin needs the list read again,
|
|
446
|
+
// which only a reload of them all does.
|
|
447
|
+
const reload = added.length > 0 ? RELOAD_COMMAND : built.map(file => `${RELOAD_COMMAND} ${file.replace(/\.aot$/, '')}`).join('; ');
|
|
448
|
+
const reply = await ask(`rcon ${challenge} "${password}" ${reload}`, 5000);
|
|
405
449
|
|
|
406
450
|
if (reply === null || /bad rcon_password/i.test(reply)) {
|
|
407
451
|
// Nothing reloaded them, so let the module's watcher see the new files.
|
|
@@ -410,8 +454,12 @@ async function deployAndReload(built: string[], names: string): Promise<string>
|
|
|
410
454
|
return `deployed ${names} - rcon ${reply === null ? 'got no answer' : 'refused the password'}, the module reloads them on its own`;
|
|
411
455
|
}
|
|
412
456
|
|
|
413
|
-
|
|
414
|
-
|
|
457
|
+
// The module's own "reloading x" and "loaded x" only repeat the line above;
|
|
458
|
+
// anything else it says - a refusal, an error - is shown.
|
|
459
|
+
const lines = reply.split('\n').map(line => line.trimEnd()).filter(line => line && !/^\[amxts\] (?:reloading|loaded) /.test(line));
|
|
460
|
+
const loaded = built.every(file => reply.includes(`[amxts] loaded ${file}`));
|
|
461
|
+
const head = loaded ? `deployed, the server reloaded ${names}` : `deployed ${names} - the server did not load it all:`;
|
|
462
|
+
return [head, ...lines.map(line => c.dim(` ${line}`))].join('\n');
|
|
415
463
|
}
|
|
416
464
|
|
|
417
465
|
/**
|
|
@@ -454,11 +502,11 @@ function writeList(owners: Target[]) {
|
|
|
454
502
|
* the files a rebuild is for. A module a save brings into use is built with
|
|
455
503
|
* the plugin that uses it.
|
|
456
504
|
*/
|
|
457
|
-
async function run(plugins: Target[], verb: string, why = ''): Promise<boolean> {
|
|
505
|
+
async function run(plugins: Target[], verb: string, why = '', sources = sourcesFor(CORE_PLUGINS)): Promise<boolean> {
|
|
458
506
|
const started = performance.now();
|
|
459
|
-
const owners = moduleOwners();
|
|
507
|
+
const owners = moduleOwners(sources);
|
|
460
508
|
const fresh = owners.filter(owner => !listed?.includes(owner.name) && !plugins.some(plugin => plugin.name === owner.name));
|
|
461
|
-
const built = await compile([...fresh, ...plugins], listed === null);
|
|
509
|
+
const built = await compile([...fresh, ...plugins], listed === null, sources);
|
|
462
510
|
if (!built) return false;
|
|
463
511
|
writeList(owners);
|
|
464
512
|
|
|
@@ -478,7 +526,9 @@ async function serverLine(): Promise<string> {
|
|
|
478
526
|
const system = SYSTEM_NAME[target.system];
|
|
479
527
|
if (forDocker) return `the Docker one that mounts this project · ${system}`;
|
|
480
528
|
if (!serverDir) return `${c.dim('none - AMXTS_SERVER is not set')} · the plugins are for ${system}`;
|
|
481
|
-
|
|
529
|
+
// The folder used, and what AMXTS_SERVER holds when it named one above it.
|
|
530
|
+
const above = serverSetting === serverDir ? '' : c.dim(` (AMXTS_SERVER=${serverSetting.replace(/\\/g, '/')})`);
|
|
531
|
+
const folder = `${serverDir.replace(/\\/g, '/')}${above}`;
|
|
482
532
|
if (!existsSync(serverDir)) return `${folder} ${c.yellow('is not there')} · the plugins are for ${system}`;
|
|
483
533
|
const includes = serverIncludes();
|
|
484
534
|
const rehlds = includes ? existsSync(join(includes, 'reapi.inc')) : project.config?.target !== 'hlds';
|
|
@@ -498,7 +548,7 @@ async function header(): Promise<void> {
|
|
|
498
548
|
const module = (pkg: ModulePackage) => {
|
|
499
549
|
const notes = [
|
|
500
550
|
!fromRegistry(pkg.dir) && 'local',
|
|
501
|
-
!used.includes(pkg) && 'no plugin uses it',
|
|
551
|
+
pkg.library ? 'library' : !used.includes(pkg) && 'no plugin uses it',
|
|
502
552
|
].filter(Boolean);
|
|
503
553
|
return `${pkg.short} ${pkg.version}${notes.length ? c.dim(` (${notes.join(', ')})`) : ''}`;
|
|
504
554
|
};
|
|
@@ -560,8 +610,9 @@ async function flush(): Promise<void> {
|
|
|
560
610
|
|
|
561
611
|
const files = contents.changed([...heard]);
|
|
562
612
|
heard.clear();
|
|
563
|
-
const
|
|
564
|
-
|
|
613
|
+
const sources = sourcesFor(CORE_PLUGINS);
|
|
614
|
+
const plugins = affected(files, sources);
|
|
615
|
+
if (plugins.length > 0) await run(plugins, 'rebuilt', `${files.map(shown).join(', ')} changed: `, sources).catch(error => fail(String(error)));
|
|
565
616
|
|
|
566
617
|
building = false;
|
|
567
618
|
void flush();
|
package/scripts/check.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// module's folder, as a CI step:
|
|
3
3
|
//
|
|
4
4
|
// - package.json's "amxts" field points at files that are there;
|
|
5
|
-
// - the module file declares itself with defineModule, meta.name its name
|
|
5
|
+
// - the module file declares itself with defineModule, meta.name its name -
|
|
6
|
+
// or, for a library (`"library": true`), has none and compiles;
|
|
6
7
|
// - README.md and LICENSE are there;
|
|
7
8
|
// - the natives compile, and include/<name>.inc is what the build writes from
|
|
8
9
|
// them - not stale after an edit of natives.ts. With `"contract": true`
|
|
@@ -18,6 +19,7 @@ import { importedName } from './auto-imports';
|
|
|
18
19
|
import { compileToWasm } from './compile';
|
|
19
20
|
import { includeName, pawnInclude } from './plugin-natives';
|
|
20
21
|
import { CORE_PLUGINS, loadProject, readDefinition, readPackage, sourcesFor } from './project';
|
|
22
|
+
import { compileAlone } from './shared-modules';
|
|
21
23
|
import { c, log } from './ui';
|
|
22
24
|
|
|
23
25
|
function fail(message: string): void {
|
|
@@ -44,8 +46,9 @@ if (pkg) {
|
|
|
44
46
|
passed.push(`"amxts" points at ${[pkg.module, pkg.natives, pkg.include].filter(Boolean).map(file => relative(dir, file!).replace(/\\/g, '/')).join(', ')}`);
|
|
45
47
|
|
|
46
48
|
try {
|
|
47
|
-
const definition = readDefinition(pkg.module);
|
|
48
|
-
if (
|
|
49
|
+
const definition = pkg.library ? null : readDefinition(pkg.module);
|
|
50
|
+
if (pkg.library) passed.push('a library: compiled into each plugin that imports it');
|
|
51
|
+
else if (!definition) problems.push(`${relative(dir, pkg.module)}: no \`export default defineModule({ meta: { name: "${pkg.short}" }, ... })\``);
|
|
49
52
|
else if (!definition.name) problems.push(`${relative(dir, pkg.module)}: defineModule has no meta.name - "${pkg.short}"`);
|
|
50
53
|
else passed.push(`defineModule: ${definition.name}${definition.configKey ? `, options under "${definition.configKey}"` : ''}${definition.imports.length ? `, plugins use it as ${definition.imports.map(importedName).join(', ')}` : ''}`);
|
|
51
54
|
} catch (problem) {
|
|
@@ -59,6 +62,17 @@ if (pkg) {
|
|
|
59
62
|
const project = loadProject(dir);
|
|
60
63
|
problems.push(...project.problems);
|
|
61
64
|
if (pkg.natives && project.problems.length === 0) await checkNatives(pkg);
|
|
65
|
+
if (pkg.library && project.problems.length === 0) await checkLibrary(pkg);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function checkLibrary(library: NonNullable<typeof pkg>) {
|
|
69
|
+
const file = relative(dir, library.module).replace(/\\/g, '/');
|
|
70
|
+
try {
|
|
71
|
+
await compileAlone(CORE_PLUGINS, library.short);
|
|
72
|
+
passed.push(`${file} compiles`);
|
|
73
|
+
} catch (problem) {
|
|
74
|
+
problems.push((problem as Error).message.replace(`~/modules/${library.short}`, file));
|
|
75
|
+
}
|
|
62
76
|
}
|
|
63
77
|
|
|
64
78
|
async function checkNatives(module: NonNullable<typeof pkg>) {
|