@amxts/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/README.ru.md +162 -0
- package/amxmodx/base/include/amxconst.inc +594 -0
- package/amxmodx/base/include/amxmisc.inc +902 -0
- package/amxmodx/base/include/amxmodx.inc +3447 -0
- package/amxmodx/base/include/amxmodx_version.inc +20 -0
- package/amxmodx/base/include/cellarray.inc +525 -0
- package/amxmodx/base/include/cellstack.inc +166 -0
- package/amxmodx/base/include/celltrie.inc +412 -0
- package/amxmodx/base/include/core.inc +202 -0
- package/amxmodx/base/include/csstats.inc +298 -0
- package/amxmodx/base/include/csstats_const.inc +29 -0
- package/amxmodx/base/include/cstrike.inc +1287 -0
- package/amxmodx/base/include/cstrike_const.inc +521 -0
- package/amxmodx/base/include/csx.inc +259 -0
- package/amxmodx/base/include/cvars.inc +547 -0
- package/amxmodx/base/include/datapack.inc +161 -0
- package/amxmodx/base/include/dbi.inc +154 -0
- package/amxmodx/base/include/dodconst.inc +152 -0
- package/amxmodx/base/include/dodfun.inc +152 -0
- package/amxmodx/base/include/dodstats.inc +69 -0
- package/amxmodx/base/include/dodx.inc +161 -0
- package/amxmodx/base/include/engine.inc +1343 -0
- package/amxmodx/base/include/engine_const.inc +310 -0
- package/amxmodx/base/include/engine_stocks.inc +248 -0
- package/amxmodx/base/include/esf.inc +70 -0
- package/amxmodx/base/include/esf_const.inc +74 -0
- package/amxmodx/base/include/fakemeta.inc +1127 -0
- package/amxmodx/base/include/fakemeta_const.inc +793 -0
- package/amxmodx/base/include/fakemeta_stocks.inc +395 -0
- package/amxmodx/base/include/fakemeta_util.inc +882 -0
- package/amxmodx/base/include/file.inc +666 -0
- package/amxmodx/base/include/float.inc +422 -0
- package/amxmodx/base/include/fun.inc +333 -0
- package/amxmodx/base/include/gameconfig.inc +93 -0
- package/amxmodx/base/include/geoip.inc +264 -0
- package/amxmodx/base/include/ham_const.inc +4352 -0
- package/amxmodx/base/include/hamsandwich.inc +439 -0
- package/amxmodx/base/include/hlsdk_const.inc +808 -0
- package/amxmodx/base/include/json.inc +777 -0
- package/amxmodx/base/include/lang.inc +119 -0
- package/amxmodx/base/include/message_const.inc +1130 -0
- package/amxmodx/base/include/message_stocks.inc +161 -0
- package/amxmodx/base/include/messages.inc +604 -0
- package/amxmodx/base/include/newmenus.inc +387 -0
- package/amxmodx/base/include/ns.inc +754 -0
- package/amxmodx/base/include/ns_const.inc +196 -0
- package/amxmodx/base/include/nvault.inc +154 -0
- package/amxmodx/base/include/regex.inc +348 -0
- package/amxmodx/base/include/sockets.inc +183 -0
- package/amxmodx/base/include/sorting.inc +107 -0
- package/amxmodx/base/include/sqlx.inc +582 -0
- package/amxmodx/base/include/string.inc +786 -0
- package/amxmodx/base/include/string_const.inc +156 -0
- package/amxmodx/base/include/string_stocks.inc +325 -0
- package/amxmodx/base/include/textparse_ini.inc +214 -0
- package/amxmodx/base/include/textparse_smc.inc +261 -0
- package/amxmodx/base/include/tfcconst.inc +84 -0
- package/amxmodx/base/include/tfcstats.inc +62 -0
- package/amxmodx/base/include/tfcx.inc +148 -0
- package/amxmodx/base/include/time.inc +118 -0
- package/amxmodx/base/include/tsconst.inc +154 -0
- package/amxmodx/base/include/tsfun.inc +198 -0
- package/amxmodx/base/include/tsstats.inc +65 -0
- package/amxmodx/base/include/tsx.inc +88 -0
- package/amxmodx/base/include/vault.inc +60 -0
- package/amxmodx/base/include/vector.inc +131 -0
- package/amxmodx/base/include/xs.inc +1570 -0
- package/as/amxts.d.ts +153 -0
- package/as/constants.ts +4730 -0
- package/as/effects.ts +1449 -0
- package/as/entities.ts +5482 -0
- package/as/events.ts +3054 -0
- package/as/facade.ts +4639 -0
- package/as/flags.ts +299 -0
- package/as/fs.ts +287 -0
- package/as/hooks.ts +18536 -0
- package/as/kit.ts +25 -0
- package/as/lib/check.ts +86 -0
- package/as/modules/http.ts +184 -0
- package/as/natives.ts +11845 -0
- package/as/os.ts +30 -0
- package/as/promise.ts +1392 -0
- package/as/promise.types.d.ts +82 -0
- package/as/remote.ts +288 -0
- package/as/tsconfig.json +22 -0
- package/as/vector.ts +65 -0
- package/bin/amxts.mjs +10 -0
- package/includes/menu_core.inc +355 -0
- package/includes/order.txt +54 -0
- package/includes/sources.json +27 -0
- package/includes/universal_config.inc +355 -0
- package/lint/oxlint-plugin.mjs +77 -0
- package/node_modules/assemblyscript/LICENSE +201 -0
- package/node_modules/assemblyscript/NOTICE +93 -0
- package/node_modules/assemblyscript/package.json +63 -0
- package/node_modules/assemblyscript/std/README.md +6 -0
- package/node_modules/assemblyscript/std/assembly/array.ts +526 -0
- package/node_modules/assemblyscript/std/assembly/arraybuffer.ts +77 -0
- package/node_modules/assemblyscript/std/assembly/atomics.ts +127 -0
- package/node_modules/assemblyscript/std/assembly/bindings/asyncify.ts +16 -0
- package/node_modules/assemblyscript/std/assembly/bindings/dom.ts +291 -0
- package/node_modules/assemblyscript/std/assembly/bindings/node.ts +6 -0
- package/node_modules/assemblyscript/std/assembly/builtins.ts +2631 -0
- package/node_modules/assemblyscript/std/assembly/compat.ts +2 -0
- package/node_modules/assemblyscript/std/assembly/console.ts +42 -0
- package/node_modules/assemblyscript/std/assembly/crypto.ts +9 -0
- package/node_modules/assemblyscript/std/assembly/dataview.ts +181 -0
- package/node_modules/assemblyscript/std/assembly/date.ts +373 -0
- package/node_modules/assemblyscript/std/assembly/diagnostics.ts +11 -0
- package/node_modules/assemblyscript/std/assembly/error.ts +44 -0
- package/node_modules/assemblyscript/std/assembly/function.ts +38 -0
- package/node_modules/assemblyscript/std/assembly/index.d.ts +2822 -0
- package/node_modules/assemblyscript/std/assembly/iterator.ts +35 -0
- package/node_modules/assemblyscript/std/assembly/map.ts +260 -0
- package/node_modules/assemblyscript/std/assembly/math.ts +3289 -0
- package/node_modules/assemblyscript/std/assembly/memory.ts +123 -0
- package/node_modules/assemblyscript/std/assembly/number.ts +388 -0
- package/node_modules/assemblyscript/std/assembly/object.ts +36 -0
- package/node_modules/assemblyscript/std/assembly/performance.ts +9 -0
- package/node_modules/assemblyscript/std/assembly/polyfills.ts +27 -0
- package/node_modules/assemblyscript/std/assembly/process.ts +50 -0
- package/node_modules/assemblyscript/std/assembly/reference.ts +48 -0
- package/node_modules/assemblyscript/std/assembly/regexp.ts +12 -0
- package/node_modules/assemblyscript/std/assembly/rt/README.md +83 -0
- package/node_modules/assemblyscript/std/assembly/rt/common.ts +81 -0
- package/node_modules/assemblyscript/std/assembly/rt/index-incremental.ts +2 -0
- package/node_modules/assemblyscript/std/assembly/rt/index-minimal.ts +2 -0
- package/node_modules/assemblyscript/std/assembly/rt/index-stub.ts +1 -0
- package/node_modules/assemblyscript/std/assembly/rt/index.d.ts +37 -0
- package/node_modules/assemblyscript/std/assembly/rt/itcms.ts +419 -0
- package/node_modules/assemblyscript/std/assembly/rt/rtrace.ts +15 -0
- package/node_modules/assemblyscript/std/assembly/rt/stub.ts +133 -0
- package/node_modules/assemblyscript/std/assembly/rt/tcms.ts +254 -0
- package/node_modules/assemblyscript/std/assembly/rt/tlsf.ts +592 -0
- package/node_modules/assemblyscript/std/assembly/rt.ts +87 -0
- package/node_modules/assemblyscript/std/assembly/set.ts +220 -0
- package/node_modules/assemblyscript/std/assembly/shared/feature.ts +65 -0
- package/node_modules/assemblyscript/std/assembly/shared/runtime.ts +11 -0
- package/node_modules/assemblyscript/std/assembly/shared/target.ts +11 -0
- package/node_modules/assemblyscript/std/assembly/shared/tsconfig.json +11 -0
- package/node_modules/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
- package/node_modules/assemblyscript/std/assembly/staticarray.ts +401 -0
- package/node_modules/assemblyscript/std/assembly/string.ts +847 -0
- package/node_modules/assemblyscript/std/assembly/symbol.ts +114 -0
- package/node_modules/assemblyscript/std/assembly/table.ts +16 -0
- package/node_modules/assemblyscript/std/assembly/tsconfig.json +6 -0
- package/node_modules/assemblyscript/std/assembly/typedarray.ts +1945 -0
- package/node_modules/assemblyscript/std/assembly/uri.ts +17 -0
- package/node_modules/assemblyscript/std/assembly/util/bytes.ts +107 -0
- package/node_modules/assemblyscript/std/assembly/util/casemap.ts +497 -0
- package/node_modules/assemblyscript/std/assembly/util/error.ts +54 -0
- package/node_modules/assemblyscript/std/assembly/util/hash.ts +117 -0
- package/node_modules/assemblyscript/std/assembly/util/math.ts +1922 -0
- package/node_modules/assemblyscript/std/assembly/util/memory.ts +290 -0
- package/node_modules/assemblyscript/std/assembly/util/number.ts +873 -0
- package/node_modules/assemblyscript/std/assembly/util/sort.ts +313 -0
- package/node_modules/assemblyscript/std/assembly/util/string.ts +1202 -0
- package/node_modules/assemblyscript/std/assembly/util/uri.ts +275 -0
- package/node_modules/assemblyscript/std/assembly/vector.ts +4 -0
- package/node_modules/assemblyscript/std/assembly.json +16 -0
- package/node_modules/assemblyscript/std/portable/index.d.ts +462 -0
- package/node_modules/assemblyscript/std/portable/index.js +416 -0
- package/node_modules/assemblyscript/std/portable.json +11 -0
- package/node_modules/assemblyscript/std/types/assembly/index.d.ts +1 -0
- package/node_modules/assemblyscript/std/types/assembly/package.json +3 -0
- package/node_modules/assemblyscript/std/types/portable/index.d.ts +1 -0
- package/node_modules/assemblyscript/std/types/portable/package.json +3 -0
- package/node_modules/assemblyscript/tsconfig-base.json +13 -0
- package/package.json +112 -0
- package/runtime/deps/assemblyscript/LICENSE +201 -0
- package/runtime/deps/assemblyscript/NOTICE +93 -0
- package/runtime/deps/assemblyscript/bin/asc.js +35 -0
- package/runtime/deps/assemblyscript/dist/asc.js +25035 -0
- package/runtime/deps/assemblyscript/dist/assemblyscript.js +337 -0
- package/runtime/deps/assemblyscript/node_modules/binaryen/LICENSE +201 -0
- package/runtime/deps/assemblyscript/node_modules/binaryen/index.js +28905 -0
- package/runtime/deps/assemblyscript/node_modules/binaryen/package.json +50 -0
- package/runtime/deps/assemblyscript/node_modules/long/LICENSE +202 -0
- package/runtime/deps/assemblyscript/node_modules/long/index.js +1581 -0
- package/runtime/deps/assemblyscript/node_modules/long/package.json +58 -0
- package/runtime/deps/assemblyscript/package.json +114 -0
- package/runtime/host/amxts.inc +85 -0
- package/runtime/licenses/AMXX-LICENSE.txt +34 -0
- package/runtime/licenses/AssemblyScript-LICENSE +201 -0
- package/runtime/licenses/AssemblyScript-NOTICE +93 -0
- package/runtime/licenses/Binaryen-LICENSE +201 -0
- package/runtime/licenses/Bun-LICENSE.md +84 -0
- package/runtime/licenses/GPL-3.0.txt +674 -0
- package/runtime/licenses/LLVM-LICENSE.TXT +279 -0
- package/runtime/licenses/README.md +25 -0
- package/runtime/licenses/WAMR-LICENSE +219 -0
- package/runtime/natives.txt +1095 -0
- package/runtime/patches/README.md +59 -0
- package/runtime/patches/assemblyscript-0.28.20-amxts.patch +14174 -0
- package/runtime/patches/wamr-2.4.5-amxts.patch +364 -0
- package/scripts/apply-docs.ts +508 -0
- package/scripts/asc.ts +27 -0
- package/scripts/auto-imports.ts +258 -0
- package/scripts/build-wasm.ts +595 -0
- package/scripts/check.ts +93 -0
- package/scripts/compile-cache.ts +179 -0
- package/scripts/compile.ts +401 -0
- package/scripts/docker-server.ts +73 -0
- package/scripts/docs/as/amxts.ts +210 -0
- package/scripts/docs/as/effects.ts +1684 -0
- package/scripts/docs/as/facade.ts +3302 -0
- package/scripts/docs/as/fs.ts +173 -0
- package/scripts/docs/as/lib/check.ts +68 -0
- package/scripts/docs/as/modules/http.ts +66 -0
- package/scripts/docs/as/os.ts +16 -0
- package/scripts/docs/as/promise.ts +132 -0
- package/scripts/docs/as/promise.types.ts +63 -0
- package/scripts/docs/as/vector.ts +48 -0
- package/scripts/docs/entities.ts +2338 -0
- package/scripts/docs/events.ts +245 -0
- package/scripts/docs/game.ts +497 -0
- package/scripts/docs/messages.ts +148 -0
- package/scripts/file-contents.ts +74 -0
- package/scripts/includes.ts +134 -0
- package/scripts/player-fields.ts +866 -0
- package/scripts/plugin-cache.ts +92 -0
- package/scripts/plugin-natives.ts +1157 -0
- package/scripts/prebuilt.ts +268 -0
- package/scripts/prepare.ts +134 -0
- package/scripts/project.ts +973 -0
- package/scripts/run.ts +28 -0
- package/scripts/shared-modules.ts +906 -0
- package/scripts/system.ts +33 -0
- package/scripts/tracked-fs.ts +115 -0
- package/scripts/typed-configs.ts +788 -0
- package/scripts/ui.ts +77 -0
- package/src/cli-api.mjs +175 -0
- package/src/parser/include-parser.ts +506 -0
- package/src/system.mjs +155 -0
- package/src/testing/compile-cache.ts +31 -0
- package/src/testing/compile.ts +222 -0
- package/src/testing/coroutines.ts +191 -0
- package/src/testing/index.ts +39 -0
- package/src/testing/kits.ts +55 -0
- package/src/testing/memory.ts +125 -0
- package/src/testing/natives.ts +925 -0
- package/src/testing/server.ts +1874 -0
- package/src/testing/setup.ts +63 -0
- package/src/testing/tables.ts +139 -0
- package/src/types.ts +51 -0
package/scripts/check.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Checks a module package before it is published - `npx amxts check` in the
|
|
2
|
+
// module's folder, as a CI step:
|
|
3
|
+
//
|
|
4
|
+
// - package.json's "amxts" field points at files that are there;
|
|
5
|
+
// - the module file declares itself with defineModule, meta.name its name;
|
|
6
|
+
// - README.md and LICENSE are there;
|
|
7
|
+
// - the natives compile, and include/<name>.inc is what the build writes from
|
|
8
|
+
// them - not stale after an edit of natives.ts. With `"contract": true`
|
|
9
|
+
// the include is the original's: the natives compiling means they match it.
|
|
10
|
+
//
|
|
11
|
+
// It writes nothing into the package; what it compiles goes to a temporary
|
|
12
|
+
// folder.
|
|
13
|
+
import type { PluginNative } from './plugin-natives';
|
|
14
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { basename, join, relative } from 'node:path';
|
|
17
|
+
import { importedName } from './auto-imports';
|
|
18
|
+
import { compileToWasm } from './compile';
|
|
19
|
+
import { includeName, pawnInclude } from './plugin-natives';
|
|
20
|
+
import { CORE_PLUGINS, loadProject, readDefinition, readPackage, sourcesFor } from './project';
|
|
21
|
+
import { c, log } from './ui';
|
|
22
|
+
|
|
23
|
+
function fail(message: string): void {
|
|
24
|
+
process.stderr.write(`${message}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const dir = process.cwd();
|
|
28
|
+
const problems: string[] = [];
|
|
29
|
+
const passed: string[] = [];
|
|
30
|
+
|
|
31
|
+
let pkg: ReturnType<typeof readPackage> = null;
|
|
32
|
+
try {
|
|
33
|
+
pkg = readPackage(dir);
|
|
34
|
+
} catch (problem) {
|
|
35
|
+
problems.push((problem as Error).message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!pkg && problems.length === 0) {
|
|
39
|
+
fail('amxts check: this folder is not a module package - package.json has no "amxts": { "module": ... }');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (pkg) {
|
|
44
|
+
passed.push(`"amxts" points at ${[pkg.module, pkg.natives, pkg.include].filter(Boolean).map(file => relative(dir, file!).replace(/\\/g, '/')).join(', ')}`);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const definition = readDefinition(pkg.module);
|
|
48
|
+
if (!definition) problems.push(`${relative(dir, pkg.module)}: no \`export default defineModule({ meta: { name: "${pkg.short}" }, ... })\``);
|
|
49
|
+
else if (!definition.name) problems.push(`${relative(dir, pkg.module)}: defineModule has no meta.name - "${pkg.short}"`);
|
|
50
|
+
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
|
+
} catch (problem) {
|
|
52
|
+
problems.push((problem as Error).message);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const file of ['README.md', 'LICENSE']) {
|
|
56
|
+
if (!existsSync(join(dir, file))) problems.push(`${file} is missing`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const project = loadProject(dir);
|
|
60
|
+
problems.push(...project.problems);
|
|
61
|
+
if (pkg.natives && project.problems.length === 0) await checkNatives(pkg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function checkNatives(module: NonNullable<typeof pkg>) {
|
|
65
|
+
const sources = sourcesFor(CORE_PLUGINS);
|
|
66
|
+
const out = mkdtempSync(join(tmpdir(), 'amxts-check-'));
|
|
67
|
+
try {
|
|
68
|
+
const natives: PluginNative[] = [];
|
|
69
|
+
const problem = await compileToWasm({ source: sources.ownerSource(module), root: CORE_PLUGINS }, join(out, `${module.short}.wasm`), false, natives);
|
|
70
|
+
if (problem) {
|
|
71
|
+
problems.push(`${relative(dir, module.natives!)} does not compile:\n${problem.trim()}`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (module.contract) {
|
|
75
|
+
passed.push(`${natives.length} natives match the contract ${relative(dir, module.include!).replace(/\\/g, '/')}`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const include = module.include ?? join(dir, 'include', `${includeName(module.short)}.inc`);
|
|
79
|
+
const expected = pawnInclude(module.short, natives).replace(/\r\n/g, '\n');
|
|
80
|
+
const actual = existsSync(include) ? readFileSync(include, 'utf8').replace(/\r\n/g, '\n') : null;
|
|
81
|
+
const name = relative(dir, include).replace(/\\/g, '/');
|
|
82
|
+
if (actual === null) problems.push(`${name} is missing - npx amxts build writes it from ${basename(module.natives!)}`);
|
|
83
|
+
else if (actual !== expected) problems.push(`${name} is stale: it is not what ${basename(module.natives!)} gives - run npx amxts build and commit it`);
|
|
84
|
+
else passed.push(`${name} is up to date with ${natives.length} natives`);
|
|
85
|
+
} finally {
|
|
86
|
+
rmSync(out, { recursive: true, force: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const line of passed) log.success(line);
|
|
91
|
+
for (const line of problems) log.error(line);
|
|
92
|
+
if (problems.length === 0) log.success(c.bold(`${pkg!.name} is ready to publish`));
|
|
93
|
+
process.exit(problems.length ? 1 : 0);
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Compiles kept on disk, reused while what they read is the same.
|
|
2
|
+
//
|
|
3
|
+
// A compile is keyed by everything that is not a file it read - the
|
|
4
|
+
// compiler, the hood's scripts, the flags, the project and its config - and
|
|
5
|
+
// stored with every file read it made (scripts/tracked-fs.ts): the entry, what
|
|
6
|
+
// it imports, the facade and the generated API, the module packages. It is
|
|
7
|
+
// taken again only while each of those reads sees the same thing it saw, so a
|
|
8
|
+
// hit is what compiling again would give, byte for byte.
|
|
9
|
+
//
|
|
10
|
+
// One key holds one entry: a compile made again replaces the one before, so a
|
|
11
|
+
// cache does not grow with every edit. The testing library keeps its compiles
|
|
12
|
+
// in one (src/testing/compile-cache.ts), a build its plugins in another
|
|
13
|
+
// (scripts/build-wasm.ts).
|
|
14
|
+
import type { Reads } from './tracked-fs';
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
16
|
+
import { dirname, join, resolve } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { listIncludes } from './includes';
|
|
19
|
+
import { currentProjectDir, loadProject } from './project';
|
|
20
|
+
import { hashOf, recordReads, unchanged } from './tracked-fs';
|
|
21
|
+
|
|
22
|
+
/** Bump when what an entry holds changes shape. */
|
|
23
|
+
const FORMAT = 1;
|
|
24
|
+
|
|
25
|
+
/** Entries made this long ago are removed; one still in use is made again. */
|
|
26
|
+
const UNUSED_FOR = 14 * 24 * 3600 * 1000;
|
|
27
|
+
|
|
28
|
+
const CORE = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The compiler: the patched AssemblyScript (with its standard library inside
|
|
32
|
+
* asc.js), its Binaryen, and every script of the hood a compile runs through -
|
|
33
|
+
* `entries`, this file and what they import, read off their import lines.
|
|
34
|
+
*/
|
|
35
|
+
export function codeIdentity(entries: string[]): string {
|
|
36
|
+
const parts = codeFiles(entries).map(file => `${file}:${existsSync(file) ? hashOf(readFileSync(file)) : 'missing'}`);
|
|
37
|
+
return hashOf(parts.join('\n'));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The files codeIdentity hashes, sorted. */
|
|
41
|
+
export function codeFiles(entries: string[]): string[] {
|
|
42
|
+
const files = new Set<string>();
|
|
43
|
+
const visit = (file: string) => {
|
|
44
|
+
if (files.has(file) || !existsSync(file)) return;
|
|
45
|
+
files.add(file);
|
|
46
|
+
if (!file.endsWith('.ts')) return;
|
|
47
|
+
for (const [, spec] of readFileSync(file, 'utf8').matchAll(/(?:from|import)\s*['"](\.[^'"]+)['"]/g)) {
|
|
48
|
+
const target = resolve(dirname(file), spec);
|
|
49
|
+
visit(/\.[cm]?js$|\.ts$/.test(target) ? target : `${target}.ts`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
[...entries, fileURLToPath(import.meta.url)].forEach(visit);
|
|
53
|
+
const deps = join(CORE, 'runtime/deps/assemblyscript');
|
|
54
|
+
for (const file of [
|
|
55
|
+
join(deps, 'dist/asc.js'),
|
|
56
|
+
join(deps, 'dist/assemblyscript.js'),
|
|
57
|
+
join(deps, 'node_modules/binaryen/package.json'),
|
|
58
|
+
join(CORE, 'runtime/patches/assemblyscript-0.28.20-amxts.patch'),
|
|
59
|
+
]) {
|
|
60
|
+
files.add(file);
|
|
61
|
+
}
|
|
62
|
+
return [...files].sort();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The project a compile is made in: its folder, its config's text, and the
|
|
67
|
+
* modules it resolved to with their options - a module's owner is compiled
|
|
68
|
+
* with the options its project gives it. The .inc files there are to be
|
|
69
|
+
* found in are listed once per run, so they are part of it too - but for
|
|
70
|
+
* `own`, the ones a build writes itself.
|
|
71
|
+
*/
|
|
72
|
+
function projectIdentity(own: string[]): string {
|
|
73
|
+
const dir = currentProjectDir();
|
|
74
|
+
const config = join(dir, 'amxts.config.ts');
|
|
75
|
+
return hashOf(JSON.stringify({
|
|
76
|
+
dir,
|
|
77
|
+
config: existsSync(config) ? hashOf(readFileSync(config)) : null,
|
|
78
|
+
project: loadProject(dir),
|
|
79
|
+
includes: listIncludes().filter(name => !own.includes(name)),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Uint8Array fields travel as base64.
|
|
84
|
+
function encode(_: string, value: unknown) {
|
|
85
|
+
return value instanceof Uint8Array ? { $bytes: Buffer.from(value).toString('base64') } : value;
|
|
86
|
+
}
|
|
87
|
+
function decode(_: string, value: any) {
|
|
88
|
+
return value && typeof value === 'object' && typeof value.$bytes === 'string' ? new Uint8Array(Buffer.from(value.$bytes, 'base64')) : value;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface Entry<T> {
|
|
92
|
+
reads: Reads;
|
|
93
|
+
value: T;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface DiskCache {
|
|
97
|
+
/** Whether results are kept on disk: not without a folder. */
|
|
98
|
+
on: boolean;
|
|
99
|
+
/** How often this process took a result from the disk, and made one. */
|
|
100
|
+
counts: { hits: number; misses: number };
|
|
101
|
+
/** What an earlier run made for `parts`, while every file it read is the same; else null. */
|
|
102
|
+
find: <T>(parts: unknown[]) => T | null;
|
|
103
|
+
/** `run`'s value, made now and kept for `parts` - unless `keep` says it is not worth keeping. */
|
|
104
|
+
make: <T>(parts: unknown[], run: () => Promise<T>, keep?: (value: T) => boolean) => Promise<T>;
|
|
105
|
+
/** find(), else make(). */
|
|
106
|
+
cached: <T>(parts: unknown[], run: () => Promise<T>, keep?: (value: T) => boolean) => Promise<T>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A cache in `dir` (none when it is null: every compile is made). `parts` is
|
|
111
|
+
* what a value depends on besides the files it reads: the entry, the flags. A
|
|
112
|
+
* value is JSON, with Uint8Arrays in it. `ownIncludes` are includes the
|
|
113
|
+
* compiles themselves put where includes are found - a deploy copies its
|
|
114
|
+
* plugins' includes to the server - which no compile depends on.
|
|
115
|
+
*/
|
|
116
|
+
export function diskCache(dir: string | null, code: () => string, ownIncludes: string[] = []): DiskCache {
|
|
117
|
+
const counts = { hits: 0, misses: 0 };
|
|
118
|
+
let identity: string | null = null;
|
|
119
|
+
const fileOf = (parts: unknown[]) => {
|
|
120
|
+
identity ??= hashOf(`${FORMAT}\n${code()}`);
|
|
121
|
+
return join(dir!, `${hashOf(JSON.stringify([identity, projectIdentity(ownIncludes), ...parts]))}.json`);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
let pruned = false;
|
|
125
|
+
const prune = () => {
|
|
126
|
+
if (pruned || !existsSync(dir!)) return;
|
|
127
|
+
pruned = true;
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
for (const name of readdirSync(dir!)) {
|
|
130
|
+
const file = join(dir!, name);
|
|
131
|
+
try {
|
|
132
|
+
if (now - statSync(file).mtimeMs > UNUSED_FOR) rmSync(file, { force: true });
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const find = <T>(parts: unknown[]): T | null => {
|
|
138
|
+
if (!dir) return null;
|
|
139
|
+
prune();
|
|
140
|
+
try {
|
|
141
|
+
const entry = JSON.parse(readFileSync(fileOf(parts), 'utf8'), decode) as Entry<T>;
|
|
142
|
+
if (!unchanged(entry.reads)) return null;
|
|
143
|
+
counts.hits++;
|
|
144
|
+
return entry.value;
|
|
145
|
+
} catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const make = async <T>(parts: unknown[], run: () => Promise<T>, keep: (value: T) => boolean = () => true): Promise<T> => {
|
|
151
|
+
if (!dir) return run();
|
|
152
|
+
counts.misses++;
|
|
153
|
+
const { reads, result } = await recordReads(run);
|
|
154
|
+
if (result.status === 'rejected') throw result.reason;
|
|
155
|
+
if (keep(result.value)) store(fileOf(parts), { reads, value: result.value });
|
|
156
|
+
return result.value;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
on: dir !== null,
|
|
161
|
+
counts,
|
|
162
|
+
find,
|
|
163
|
+
make,
|
|
164
|
+
cached: async (parts, run, keep) => find(parts) ?? make(parts, run, keep),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function store(file: string, entry: Entry<unknown>) {
|
|
169
|
+
// Beside it first, then into place: a run stopped halfway leaves the old
|
|
170
|
+
// entry or the new one, never half of one.
|
|
171
|
+
const partial = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
172
|
+
try {
|
|
173
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
174
|
+
writeFileSync(partial, JSON.stringify(entry, encode));
|
|
175
|
+
renameSync(partial, file);
|
|
176
|
+
} catch {
|
|
177
|
+
rmSync(partial, { force: true });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import type { PluginNative } from './plugin-natives';
|
|
2
|
+
import type { System } from './system';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
// Compiles one plugin: .ts in, .aot out.
|
|
6
|
+
//
|
|
7
|
+
// Both halves of the project use this one function - `bun run plugins` here,
|
|
8
|
+
// and amxts-compile.exe on a server - so that a plugin compiles the same way
|
|
9
|
+
// in both places. In particular the `~/` alias, which is ours rather than
|
|
10
|
+
// AssemblyScript's and lives in the readFile hook below.
|
|
11
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { basename, dirname, relative, resolve } from 'node:path';
|
|
13
|
+
// The Binaryen asc itself runs on - the same copy, so the compiler a server
|
|
14
|
+
// gets carries one - for the one pass asc does not run: Asyncify.
|
|
15
|
+
// @ts-ignore - shipped as JavaScript, with types beside it we do not need here
|
|
16
|
+
import * as assemblyscript from '../runtime/deps/assemblyscript/dist/assemblyscript.js';
|
|
17
|
+
// @ts-ignore - its types are beside it, under a path tsconfig does not map
|
|
18
|
+
import binaryen from '../runtime/deps/assemblyscript/node_modules/binaryen/index.js';
|
|
19
|
+
import { ascMain } from './asc';
|
|
20
|
+
import { playerFieldsBuild } from './player-fields';
|
|
21
|
+
import { includeName, nativeContract, nativesTransform, pawnInclude } from './plugin-natives';
|
|
22
|
+
import { ascPath, sourcesFor } from './project';
|
|
23
|
+
import { sharedModulesBuild } from './shared-modules';
|
|
24
|
+
import { HOST_SYSTEM, TARGET_ABI } from './system';
|
|
25
|
+
|
|
26
|
+
export interface Plugin {
|
|
27
|
+
/** The .ts to compile. */
|
|
28
|
+
source: string;
|
|
29
|
+
/** The .aot to write. */
|
|
30
|
+
output: string;
|
|
31
|
+
/** What `~/` means: the folder plugins and their libraries live in. */
|
|
32
|
+
root: string;
|
|
33
|
+
wamrc: string;
|
|
34
|
+
signatures: string;
|
|
35
|
+
/**
|
|
36
|
+
* The system of the server that loads it: the .aot is written in that
|
|
37
|
+
* system's object format (scripts/system.ts). This machine's by default.
|
|
38
|
+
*/
|
|
39
|
+
system?: System;
|
|
40
|
+
/**
|
|
41
|
+
* Compiled for a development loop (`amxts dev`): Binaryen's -O1 in place
|
|
42
|
+
* of asc's full optimisation, and wamrc at -O0 - a few seconds for a small
|
|
43
|
+
* plugin rather than ten. It does the same, a little slower.
|
|
44
|
+
*/
|
|
45
|
+
quick?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Binaryen's optimisation level: a quick build's, and a full one's - asc's own --optimize. */
|
|
49
|
+
const OPTIMIZE = { quick: 1, full: 3 };
|
|
50
|
+
|
|
51
|
+
/** The hood's exports every plugin carries; see compilePlugin. */
|
|
52
|
+
export const HOOD_EXPORTS = '__amxts_exports.ts';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The second entry of a plugin that never waits: the hood has nothing to
|
|
56
|
+
* export, but its globals - Promise, defineModule - are there for any
|
|
57
|
+
* plugin, one that imports nothing included.
|
|
58
|
+
*/
|
|
59
|
+
export const BASE_EXPORTS = 'import "~/facade";\n';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What the module's coroutine scheduler calls, for a plugin that uses
|
|
63
|
+
* Promise or async/await (as/promise.ts, runtime/src/module.cpp). Only such a
|
|
64
|
+
* plugin carries them: exporting them from every plugin would compile the
|
|
65
|
+
* whole scheduler into plugins that never wait for anything.
|
|
66
|
+
*/
|
|
67
|
+
export const ASYNC_EXPORTS = BASE_EXPORTS + [
|
|
68
|
+
'export function __co_next(): i32 { return __co_next_job(); }',
|
|
69
|
+
'export function __co_done(outcome: i32): void { __co_resumed(outcome); }',
|
|
70
|
+
'export function __co_stack(): usize { return __co_sp(); }',
|
|
71
|
+
'export function __co_stack_set(sp: usize): void { __co_set_sp(sp); }',
|
|
72
|
+
'export function __co_save(id: i32, lo: usize, hi: usize): void { __co_park(id, lo, hi); }',
|
|
73
|
+
'export function __co_saved(id: i32): usize { return __co_parked(id); }',
|
|
74
|
+
'export function __co_restore(id: i32, lo: usize): void { __co_unpark(id, lo); }',
|
|
75
|
+
'',
|
|
76
|
+
].join('\n');
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A transform that moves the imports at the end of a file - the ones the
|
|
80
|
+
* build adds (scripts/auto-imports.ts), after the file's last line - to its
|
|
81
|
+
* top, as ES modules run an imported file before the one that imports it:
|
|
82
|
+
* asc compiles an imported file's top level where the import stands. The
|
|
83
|
+
* imports a file writes itself stay where they are.
|
|
84
|
+
*/
|
|
85
|
+
export class HoistImports {
|
|
86
|
+
afterParse(parser: any): void {
|
|
87
|
+
for (const source of parser.sources) {
|
|
88
|
+
const statements: any[] = source.statements;
|
|
89
|
+
const last = statements.findLastIndex(statement => statement.kind !== assemblyscript.NodeKind.Import);
|
|
90
|
+
if (last < 0 || last === statements.length - 1) continue;
|
|
91
|
+
source.statements = [...statements.slice(last + 1), ...statements.slice(0, last + 1)];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The import a coroutine parks in - the one Asyncify unwinds from. */
|
|
97
|
+
export const SUSPEND_IMPORT = 'env.co_suspend';
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Where compilePlugin writes the Pawn include for a plugin's natives: beside
|
|
101
|
+
* the .aot, named after the plugin - or, for a plugin that implements an
|
|
102
|
+
* include (a contract), under that include's own name, so a Pawn plugin's
|
|
103
|
+
* `#include` of it works whatever the module is called.
|
|
104
|
+
*/
|
|
105
|
+
export function includePath(output: string, natives: PluginNative[] = []): string {
|
|
106
|
+
const contract = nativeContract(natives);
|
|
107
|
+
const name = contract ? basename(contract.file).replace(/\.inc$/, '') : includeName(basename(output).replace(/\.aot$/, ''));
|
|
108
|
+
return resolve(dirname(output), `${name}.inc`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** What went wrong, or null when the plugin compiled. `natives` gets the plugin's natives. */
|
|
112
|
+
export async function compilePlugin(plugin: Plugin, natives: PluginNative[] = []): Promise<string | null> {
|
|
113
|
+
const wasm = plugin.output.replace(/\.aot$/, '.wasm');
|
|
114
|
+
mkdirSync(dirname(plugin.output), { recursive: true });
|
|
115
|
+
|
|
116
|
+
const problem = await compileToWasm(plugin, wasm, false, natives);
|
|
117
|
+
if (problem) return problem;
|
|
118
|
+
return compileToMachineCode(plugin, wasm, natives);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The second half of compilePlugin: wamrc, from `wasm` to the plugin's .aot
|
|
123
|
+
* for its system, with the include of its `natives` beside it. One wasm
|
|
124
|
+
* makes the .aot of either system (scripts/prebuilt.ts makes both).
|
|
125
|
+
*/
|
|
126
|
+
export function compileToMachineCode(plugin: Plugin, wasm: string, natives: PluginNative[]): string | null {
|
|
127
|
+
// Written beside the .aot and moved over it once it passed: a server that
|
|
128
|
+
// reads the build folder where it is - the Docker one - reloads a plugin
|
|
129
|
+
// the moment its file changes, and must never see half of one; and a
|
|
130
|
+
// plugin that fails keeps the last good build.
|
|
131
|
+
mkdirSync(dirname(plugin.output), { recursive: true });
|
|
132
|
+
const part = `${plugin.output}.part`;
|
|
133
|
+
const wamrc = (level: string[]) => spawnSync(plugin.wamrc, [
|
|
134
|
+
'--target=i386',
|
|
135
|
+
`--target-abi=${TARGET_ABI[plugin.system ?? HOST_SYSTEM]}`,
|
|
136
|
+
`--native-signatures=${plugin.signatures}`,
|
|
137
|
+
...level,
|
|
138
|
+
'-o',
|
|
139
|
+
part,
|
|
140
|
+
wasm,
|
|
141
|
+
], { encoding: 'utf-8' });
|
|
142
|
+
let compile = wamrc(plugin.quick ? ['--opt-level=0'] : []);
|
|
143
|
+
// At -O0 a big function keeps a big stack frame, and for a frame over
|
|
144
|
+
// 4 KB LLVM calls _chkstk on Windows, which the module's loader does not
|
|
145
|
+
// have ("resolve symbol _chkstk failed"): such a plugin - menu-core is
|
|
146
|
+
// one - is compiled again at wamrc's own level. The .aot names the symbol.
|
|
147
|
+
if (plugin.quick && compile.status === 0 && readFileSync(part).includes('_chkstk')) compile = wamrc([]);
|
|
148
|
+
|
|
149
|
+
const out = `${compile.stdout}${compile.stderr}`;
|
|
150
|
+
|
|
151
|
+
if (compile.status !== 0) {
|
|
152
|
+
rmSync(part, { force: true });
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// A signature that disagrees with its import is not an error to wamrc: it
|
|
157
|
+
// drops that native to the generic path, 28 ns a call instead of 2, and
|
|
158
|
+
// says so only in this warning. That means the API and the module have
|
|
159
|
+
// drifted apart, which is worth refusing rather than running slowly.
|
|
160
|
+
if (/failed to check signature/.test(out)) {
|
|
161
|
+
rmSync(part, { force: true });
|
|
162
|
+
return `a native signature does not match its import - this plugin kit does not belong to this module\n${out}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
writeInclude(plugin.output, natives);
|
|
166
|
+
renameSync(part, plugin.output);
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The plugin's `export function`s, as a Pawn plugin includes them, beside
|
|
172
|
+
* its .aot. One that exports nothing leaves no include behind, not even a
|
|
173
|
+
* stale one; neither does a contract, whose include has a name of its own.
|
|
174
|
+
*/
|
|
175
|
+
export function writeInclude(output: string, natives: PluginNative[]): void {
|
|
176
|
+
const include = includePath(output, natives);
|
|
177
|
+
const ownName = includePath(output);
|
|
178
|
+
if (include !== ownName) rmSync(ownName, { force: true });
|
|
179
|
+
if (natives.length) writeFileSync(include, pawnInclude(basename(output).replace(/\.aot$/, ''), natives));
|
|
180
|
+
else rmSync(include, { force: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The first half of compilePlugin: the plugin's .ts to `wasm`, ready for
|
|
185
|
+
* wamrc. Tests run the result as it is; with `names` it keeps its function
|
|
186
|
+
* names, so a test can see what Asyncify instrumented. With `natives`, the
|
|
187
|
+
* plugin's `export function`s become natives, listed there.
|
|
188
|
+
*/
|
|
189
|
+
export async function compileToWasm(
|
|
190
|
+
plugin: Pick<Plugin, 'source' | 'root' | 'quick'>,
|
|
191
|
+
wasm: string,
|
|
192
|
+
names = false,
|
|
193
|
+
natives?: PluginNative[],
|
|
194
|
+
): Promise<string | null> {
|
|
195
|
+
// A quick build leaves the optimising to one Binaryen pass afterwards.
|
|
196
|
+
const flags = [...(plugin.quick ? [] : ['--optimize']), ...(names ? ['--debug'] : [])];
|
|
197
|
+
const level = plugin.quick ? OPTIMIZE.quick : OPTIMIZE.full;
|
|
198
|
+
let problem = await compileWasm(plugin, wasm, BASE_EXPORTS, flags, natives);
|
|
199
|
+
if (problem) return problem;
|
|
200
|
+
|
|
201
|
+
// A plugin that makes a promise - an async function, fetch, sleep - has
|
|
202
|
+
// the host drive its jobs, so it is compiled again with the scheduler's
|
|
203
|
+
// exports. Only then, and only when a coroutine can park, does Asyncify
|
|
204
|
+
// run: a plugin without either is the same wasm it always was.
|
|
205
|
+
if (importsOf(readFileSync(wasm)).has('env.co_wake')) {
|
|
206
|
+
if (natives) natives.length = 0;
|
|
207
|
+
problem = await compileWasm(plugin, wasm, ASYNC_EXPORTS, flags, natives);
|
|
208
|
+
if (problem) return problem;
|
|
209
|
+
}
|
|
210
|
+
const binary = readFileSync(wasm);
|
|
211
|
+
if (importsOf(binary).has(SUSPEND_IMPORT)) writeFileSync(wasm, asyncify(binary, names, level));
|
|
212
|
+
else if (plugin.quick) writeFileSync(wasm, optimized(binary, level));
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Binaryen's optimisation at `level`: what a quick build runs in place of asc's. */
|
|
217
|
+
function optimized(wasm: Uint8Array, level: number): Uint8Array {
|
|
218
|
+
const module = binaryen.readBinary(wasm);
|
|
219
|
+
try {
|
|
220
|
+
module.setFeatures(binaryen.Features.All);
|
|
221
|
+
binaryen.setOptimizeLevel(level);
|
|
222
|
+
binaryen.setShrinkLevel(0);
|
|
223
|
+
module.optimize();
|
|
224
|
+
return module.emitBinary();
|
|
225
|
+
} finally {
|
|
226
|
+
module.dispose();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* asc, from the plugin's .ts to `wasm`, with `hoodExports` as the second entry.
|
|
232
|
+
* With `natives`, the plugin's `export function`s become its natives and are
|
|
233
|
+
* listed there; without, they stay plain wasm exports - what a test host that
|
|
234
|
+
* calls a fixture's functions by name wants.
|
|
235
|
+
*/
|
|
236
|
+
async function compileWasm(
|
|
237
|
+
plugin: Pick<Plugin, 'source' | 'root'>,
|
|
238
|
+
wasm: string,
|
|
239
|
+
hoodExports: string,
|
|
240
|
+
flags: string[],
|
|
241
|
+
natives?: PluginNative[],
|
|
242
|
+
): Promise<string | null> {
|
|
243
|
+
// --exportTable is not optional: a handler reaches the module as its index
|
|
244
|
+
// in the function table, and without this asc emits no table at all, so
|
|
245
|
+
// every index points at nothing.
|
|
246
|
+
// asc is given the entry relative to the root it is told to read from, so
|
|
247
|
+
// that a plugin in a subfolder and the alias below resolve the same way.
|
|
248
|
+
// A module package's plugin compiles as ~/<name>.ts, a project's plugin as
|
|
249
|
+
// its place under ~/ (scripts/project.ts).
|
|
250
|
+
const sources = sourcesFor(plugin.root);
|
|
251
|
+
if (sources.project.problems.length) return sources.project.problems.join('\n');
|
|
252
|
+
const entry = sources.entry(plugin.source);
|
|
253
|
+
// Player's fields that plugins add, from every file the plugin reaches (scripts/player-fields.ts).
|
|
254
|
+
const playerFields = playerFieldsBuild();
|
|
255
|
+
// The modules it reaches that another plugin owns come as proxies (scripts/shared-modules.ts).
|
|
256
|
+
let shared: Awaited<ReturnType<typeof sharedModulesBuild>>;
|
|
257
|
+
try {
|
|
258
|
+
shared = await sharedModulesBuild(plugin.root, entry);
|
|
259
|
+
} catch (problem) {
|
|
260
|
+
return String((problem as Error).message ?? problem);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const { error, stderr } = await ascMain(
|
|
264
|
+
[
|
|
265
|
+
entry,
|
|
266
|
+
// A second entry nobody writes: what the hood exports - the
|
|
267
|
+
// coroutine scheduler's functions, for a plugin that waits (only an
|
|
268
|
+
// entry file's exports reach the wasm).
|
|
269
|
+
HOOD_EXPORTS,
|
|
270
|
+
'--outFile',
|
|
271
|
+
wasm,
|
|
272
|
+
'--exportTable',
|
|
273
|
+
...flags,
|
|
274
|
+
],
|
|
275
|
+
{
|
|
276
|
+
readFile(filename: string, baseDir: string): string | null {
|
|
277
|
+
if (filename.replace(/\\/g, '/').endsWith(HOOD_EXPORTS)) return hoodExports;
|
|
278
|
+
|
|
279
|
+
// `~/` is the plugins folder: ascPath answers the alias.
|
|
280
|
+
const path = ascPath(plugin.root, filename, baseDir);
|
|
281
|
+
const text = sources.read(path);
|
|
282
|
+
return text === null ? null : shared.read(path, playerFields.read(relative('.', (sources.real(path) ?? path)), text));
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
writeFile(filename: string, contents: string | Uint8Array): void {
|
|
286
|
+
// The name asc passes back is the one it was given in --outFile,
|
|
287
|
+
// so it is already where the caller wants it.
|
|
288
|
+
const path = resolve(filename);
|
|
289
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
290
|
+
writeFileSync(path, contents);
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
listFiles(): string[] {
|
|
294
|
+
return [];
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
transforms: natives
|
|
298
|
+
? [HoistImports, nativesTransform(entry, natives, plugin.root), playerFields.transform, shared.transform]
|
|
299
|
+
: [HoistImports, playerFields.transform, shared.transform],
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// An import of a module the config does not list says so first: asc's
|
|
304
|
+
// "not found" after it is only the consequence.
|
|
305
|
+
if (sources.problems.length) return [...sources.problems, stderr.toString()].join('\n').trim();
|
|
306
|
+
|
|
307
|
+
// A transform's refusal - a native whose signature cannot cross - is an
|
|
308
|
+
// error with nothing written to stderr.
|
|
309
|
+
return error ? (stderr.toString() || String(error.message ?? error)) : null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Asyncify, so that a coroutine can park in co_suspend and be rewound later.
|
|
314
|
+
*
|
|
315
|
+
* What to instrument is Binaryen's own call graph from that one import: the
|
|
316
|
+
* async function bodies, __await, and whatever calls them directly. Indirect
|
|
317
|
+
* calls are left out (asyncify-ignore-indirect), and that is exact rather
|
|
318
|
+
* than a guess: an async function is always entered by the host - its
|
|
319
|
+
* prologue asks co_spawn to call it - so no `await` is ever reached through
|
|
320
|
+
* a function table, and a listener that is async returns to its caller at its
|
|
321
|
+
* first `await` like any other call. Nothing can be missing from the list,
|
|
322
|
+
* which is the failure that hangs rather than traps.
|
|
323
|
+
*/
|
|
324
|
+
export function asyncify(wasm: Uint8Array, names = false, level = OPTIMIZE.full): Uint8Array {
|
|
325
|
+
const module = binaryen.readBinary(wasm);
|
|
326
|
+
try {
|
|
327
|
+
module.setFeatures(binaryen.Features.All);
|
|
328
|
+
binaryen.setPassArgument('asyncify-imports', SUSPEND_IMPORT);
|
|
329
|
+
binaryen.setPassArgument('asyncify-ignore-indirect', '1');
|
|
330
|
+
// asc's own --optimize levels (a quick build's lower one), so the rest
|
|
331
|
+
// of the module comes out as it went in.
|
|
332
|
+
binaryen.setOptimizeLevel(level);
|
|
333
|
+
binaryen.setShrinkLevel(0);
|
|
334
|
+
binaryen.setDebugInfo(names);
|
|
335
|
+
module.runPasses(['asyncify']);
|
|
336
|
+
module.optimize();
|
|
337
|
+
if (!module.validate()) throw new Error('Asyncify produced an invalid module');
|
|
338
|
+
return module.emitBinary();
|
|
339
|
+
} finally {
|
|
340
|
+
binaryen.setPassArgument('asyncify-imports', null);
|
|
341
|
+
binaryen.setPassArgument('asyncify-ignore-indirect', null);
|
|
342
|
+
binaryen.setDebugInfo(false);
|
|
343
|
+
module.dispose();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** `module.name` of every function a wasm binary imports. */
|
|
348
|
+
export function importsOf(wasm: Uint8Array): Set<string> {
|
|
349
|
+
const names = new Set<string>();
|
|
350
|
+
let at = 8;
|
|
351
|
+
|
|
352
|
+
const u32 = () => {
|
|
353
|
+
let result = 0;
|
|
354
|
+
let shift = 0;
|
|
355
|
+
for (;;) {
|
|
356
|
+
const byte = wasm[at++];
|
|
357
|
+
result |= (byte & 0x7F) << shift;
|
|
358
|
+
if ((byte & 0x80) === 0) return result >>> 0;
|
|
359
|
+
shift += 7;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const text = () => {
|
|
363
|
+
const length = u32();
|
|
364
|
+
const value = new TextDecoder().decode(wasm.subarray(at, at + length));
|
|
365
|
+
at += length;
|
|
366
|
+
return value;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
while (at < wasm.length) {
|
|
370
|
+
const id = wasm[at++];
|
|
371
|
+
const size = u32();
|
|
372
|
+
const end = at + size;
|
|
373
|
+
if (id !== 2) {
|
|
374
|
+
at = end;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
for (let count = u32(); count > 0; count--) {
|
|
378
|
+
const name = `${text()}.${text()}`;
|
|
379
|
+
const kind = wasm[at++];
|
|
380
|
+
if (kind === 0) {
|
|
381
|
+
u32();
|
|
382
|
+
names.add(name);
|
|
383
|
+
} else if (kind === 1) {
|
|
384
|
+
at++;
|
|
385
|
+
const flags = u32();
|
|
386
|
+
u32();
|
|
387
|
+
if (flags & 1) u32();
|
|
388
|
+
} else if (kind === 2) {
|
|
389
|
+
const flags = u32();
|
|
390
|
+
u32();
|
|
391
|
+
if (flags & 1) u32();
|
|
392
|
+
} else if (kind === 3) {
|
|
393
|
+
at += 2;
|
|
394
|
+
} else {
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
return names;
|
|
401
|
+
}
|