@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.
Files changed (247) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/README.ru.md +162 -0
  4. package/amxmodx/base/include/amxconst.inc +594 -0
  5. package/amxmodx/base/include/amxmisc.inc +902 -0
  6. package/amxmodx/base/include/amxmodx.inc +3447 -0
  7. package/amxmodx/base/include/amxmodx_version.inc +20 -0
  8. package/amxmodx/base/include/cellarray.inc +525 -0
  9. package/amxmodx/base/include/cellstack.inc +166 -0
  10. package/amxmodx/base/include/celltrie.inc +412 -0
  11. package/amxmodx/base/include/core.inc +202 -0
  12. package/amxmodx/base/include/csstats.inc +298 -0
  13. package/amxmodx/base/include/csstats_const.inc +29 -0
  14. package/amxmodx/base/include/cstrike.inc +1287 -0
  15. package/amxmodx/base/include/cstrike_const.inc +521 -0
  16. package/amxmodx/base/include/csx.inc +259 -0
  17. package/amxmodx/base/include/cvars.inc +547 -0
  18. package/amxmodx/base/include/datapack.inc +161 -0
  19. package/amxmodx/base/include/dbi.inc +154 -0
  20. package/amxmodx/base/include/dodconst.inc +152 -0
  21. package/amxmodx/base/include/dodfun.inc +152 -0
  22. package/amxmodx/base/include/dodstats.inc +69 -0
  23. package/amxmodx/base/include/dodx.inc +161 -0
  24. package/amxmodx/base/include/engine.inc +1343 -0
  25. package/amxmodx/base/include/engine_const.inc +310 -0
  26. package/amxmodx/base/include/engine_stocks.inc +248 -0
  27. package/amxmodx/base/include/esf.inc +70 -0
  28. package/amxmodx/base/include/esf_const.inc +74 -0
  29. package/amxmodx/base/include/fakemeta.inc +1127 -0
  30. package/amxmodx/base/include/fakemeta_const.inc +793 -0
  31. package/amxmodx/base/include/fakemeta_stocks.inc +395 -0
  32. package/amxmodx/base/include/fakemeta_util.inc +882 -0
  33. package/amxmodx/base/include/file.inc +666 -0
  34. package/amxmodx/base/include/float.inc +422 -0
  35. package/amxmodx/base/include/fun.inc +333 -0
  36. package/amxmodx/base/include/gameconfig.inc +93 -0
  37. package/amxmodx/base/include/geoip.inc +264 -0
  38. package/amxmodx/base/include/ham_const.inc +4352 -0
  39. package/amxmodx/base/include/hamsandwich.inc +439 -0
  40. package/amxmodx/base/include/hlsdk_const.inc +808 -0
  41. package/amxmodx/base/include/json.inc +777 -0
  42. package/amxmodx/base/include/lang.inc +119 -0
  43. package/amxmodx/base/include/message_const.inc +1130 -0
  44. package/amxmodx/base/include/message_stocks.inc +161 -0
  45. package/amxmodx/base/include/messages.inc +604 -0
  46. package/amxmodx/base/include/newmenus.inc +387 -0
  47. package/amxmodx/base/include/ns.inc +754 -0
  48. package/amxmodx/base/include/ns_const.inc +196 -0
  49. package/amxmodx/base/include/nvault.inc +154 -0
  50. package/amxmodx/base/include/regex.inc +348 -0
  51. package/amxmodx/base/include/sockets.inc +183 -0
  52. package/amxmodx/base/include/sorting.inc +107 -0
  53. package/amxmodx/base/include/sqlx.inc +582 -0
  54. package/amxmodx/base/include/string.inc +786 -0
  55. package/amxmodx/base/include/string_const.inc +156 -0
  56. package/amxmodx/base/include/string_stocks.inc +325 -0
  57. package/amxmodx/base/include/textparse_ini.inc +214 -0
  58. package/amxmodx/base/include/textparse_smc.inc +261 -0
  59. package/amxmodx/base/include/tfcconst.inc +84 -0
  60. package/amxmodx/base/include/tfcstats.inc +62 -0
  61. package/amxmodx/base/include/tfcx.inc +148 -0
  62. package/amxmodx/base/include/time.inc +118 -0
  63. package/amxmodx/base/include/tsconst.inc +154 -0
  64. package/amxmodx/base/include/tsfun.inc +198 -0
  65. package/amxmodx/base/include/tsstats.inc +65 -0
  66. package/amxmodx/base/include/tsx.inc +88 -0
  67. package/amxmodx/base/include/vault.inc +60 -0
  68. package/amxmodx/base/include/vector.inc +131 -0
  69. package/amxmodx/base/include/xs.inc +1570 -0
  70. package/as/amxts.d.ts +153 -0
  71. package/as/constants.ts +4730 -0
  72. package/as/effects.ts +1449 -0
  73. package/as/entities.ts +5482 -0
  74. package/as/events.ts +3054 -0
  75. package/as/facade.ts +4639 -0
  76. package/as/flags.ts +299 -0
  77. package/as/fs.ts +287 -0
  78. package/as/hooks.ts +18536 -0
  79. package/as/kit.ts +25 -0
  80. package/as/lib/check.ts +86 -0
  81. package/as/modules/http.ts +184 -0
  82. package/as/natives.ts +11845 -0
  83. package/as/os.ts +30 -0
  84. package/as/promise.ts +1392 -0
  85. package/as/promise.types.d.ts +82 -0
  86. package/as/remote.ts +288 -0
  87. package/as/tsconfig.json +22 -0
  88. package/as/vector.ts +65 -0
  89. package/bin/amxts.mjs +10 -0
  90. package/includes/menu_core.inc +355 -0
  91. package/includes/order.txt +54 -0
  92. package/includes/sources.json +27 -0
  93. package/includes/universal_config.inc +355 -0
  94. package/lint/oxlint-plugin.mjs +77 -0
  95. package/node_modules/assemblyscript/LICENSE +201 -0
  96. package/node_modules/assemblyscript/NOTICE +93 -0
  97. package/node_modules/assemblyscript/package.json +63 -0
  98. package/node_modules/assemblyscript/std/README.md +6 -0
  99. package/node_modules/assemblyscript/std/assembly/array.ts +526 -0
  100. package/node_modules/assemblyscript/std/assembly/arraybuffer.ts +77 -0
  101. package/node_modules/assemblyscript/std/assembly/atomics.ts +127 -0
  102. package/node_modules/assemblyscript/std/assembly/bindings/asyncify.ts +16 -0
  103. package/node_modules/assemblyscript/std/assembly/bindings/dom.ts +291 -0
  104. package/node_modules/assemblyscript/std/assembly/bindings/node.ts +6 -0
  105. package/node_modules/assemblyscript/std/assembly/builtins.ts +2631 -0
  106. package/node_modules/assemblyscript/std/assembly/compat.ts +2 -0
  107. package/node_modules/assemblyscript/std/assembly/console.ts +42 -0
  108. package/node_modules/assemblyscript/std/assembly/crypto.ts +9 -0
  109. package/node_modules/assemblyscript/std/assembly/dataview.ts +181 -0
  110. package/node_modules/assemblyscript/std/assembly/date.ts +373 -0
  111. package/node_modules/assemblyscript/std/assembly/diagnostics.ts +11 -0
  112. package/node_modules/assemblyscript/std/assembly/error.ts +44 -0
  113. package/node_modules/assemblyscript/std/assembly/function.ts +38 -0
  114. package/node_modules/assemblyscript/std/assembly/index.d.ts +2822 -0
  115. package/node_modules/assemblyscript/std/assembly/iterator.ts +35 -0
  116. package/node_modules/assemblyscript/std/assembly/map.ts +260 -0
  117. package/node_modules/assemblyscript/std/assembly/math.ts +3289 -0
  118. package/node_modules/assemblyscript/std/assembly/memory.ts +123 -0
  119. package/node_modules/assemblyscript/std/assembly/number.ts +388 -0
  120. package/node_modules/assemblyscript/std/assembly/object.ts +36 -0
  121. package/node_modules/assemblyscript/std/assembly/performance.ts +9 -0
  122. package/node_modules/assemblyscript/std/assembly/polyfills.ts +27 -0
  123. package/node_modules/assemblyscript/std/assembly/process.ts +50 -0
  124. package/node_modules/assemblyscript/std/assembly/reference.ts +48 -0
  125. package/node_modules/assemblyscript/std/assembly/regexp.ts +12 -0
  126. package/node_modules/assemblyscript/std/assembly/rt/README.md +83 -0
  127. package/node_modules/assemblyscript/std/assembly/rt/common.ts +81 -0
  128. package/node_modules/assemblyscript/std/assembly/rt/index-incremental.ts +2 -0
  129. package/node_modules/assemblyscript/std/assembly/rt/index-minimal.ts +2 -0
  130. package/node_modules/assemblyscript/std/assembly/rt/index-stub.ts +1 -0
  131. package/node_modules/assemblyscript/std/assembly/rt/index.d.ts +37 -0
  132. package/node_modules/assemblyscript/std/assembly/rt/itcms.ts +419 -0
  133. package/node_modules/assemblyscript/std/assembly/rt/rtrace.ts +15 -0
  134. package/node_modules/assemblyscript/std/assembly/rt/stub.ts +133 -0
  135. package/node_modules/assemblyscript/std/assembly/rt/tcms.ts +254 -0
  136. package/node_modules/assemblyscript/std/assembly/rt/tlsf.ts +592 -0
  137. package/node_modules/assemblyscript/std/assembly/rt.ts +87 -0
  138. package/node_modules/assemblyscript/std/assembly/set.ts +220 -0
  139. package/node_modules/assemblyscript/std/assembly/shared/feature.ts +65 -0
  140. package/node_modules/assemblyscript/std/assembly/shared/runtime.ts +11 -0
  141. package/node_modules/assemblyscript/std/assembly/shared/target.ts +11 -0
  142. package/node_modules/assemblyscript/std/assembly/shared/tsconfig.json +11 -0
  143. package/node_modules/assemblyscript/std/assembly/shared/typeinfo.ts +72 -0
  144. package/node_modules/assemblyscript/std/assembly/staticarray.ts +401 -0
  145. package/node_modules/assemblyscript/std/assembly/string.ts +847 -0
  146. package/node_modules/assemblyscript/std/assembly/symbol.ts +114 -0
  147. package/node_modules/assemblyscript/std/assembly/table.ts +16 -0
  148. package/node_modules/assemblyscript/std/assembly/tsconfig.json +6 -0
  149. package/node_modules/assemblyscript/std/assembly/typedarray.ts +1945 -0
  150. package/node_modules/assemblyscript/std/assembly/uri.ts +17 -0
  151. package/node_modules/assemblyscript/std/assembly/util/bytes.ts +107 -0
  152. package/node_modules/assemblyscript/std/assembly/util/casemap.ts +497 -0
  153. package/node_modules/assemblyscript/std/assembly/util/error.ts +54 -0
  154. package/node_modules/assemblyscript/std/assembly/util/hash.ts +117 -0
  155. package/node_modules/assemblyscript/std/assembly/util/math.ts +1922 -0
  156. package/node_modules/assemblyscript/std/assembly/util/memory.ts +290 -0
  157. package/node_modules/assemblyscript/std/assembly/util/number.ts +873 -0
  158. package/node_modules/assemblyscript/std/assembly/util/sort.ts +313 -0
  159. package/node_modules/assemblyscript/std/assembly/util/string.ts +1202 -0
  160. package/node_modules/assemblyscript/std/assembly/util/uri.ts +275 -0
  161. package/node_modules/assemblyscript/std/assembly/vector.ts +4 -0
  162. package/node_modules/assemblyscript/std/assembly.json +16 -0
  163. package/node_modules/assemblyscript/std/portable/index.d.ts +462 -0
  164. package/node_modules/assemblyscript/std/portable/index.js +416 -0
  165. package/node_modules/assemblyscript/std/portable.json +11 -0
  166. package/node_modules/assemblyscript/std/types/assembly/index.d.ts +1 -0
  167. package/node_modules/assemblyscript/std/types/assembly/package.json +3 -0
  168. package/node_modules/assemblyscript/std/types/portable/index.d.ts +1 -0
  169. package/node_modules/assemblyscript/std/types/portable/package.json +3 -0
  170. package/node_modules/assemblyscript/tsconfig-base.json +13 -0
  171. package/package.json +112 -0
  172. package/runtime/deps/assemblyscript/LICENSE +201 -0
  173. package/runtime/deps/assemblyscript/NOTICE +93 -0
  174. package/runtime/deps/assemblyscript/bin/asc.js +35 -0
  175. package/runtime/deps/assemblyscript/dist/asc.js +25035 -0
  176. package/runtime/deps/assemblyscript/dist/assemblyscript.js +337 -0
  177. package/runtime/deps/assemblyscript/node_modules/binaryen/LICENSE +201 -0
  178. package/runtime/deps/assemblyscript/node_modules/binaryen/index.js +28905 -0
  179. package/runtime/deps/assemblyscript/node_modules/binaryen/package.json +50 -0
  180. package/runtime/deps/assemblyscript/node_modules/long/LICENSE +202 -0
  181. package/runtime/deps/assemblyscript/node_modules/long/index.js +1581 -0
  182. package/runtime/deps/assemblyscript/node_modules/long/package.json +58 -0
  183. package/runtime/deps/assemblyscript/package.json +114 -0
  184. package/runtime/host/amxts.inc +85 -0
  185. package/runtime/licenses/AMXX-LICENSE.txt +34 -0
  186. package/runtime/licenses/AssemblyScript-LICENSE +201 -0
  187. package/runtime/licenses/AssemblyScript-NOTICE +93 -0
  188. package/runtime/licenses/Binaryen-LICENSE +201 -0
  189. package/runtime/licenses/Bun-LICENSE.md +84 -0
  190. package/runtime/licenses/GPL-3.0.txt +674 -0
  191. package/runtime/licenses/LLVM-LICENSE.TXT +279 -0
  192. package/runtime/licenses/README.md +25 -0
  193. package/runtime/licenses/WAMR-LICENSE +219 -0
  194. package/runtime/natives.txt +1095 -0
  195. package/runtime/patches/README.md +59 -0
  196. package/runtime/patches/assemblyscript-0.28.20-amxts.patch +14174 -0
  197. package/runtime/patches/wamr-2.4.5-amxts.patch +364 -0
  198. package/scripts/apply-docs.ts +508 -0
  199. package/scripts/asc.ts +27 -0
  200. package/scripts/auto-imports.ts +258 -0
  201. package/scripts/build-wasm.ts +595 -0
  202. package/scripts/check.ts +93 -0
  203. package/scripts/compile-cache.ts +179 -0
  204. package/scripts/compile.ts +401 -0
  205. package/scripts/docker-server.ts +73 -0
  206. package/scripts/docs/as/amxts.ts +210 -0
  207. package/scripts/docs/as/effects.ts +1684 -0
  208. package/scripts/docs/as/facade.ts +3302 -0
  209. package/scripts/docs/as/fs.ts +173 -0
  210. package/scripts/docs/as/lib/check.ts +68 -0
  211. package/scripts/docs/as/modules/http.ts +66 -0
  212. package/scripts/docs/as/os.ts +16 -0
  213. package/scripts/docs/as/promise.ts +132 -0
  214. package/scripts/docs/as/promise.types.ts +63 -0
  215. package/scripts/docs/as/vector.ts +48 -0
  216. package/scripts/docs/entities.ts +2338 -0
  217. package/scripts/docs/events.ts +245 -0
  218. package/scripts/docs/game.ts +497 -0
  219. package/scripts/docs/messages.ts +148 -0
  220. package/scripts/file-contents.ts +74 -0
  221. package/scripts/includes.ts +134 -0
  222. package/scripts/player-fields.ts +866 -0
  223. package/scripts/plugin-cache.ts +92 -0
  224. package/scripts/plugin-natives.ts +1157 -0
  225. package/scripts/prebuilt.ts +268 -0
  226. package/scripts/prepare.ts +134 -0
  227. package/scripts/project.ts +973 -0
  228. package/scripts/run.ts +28 -0
  229. package/scripts/shared-modules.ts +906 -0
  230. package/scripts/system.ts +33 -0
  231. package/scripts/tracked-fs.ts +115 -0
  232. package/scripts/typed-configs.ts +788 -0
  233. package/scripts/ui.ts +77 -0
  234. package/src/cli-api.mjs +175 -0
  235. package/src/parser/include-parser.ts +506 -0
  236. package/src/system.mjs +155 -0
  237. package/src/testing/compile-cache.ts +31 -0
  238. package/src/testing/compile.ts +222 -0
  239. package/src/testing/coroutines.ts +191 -0
  240. package/src/testing/index.ts +39 -0
  241. package/src/testing/kits.ts +55 -0
  242. package/src/testing/memory.ts +125 -0
  243. package/src/testing/natives.ts +925 -0
  244. package/src/testing/server.ts +1874 -0
  245. package/src/testing/setup.ts +63 -0
  246. package/src/testing/tables.ts +139 -0
  247. package/src/types.ts +51 -0
@@ -0,0 +1,222 @@
1
+ import type { NativesBeside, PluginNative } from '../../scripts/plugin-natives';
2
+ // A plugin compiled for a test: the same AssemblyScript, the same facade and
3
+ // the same flags as scripts/compile.ts, stopping at the .wasm - bun runs that
4
+ // directly, so wamrc and its .aot are not needed.
5
+ import { spawnSync } from 'node:child_process';
6
+ import { readFileSync, statSync, writeFileSync } from 'node:fs';
7
+ import { join, relative, resolve } from 'node:path';
8
+ import { ascMain } from '../../scripts/asc';
9
+ import { ASYNC_EXPORTS, asyncify, BASE_EXPORTS, compileToWasm, HoistImports, HOOD_EXPORTS, importsOf } from '../../scripts/compile';
10
+ import { playerFieldsBuild } from '../../scripts/player-fields';
11
+ import { nativesBeside, nativesTransform, setNativesBeside } from '../../scripts/plugin-natives';
12
+ import { ascPath, CONFIG_FILE, currentProjectDir, sourcesFor } from '../../scripts/project';
13
+ import { sharedModulesBuild } from '../../scripts/shared-modules';
14
+ import { existsSync, hashOf } from '../../scripts/tracked-fs';
15
+ import { cached } from './compile-cache';
16
+ import { PLUGINS_ROOT } from './tables';
17
+
18
+ declare const WebAssembly: any;
19
+
20
+ /** A compiled plugin, and the natives its `export function`s became. */
21
+ export interface Compiled {
22
+ module: any;
23
+ natives: PluginNative[];
24
+ /** The binary, for a plugin that awaits: the scheduler reads its function table. */
25
+ binary: Uint8Array;
26
+ /** Whether it carries the coroutine scheduler's exports (as/promise.ts). */
27
+ async: boolean;
28
+ }
29
+
30
+ const compiled = new Map<string, Promise<Compiled>>();
31
+
32
+ /** Forgets what this run compiled, so that the next compile() asks the disk cache again. */
33
+ export function forgetCompiled() {
34
+ compiled.clear();
35
+ }
36
+
37
+ /**
38
+ * The plugin at `source` as a WebAssembly.Module, compiled once per test run.
39
+ *
40
+ * One flag differs from the server build: `--exportStart`. On the server WAMR
41
+ * runs a plugin's top level while it instantiates, and its natives can read
42
+ * the plugin's memory while that happens. A JavaScript import cannot - the
43
+ * instance, and the memory with it, exists only once instantiation is over -
44
+ * so the top level is exported as `_start` and the fake calls it straight
45
+ * afterwards. The order of everything is the same.
46
+ */
47
+ export function compile(source: string): Promise<Compiled> {
48
+ const path = moduleOwner(source) ?? resolve(source);
49
+ // A module's owner is ~/<name>.ts in every project, with the options that
50
+ // project's amxts.config.ts merges into it: the project and its config are
51
+ // part of what was compiled.
52
+ const config = join(currentProjectDir(), CONFIG_FILE);
53
+ const key = [path, currentProjectDir(), existsSync(config) ? statSync(config).mtimeMs : 'none'].join('\0');
54
+ let module = compiled.get(key);
55
+
56
+ if (!module) {
57
+ module = build(path);
58
+ compiled.set(key, module);
59
+ }
60
+
61
+ return module;
62
+ }
63
+
64
+ /**
65
+ * A module package by name - "@amxts/menu-core" - is its owner plugin: its
66
+ * natives, or the one the build generates for a module without them.
67
+ */
68
+ function moduleOwner(source: string): string | null {
69
+ if (!/^@?[\w.-]+(?:\/[\w.-]+)?$/.test(source) || source.endsWith('.ts')) return null;
70
+ const sources = sourcesFor(PLUGINS_ROOT);
71
+ const pkg = sources.project.modules.find(each => each.name === source);
72
+ return pkg ? sources.ownerSource(pkg) : null;
73
+ }
74
+
75
+ /** A compile as the disk cache keeps it: no WebAssembly.Module, and an error as its text. */
76
+ type Built = (Omit<Compiled, 'module'> & { beside: NativesBeside }) | { error: string };
77
+
78
+ /** A failure worth keeping on disk is the compiler's; a file that could not be opened is not. */
79
+ const PASSING = /\b(?:EBUSY|EPERM|EACCES|EMFILE|ENFILE|EAGAIN)\b/;
80
+
81
+ function lasting(built: Built): boolean {
82
+ return !('error' in built) || !PASSING.test(built.error);
83
+ }
84
+
85
+ /**
86
+ * The plugin compiled - or taken from the disk cache (./compile-cache.ts),
87
+ * when an earlier run compiled it from the same files.
88
+ */
89
+ async function build(path: string): Promise<Compiled> {
90
+ const built = await cached<Built>(['plugin', process.cwd(), path, PLUGINS_ROOT], async () => {
91
+ try {
92
+ const fresh = await buildFresh(path);
93
+ return { ...fresh, beside: nativesBeside(fresh.natives) };
94
+ } catch (problem) {
95
+ return { error: String((problem as Error).message ?? problem) };
96
+ }
97
+ }, lasting);
98
+ if ('error' in built) throw new Error(built.error);
99
+ const { beside, ...compiled } = built;
100
+ setNativesBeside(compiled.natives, beside);
101
+ return { ...compiled, module: new WebAssembly.Module(compiled.binary) };
102
+ }
103
+
104
+ /**
105
+ * A plugin that makes a promise is compiled again with the scheduler's
106
+ * exports and run through Asyncify, as scripts/compile.ts builds it for a
107
+ * server - so an async function parks and resumes here as it does there.
108
+ */
109
+ async function buildFresh(path: string): Promise<Omit<Compiled, 'module'>> {
110
+ const first = await buildWith(path, BASE_EXPORTS);
111
+ if (!importsOf(first.binary).has('env.co_wake')) return first;
112
+
113
+ const second = await buildWith(path, ASYNC_EXPORTS);
114
+ return { natives: second.natives, binary: asyncify(second.binary), async: true };
115
+ }
116
+
117
+ async function buildWith(path: string, hoodExports: string): Promise<Omit<Compiled, 'module'>> {
118
+ const sources = sourcesFor(PLUGINS_ROOT);
119
+ if (!existsSync(path) && !sources.exists(path)) throw new Error(`no plugin at ${path}`);
120
+
121
+ // A module package's plugin compiles as ~/<name>.ts (scripts/project.ts).
122
+ if (sources.project.problems.length) throw new Error(sources.project.problems.join('\n'));
123
+ const entry = sources.entry(path);
124
+ let binary: Uint8Array | undefined;
125
+ const natives: PluginNative[] = [];
126
+ const playerFields = playerFieldsBuild();
127
+ // scripts/compile.ts's shared modules: a proxy for one another plugin owns.
128
+ const shared = await sharedModulesBuild(PLUGINS_ROOT, entry);
129
+
130
+ const { error, stderr } = await ascMain(
131
+ [entry, HOOD_EXPORTS, '--outFile', 'plugin.wasm', '--optimize', '--exportTable', '--exportStart', '_start'],
132
+ {
133
+ // scripts/compile.ts's readFile: the `~/` alias, the hood's exports and the fields plugins add to Player.
134
+ readFile(filename: string, baseDir: string): string | null {
135
+ if (filename.replace(/\\/g, '/').endsWith(HOOD_EXPORTS)) return hoodExports;
136
+
137
+ const file = ascPath(PLUGINS_ROOT, filename, baseDir);
138
+ const text = sources.read(file);
139
+ return text === null ? null : shared.read(file, playerFields.read(relative('.', (sources.real(file) ?? file)), text));
140
+ },
141
+ // Only the binary is wanted; nothing is written to disk.
142
+ writeFile(_: string, contents: string | Uint8Array) {
143
+ if (typeof contents !== 'string') binary = contents;
144
+ },
145
+ listFiles: () => [],
146
+ transforms: [HoistImports, nativesTransform(entry, natives, PLUGINS_ROOT), playerFields.transform, shared.transform],
147
+ },
148
+ );
149
+
150
+ if (sources.problems.length) throw new Error(sources.problems.join('\n'));
151
+
152
+ if (error || !binary) {
153
+ throw new Error(`${relative(process.cwd(), path)} does not compile:\n${stderr.toString() || String(error?.message ?? error)}`);
154
+ }
155
+
156
+ return { natives, binary, async: false };
157
+ }
158
+
159
+ /**
160
+ * scripts/compile.ts's compileToWasm - the server build's first half - with
161
+ * the disk cache in front: the problem it reports, or null and `wasm` written.
162
+ */
163
+ export async function compileWasmFile(plugin: { source: string; root: string }, wasm: string, names = false): Promise<string | null> {
164
+ const made = await cached<{ problem: string } | { binary: Uint8Array }>(
165
+ ['wasm', process.cwd(), resolve(plugin.source), resolve(plugin.root), resolve(wasm), names],
166
+ async () => {
167
+ const problem = await compileToWasm(plugin, wasm, names);
168
+ return problem === null ? { binary: new Uint8Array(readFileSync(wasm)) } : { problem };
169
+ },
170
+ made => !('problem' in made) || !PASSING.test(made.problem),
171
+ );
172
+ if ('problem' in made) return made.problem;
173
+ writeFileSync(wasm, made.binary);
174
+ return null;
175
+ }
176
+
177
+ /** What asc made of sources held in memory: the binary and the text files it wrote, or what it printed. */
178
+ export interface SourcesCompiled {
179
+ error: string | null;
180
+ binary: Uint8Array | null;
181
+ /** Text outputs by the name asc was given: `--textFile probe.wat`. */
182
+ text: Record<string, string>;
183
+ }
184
+
185
+ /**
186
+ * asc - the patched one, as the build runs it - on `sources` alone, a test's
187
+ * snippet of the compiler at work, with the disk cache in front. Nothing is
188
+ * read from disk, so the sources and `args` are the whole key.
189
+ */
190
+ export async function compileSources(args: string[], sources: Record<string, string>): Promise<SourcesCompiled> {
191
+ return cached<SourcesCompiled>(['sources', args, sources], async () => {
192
+ let binary: Uint8Array | null = null;
193
+ const text: Record<string, string> = {};
194
+ const { error, stderr } = await ascMain(args, {
195
+ readFile: (name: string) => sources[name] ?? null,
196
+ writeFile(name: string, contents: string | Uint8Array) {
197
+ if (typeof contents === 'string') text[name] = contents;
198
+ else binary = contents;
199
+ },
200
+ listFiles: () => [],
201
+ });
202
+ return error ? { error: stderr.toString(), binary: null, text } : { error: null, binary, text };
203
+ });
204
+ }
205
+
206
+ /**
207
+ * wamrc from `wasm` to `aot`, with the disk cache in front - keyed by the
208
+ * binary, wamrc itself and its arguments: its exit status and what it printed.
209
+ */
210
+ export async function compileAot(wamrc: string, args: string[], wasm: string, aot: string): Promise<{ status: number | null; output: string }> {
211
+ const made = await cached<{ status: number | null; output: string; aot?: Uint8Array }>(
212
+ ['aot', hashOf(readFileSync(wasm)), hashOf(readFileSync(wamrc)), args],
213
+ async () => {
214
+ const run = spawnSync(wamrc, [...args, '-o', aot, wasm], { encoding: 'utf-8' });
215
+ const output = `${run.stdout}${run.stderr}`;
216
+ return run.status === 0 ? { status: 0, output, aot: new Uint8Array(readFileSync(aot)) } : { status: run.status, output };
217
+ },
218
+ made => made.status === 0,
219
+ );
220
+ if (made.aot) writeFileSync(aot, made.aot);
221
+ return { status: made.status, output: made.output };
222
+ }
@@ -0,0 +1,191 @@
1
+ // The module's coroutine scheduler, for a plugin that awaits on the fake
2
+ // server: co_spawn, co_suspend, the shadow-stack park and the jobs run once
3
+ // the plugin's stack is empty, as runtime/src/module.cpp runs them (and
4
+ // tests/async-host.ts, which checks the protocol on its own).
5
+ //
6
+ // A plugin with no async function imports none of this and never gets here.
7
+ // @ts-ignore - its types are beside it, under a path tsconfig does not map
8
+ import binaryen from '../../runtime/deps/assemblyscript/node_modules/binaryen/index.js';
9
+
10
+ // Must match CO_* in runtime/src/module.cpp and as/promise.ts.
11
+ const CO_FINISHED = 0;
12
+ const CO_PARKED = 1;
13
+ const CO_TRAPPED = 2;
14
+ const CO_DROPPED = 3;
15
+
16
+ type Kind = 'i32' | 'i64' | 'f32' | 'f64';
17
+
18
+ interface Coroutine {
19
+ id: number;
20
+ fn: number;
21
+ cells: Uint32Array;
22
+ buffer: number;
23
+ base: number;
24
+ drop: boolean;
25
+ }
26
+
27
+ // co_id(): a task id no other plugin on the server has, as module.cpp's is -
28
+ // one counter for every plugin.
29
+ let nextId = 0x60000000;
30
+
31
+ /** The next of those ids. @internal */
32
+ export function nextTaskId(): number {
33
+ return nextId++;
34
+ }
35
+
36
+ export class Coroutines {
37
+ /** What a trap inside a coroutine said: it drops that one coroutine, as on the server. */
38
+ readonly traps: string[] = [];
39
+
40
+ private exports: any = null;
41
+ private kinds = new Map<number, Kind[]>();
42
+ private coroutines = new Map<number, Coroutine>();
43
+ private running: Coroutine[] = [];
44
+ private wake = false;
45
+ private entering = false;
46
+ private top = 0;
47
+ /** How deep the server is inside this plugin: jobs run when it is out. */
48
+ depth = 0;
49
+
50
+ constructor(wasm: Uint8Array) {
51
+ this.readTable(wasm);
52
+ }
53
+
54
+ /** The imports the scheduler answers. */
55
+ imports(): Record<string, (...args: any[]) => any> {
56
+ return {
57
+ co_entered: () => {
58
+ const entering = this.entering;
59
+ this.entering = false;
60
+ return entering ? 1 : 0;
61
+ },
62
+ co_spawn: (fn: number, args: number, cells: number, id: number, buffer: number) => {
63
+ const coroutine: Coroutine = {
64
+ id,
65
+ fn,
66
+ buffer,
67
+ drop: false,
68
+ cells: new Uint32Array(this.exports.memory.buffer.slice(args, args + cells * 4)),
69
+ base: this.exports.__co_stack(),
70
+ };
71
+ this.coroutines.set(id, coroutine);
72
+ return this.run(coroutine, false);
73
+ },
74
+ co_suspend: (drop: number) => {
75
+ if (this.exports.asyncify_get_state() === 2) {
76
+ this.exports.asyncify_stop_rewind();
77
+ return;
78
+ }
79
+ const coroutine = this.running[this.running.length - 1];
80
+ coroutine.drop = drop !== 0;
81
+ this.exports.asyncify_start_unwind(coroutine.buffer);
82
+ },
83
+ co_wake: () => { this.wake = true; },
84
+ co_id: nextTaskId,
85
+ };
86
+ }
87
+
88
+ /** Once the instance is there: where the shadow stack starts with nothing running. */
89
+ attach(exports: any): void {
90
+ this.exports = exports;
91
+ this.top = exports.__co_stack ? exports.__co_stack() : 0;
92
+ }
93
+
94
+ /** Whether a job is waiting to run. */
95
+ get waiting(): boolean {
96
+ return this.wake;
97
+ }
98
+
99
+ /** Runs the plugin's jobs once nothing of it is on the stack - module.cpp's DrainJobs. */
100
+ drain(): void {
101
+ if (!this.wake || !this.exports?.__co_next) return;
102
+ this.wake = false;
103
+ if (this.top) this.exports.__co_stack_set(this.top);
104
+ for (;;) {
105
+ const id = this.exports.__co_next();
106
+ if (id === 0) break;
107
+ const coroutine = this.coroutines.get(id);
108
+ const outcome = coroutine ? this.run(coroutine, true) : CO_FINISHED;
109
+ this.exports.__co_done(outcome);
110
+ }
111
+ }
112
+
113
+ /** One call into a coroutine, first or resumed - module.cpp's RunCoroutine. */
114
+ private run(coroutine: Coroutine, rewind: boolean): number {
115
+ const outer = this.exports.__co_stack();
116
+ const memory = this.exports.memory;
117
+ if (rewind) {
118
+ const low = coroutine.base - this.exports.__co_saved(coroutine.id);
119
+ new Uint8Array(memory.buffer, coroutine.base, outer - coroutine.base).fill(0);
120
+ this.exports.__co_stack_set(low);
121
+ this.exports.__co_restore(coroutine.id, low);
122
+ this.exports.asyncify_start_rewind(coroutine.buffer);
123
+ } else {
124
+ this.entering = true;
125
+ }
126
+
127
+ this.running.push(coroutine);
128
+ try {
129
+ this.callRaw(coroutine.fn, coroutine.cells);
130
+ } catch (error) {
131
+ this.running.pop();
132
+ this.traps.push((error as Error).message);
133
+ const state = this.exports.asyncify_get_state();
134
+ if (state === 1) this.exports.asyncify_stop_unwind();
135
+ if (state === 2) this.exports.asyncify_stop_rewind();
136
+ this.exports.__co_stack_set(outer);
137
+ this.coroutines.delete(coroutine.id);
138
+ return CO_TRAPPED;
139
+ }
140
+ this.running.pop();
141
+
142
+ if (this.exports.asyncify_get_state() !== 1) {
143
+ this.exports.__co_stack_set(outer);
144
+ this.coroutines.delete(coroutine.id);
145
+ return CO_FINISHED;
146
+ }
147
+ this.exports.asyncify_stop_unwind();
148
+ if (coroutine.drop) {
149
+ this.exports.__co_stack_set(outer);
150
+ this.coroutines.delete(coroutine.id);
151
+ return CO_DROPPED;
152
+ }
153
+ this.exports.__co_save(coroutine.id, this.exports.__co_stack(), coroutine.base);
154
+ this.exports.__co_stack_set(outer);
155
+ return CO_PARKED;
156
+ }
157
+
158
+ /** call_indirect with the cells co_spawn copied, each read as the parameter it is. */
159
+ private callRaw(fn: number, cells: Uint32Array) {
160
+ const bytes = new DataView(new Uint32Array(Array.from(cells)).buffer);
161
+ const args: (number | bigint)[] = [];
162
+ let at = 0;
163
+ for (const kind of this.kinds.get(fn) ?? []) {
164
+ if (kind === 'i32') args.push(bytes.getInt32(at, true));
165
+ if (kind === 'f32') args.push(bytes.getFloat32(at, true));
166
+ if (kind === 'i64') args.push(bytes.getBigInt64(at, true));
167
+ if (kind === 'f64') args.push(bytes.getFloat64(at, true));
168
+ at += kind === 'i64' || kind === 'f64' ? 8 : 4;
169
+ }
170
+ return this.exports.table.get(fn)(...args);
171
+ }
172
+
173
+ /** The parameter types of every function in the table, by index. */
174
+ private readTable(wasm: Uint8Array) {
175
+ const module = binaryen.readBinary(wasm);
176
+ try {
177
+ const kindOf = (type: number): Kind =>
178
+ type === binaryen.i64 ? 'i64' : type === binaryen.f32 ? 'f32' : type === binaryen.f64 ? 'f64' : 'i32';
179
+ for (let i = 0; i < module.getNumElementSegments(); i++) {
180
+ const segment = binaryen.getElementSegmentInfo(module.getElementSegmentByIndex(i));
181
+ const offset = (binaryen.getExpressionInfo(segment.offset as any) as any).value as number;
182
+ segment.data.forEach((name: string, at: number) => {
183
+ const info = binaryen.getFunctionInfo(module.getFunction(name));
184
+ this.kinds.set(offset + at, binaryen.expandType(info.params).map(kindOf));
185
+ });
186
+ }
187
+ } finally {
188
+ module.dispose();
189
+ }
190
+ }
191
+ }
@@ -0,0 +1,39 @@
1
+ // @amxts/core/test-utils: unit tests for amxts plugins and modules, without a
2
+ // game server.
3
+ //
4
+ // import { loadPlugin, setup } from "@amxts/core/test-utils";
5
+ //
6
+ // const server = await setup(); // the project: its modules, then its plugins
7
+ // const player = server.join("Alice", { team: "CT" });
8
+ // player.say("/tour");
9
+ // expect(player.chat).toContain("100 HP");
10
+ //
11
+ // The plugin is the real one - compiled by the same AssemblyScript, against
12
+ // the same facade and generated natives - and runs under WebAssembly with a
13
+ // fake server behind its imports. See docs/en/7.testing/01.index.md.
14
+ import type { ServerOptions } from './server';
15
+ import { FakeServer } from './server';
16
+
17
+ export { defineTestKit } from './kits';
18
+ export type { TestKit } from './kits';
19
+ export { bitsFloat, floatBits, Memory } from './memory';
20
+ export type { Native, NativeCall } from './natives';
21
+ export { FakeEntity, FakePlayer, FakeServer, FakeWeapon, PluginInstance } from './server';
22
+ export type { ArgValue, HookResult, JoinOptions, Message, NativeResult, SentForward, ServerOptions, TeamName, UserMessage, Value } from './server';
23
+ export { setup } from './setup';
24
+ export type { SetupOptions } from './setup';
25
+ export { constant } from './tables';
26
+
27
+ /**
28
+ * Compiles a plugin (once per test run), loads it into a new fake server and
29
+ * starts the map: its top level runs, then plugin_init and plugin_cfg.
30
+ *
31
+ * Several plugins go into one server as an array - they hear each other's
32
+ * forwards and natives, as on a real one.
33
+ */
34
+ export async function loadPlugin(source: string | string[], options: ServerOptions = {}): Promise<FakeServer> {
35
+ const server = new FakeServer(options);
36
+ for (const file of Array.isArray(source) ? source : [source]) await server.load(file);
37
+ server.start();
38
+ return server;
39
+ }
@@ -0,0 +1,55 @@
1
+ // A module's test kit: what the fake server needs to run the module - the
2
+ // natives its plugin calls that the fake does not answer, the messages it
3
+ // sends read back for a test. The module ships it and names it in its
4
+ // package.json, and the fake server installs it before the module's plugin
5
+ // loads:
6
+ //
7
+ // "amxts": { "module": "src/index.ts", "testing": "testing/index.ts" }
8
+ //
9
+ // // testing/index.ts
10
+ // import { defineTestKit } from "@amxts/core/test-utils";
11
+ // export default defineTestKit({ install: server => installMenus(server) });
12
+ import type { ModulePackage } from '../../scripts/project';
13
+ import type { FakeServer } from './server';
14
+ import { realpathSync } from 'node:fs';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { sourcesFor } from '../../scripts/project';
17
+ import { PLUGINS_ROOT } from './tables';
18
+
19
+ /** What a module's test kit does to a new fake server - before any plugin loads. */
20
+ export interface TestKit<T = unknown> {
21
+ /**
22
+ * Adds what the module needs on the fake server - `server.defineNative`,
23
+ * `server.messageListeners` - and gives back what its tests use.
24
+ */
25
+ install: (server: FakeServer) => T;
26
+ }
27
+
28
+ /**
29
+ * A module's test kit, as the default export of the file its package.json
30
+ * names in `"amxts": { "testing": ... }`.
31
+ */
32
+ export function defineTestKit<T>(kit: TestKit<T>): TestKit<T> {
33
+ return kit;
34
+ }
35
+
36
+ /** Installs a module package's test kit on the server, once; nothing for a package without one. */
37
+ export async function installKit(server: FakeServer, pkg: ModulePackage): Promise<void> {
38
+ if (!pkg.testing || server.kits.has(pkg.name)) return;
39
+ // By its real path: the test imports the same file through node_modules,
40
+ // and one file must be one module - the kit keeps its state per server.
41
+ const loaded = await import(pathToFileURL(realpathSync(pkg.testing)).href);
42
+ const kit = loaded.default as TestKit | undefined;
43
+ if (!kit || typeof kit.install !== 'function') {
44
+ throw new Error(`${pkg.name}: ${pkg.testing} has no \`export default defineTestKit({ install(server) { ... } })\``);
45
+ }
46
+ if (server.kits.has(pkg.name)) return;
47
+ server.kits.set(pkg.name, kit.install(server));
48
+ }
49
+
50
+ /** The kit of the module package `source` names, when it is one: `server.load("@amxts/menu-core")`. */
51
+ export async function installKitFor(server: FakeServer, source: string): Promise<void> {
52
+ if (source.endsWith('.ts') || !/^@?[\w.-]+(?:\/[\w.-]+)?$/.test(source)) return;
53
+ const pkg = sourcesFor(PLUGINS_ROOT).project.modules.find(each => each.name === source);
54
+ if (pkg) await installKit(server, pkg);
55
+ }
@@ -0,0 +1,125 @@
1
+ // A plugin's linear memory, read and written the ways the module does.
2
+ //
3
+ // Two kinds of text cross it. A Pawn string - what a native takes and fills -
4
+ // is one character per 32-bit cell, terminated by a zero cell. An
5
+ // AssemblyScript string - what the facade's own externals take, console.log's
6
+ // included - is UTF-16, with its length in bytes in the word before it.
7
+
8
+ const floatView = new Float32Array(1);
9
+ const intView = new Int32Array(floatView.buffer);
10
+ const encoder = new TextEncoder();
11
+ const decoder = new TextDecoder();
12
+
13
+ /** A float as the cell Pawn carries it in: its bit pattern. */
14
+ export function floatBits(value: number): number {
15
+ floatView[0] = value;
16
+ return intView[0];
17
+ }
18
+
19
+ /** The other way: a cell that holds a float. */
20
+ export function bitsFloat(cell: number): number {
21
+ intView[0] = cell;
22
+ return floatView[0];
23
+ }
24
+
25
+ /** How many of the bytes fit in `max` without cutting a UTF-8 character in two. */
26
+ export function utf8Fit(bytes: Uint8Array, max: number): number {
27
+ let length = Math.min(bytes.length, max);
28
+ while (length > 0 && length < bytes.length && (bytes[length] & 0xC0) === 0x80) length--;
29
+ return length;
30
+ }
31
+
32
+ export class Memory {
33
+ private dataView?: DataView;
34
+
35
+ constructor(private exports: any) {}
36
+
37
+ // memory.grow detaches the old buffer, so the view follows the current one.
38
+ private get view(): DataView {
39
+ const buffer = this.exports.memory.buffer;
40
+ if (!this.dataView || this.dataView.buffer !== buffer) this.dataView = new DataView(buffer);
41
+ return this.dataView;
42
+ }
43
+
44
+ cell(pointer: number): number {
45
+ return this.view.getInt32(pointer, true);
46
+ }
47
+
48
+ setCell(pointer: number, value: number): void {
49
+ this.view.setInt32(pointer, value | 0, true);
50
+ }
51
+
52
+ float(pointer: number): number {
53
+ return bitsFloat(this.cell(pointer));
54
+ }
55
+
56
+ setFloat(pointer: number, value: number): void {
57
+ this.setCell(pointer, floatBits(value));
58
+ }
59
+
60
+ vector(pointer: number): number[] {
61
+ return [this.float(pointer), this.float(pointer + 4), this.float(pointer + 8)];
62
+ }
63
+
64
+ setVector(pointer: number, value: number[]): void {
65
+ for (let i = 0; i < 3; i++) this.setFloat(pointer + i * 4, value[i] ?? 0);
66
+ }
67
+
68
+ /** A Pawn string: a byte of UTF-8 a cell, up to the zero cell - what get_amxstring reads. */
69
+ text(pointer: number, max = 4096): string {
70
+ return decoder.decode(this.bytes(pointer, max));
71
+ }
72
+
73
+ /** A Pawn string's bytes as they are, at most `max` of them. */
74
+ bytes(pointer: number, max = 4096): Uint8Array {
75
+ if (!pointer) return new Uint8Array();
76
+ const bytes: number[] = [];
77
+ for (let i = 0; i < max; i++) {
78
+ const c = this.cell(pointer + i * 4);
79
+ if (c === 0) break;
80
+ bytes.push(c & 0xFF);
81
+ }
82
+ return Uint8Array.from(bytes);
83
+ }
84
+
85
+ /** Fills a Pawn buffer: at most `max` bytes of UTF-8 and the terminator, as set_amxstring. */
86
+ setText(pointer: number, max: number, value: string): number {
87
+ if (!pointer || max < 0) return 0;
88
+ const bytes = encoder.encode(value);
89
+ const length = Math.min(bytes.length, max);
90
+ for (let i = 0; i < length; i++) this.setCell(pointer + i * 4, bytes[i]);
91
+ this.setCell(pointer + length * 4, 0);
92
+ return length;
93
+ }
94
+
95
+ /** An AssemblyScript string. */
96
+ string(pointer: number): string {
97
+ if (!pointer) return '';
98
+ const bytes = this.view.getUint32(pointer - 4, true);
99
+ const chars = new Uint16Array(this.exports.memory.buffer, pointer, bytes >>> 1);
100
+ return String.fromCharCode(...chars);
101
+ }
102
+
103
+ /** Bytes as they are, into the plugin's memory. */
104
+ setRaw(pointer: number, bytes: Uint8Array): void {
105
+ new Uint8Array(this.exports.memory.buffer, pointer, bytes.length).set(bytes);
106
+ }
107
+
108
+ /** A text's UTF-8 length, and as much of it as fits into `max` bytes at `pointer`. */
109
+ setUtf8(pointer: number, max: number, value: string): number {
110
+ const bytes = encoder.encode(value);
111
+ if (pointer && max > 0) this.setRaw(pointer, bytes.subarray(0, max));
112
+ return bytes.length;
113
+ }
114
+
115
+ /** UTF-8 into a byte buffer the plugin decodes with String.UTF8 - the module's WriteBytes. */
116
+ setBytes(pointer: number, max: number, value: string): number {
117
+ if (!pointer || max <= 0) return 0;
118
+ const encoded = encoder.encode(value);
119
+ const length = Math.min(encoded.length, max - 1);
120
+ const target = new Uint8Array(this.exports.memory.buffer, pointer, length + 1);
121
+ target.set(encoded.subarray(0, length));
122
+ target[length] = 0;
123
+ return length;
124
+ }
125
+ }