@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
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
// The messages Counter-Strike's server sends its clients, as
|
|
2
|
+
// server.addMessageListener(name, ...) hears them - read by
|
|
3
|
+
// scripts/generate-host.ts, which writes their events into as/events.ts.
|
|
4
|
+
//
|
|
5
|
+
// Every name the game registers is a message a plugin can listen to, by the
|
|
6
|
+
// name MESSAGE_NAMES gives it in the player's words: `death`, not `DeathMsg`.
|
|
7
|
+
// The game's own name stays in the tooltip and in `event.name`, where someone
|
|
8
|
+
// porting a Pawn plugin looks for it. MESSAGE_FIELDS is the one table
|
|
9
|
+
// of the layouts the game writes them in: a message there is a class of its
|
|
10
|
+
// own with a typed field per argument it names (1 is the first write_*),
|
|
11
|
+
// read and written as the kind says; one without a known layout - a bot's
|
|
12
|
+
// progress, a tutor's text - is read by place, through `event.args`. Every
|
|
13
|
+
// message has `args` too, for what the table leaves out. `player` is every
|
|
14
|
+
// message's receiver, so a player an argument names is `target` (or its
|
|
15
|
+
// role: `killer`, `sender`). The words are scripts/docs/messages.ts's.
|
|
16
|
+
//
|
|
17
|
+
// A name hears one message, or a few that are the same thing to an author:
|
|
18
|
+
// `progressBar` is BarTime and BarTime2, which only adds where the bar
|
|
19
|
+
// starts. Its event has the fields of them all, each at the same argument in
|
|
20
|
+
// every one that writes it; a field one of them lacks reads as empty there,
|
|
21
|
+
// as an argument past the last does.
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* What an argument is read as: a number, a boolean, text; `texts` every text
|
|
25
|
+
* from the argument on (the words a game text puts in); `player` a player's
|
|
26
|
+
* number, `0` none (`Player | null`); `weapon` a weapon's id as WeaponKind
|
|
27
|
+
* names it; `team` a TeamName number and `teamName` a team written as text,
|
|
28
|
+
* both a Team; `vector` three coordinates from the argument on; `color`
|
|
29
|
+
* `size` bytes from it, red, green, blue (and alpha); `fixed` a number of
|
|
30
|
+
* `scale` parts to one (a time in 1/4096 s); `bit` whether `mask`'s bit is
|
|
31
|
+
* set; `fadeDirection` ScreenFade's FFADE_OUT bit as FadeDirection;
|
|
32
|
+
* `hideHud`, `damage`, `scoreStatus` a mask as an array of HideHud, Damage,
|
|
33
|
+
* ScoreStatus names; `statusIcon` StatusIcon's state (0 hide, 1 show,
|
|
34
|
+
* 2 flash); `destination` TextMsg's (1 notify, 2 console, 3 chat, 4 center);
|
|
35
|
+
* `vguiMenu` a VGUIMenu number as VguiMenu names it.
|
|
36
|
+
*/
|
|
37
|
+
export type MessageFieldKind = 'number' | 'boolean' | 'string' | 'texts' | 'player' | 'weapon' | 'team' | 'teamName' | 'vector' | 'color' | 'fixed' | 'bit' | 'fadeDirection' | 'hideHud' | 'damage' | 'scoreStatus' | 'statusIcon' | 'destination' | 'vguiMenu';
|
|
38
|
+
|
|
39
|
+
export interface MessageField {
|
|
40
|
+
name: string;
|
|
41
|
+
/** The argument's number, 1 for the first. */
|
|
42
|
+
arg: number;
|
|
43
|
+
kind: MessageFieldKind;
|
|
44
|
+
/** `fixed`: the parts that make one. `bit`: the bit. `color`: the bytes. */
|
|
45
|
+
of?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const f = (name: string, arg: number, kind: MessageFieldKind, of?: number): MessageField => ({ name, arg, kind, ...(of === undefined ? {} : { of }) });
|
|
49
|
+
|
|
50
|
+
/** The messages whose layout is known, each argument a field. */
|
|
51
|
+
export const MESSAGE_FIELDS: Record<string, MessageField[]> = {
|
|
52
|
+
ADStop: [],
|
|
53
|
+
AllowSpec: [f('allowed', 1, 'boolean')],
|
|
54
|
+
AmmoPickup: [f('ammo', 1, 'number'), f('amount', 2, 'number')],
|
|
55
|
+
AmmoX: [f('ammo', 1, 'number'), f('amount', 2, 'number')],
|
|
56
|
+
ArmorType: [f('helmet', 1, 'boolean')],
|
|
57
|
+
BarTime: [f('seconds', 1, 'number')],
|
|
58
|
+
BarTime2: [f('seconds', 1, 'number'), f('startPercent', 2, 'number')],
|
|
59
|
+
Battery: [f('armor', 1, 'number')],
|
|
60
|
+
BlinkAcct: [f('blinks', 1, 'number')],
|
|
61
|
+
BombDrop: [f('origin', 1, 'vector'), f('planted', 4, 'boolean')],
|
|
62
|
+
BombPickup: [],
|
|
63
|
+
BotVoice: [f('talking', 1, 'boolean'), f('target', 2, 'player')],
|
|
64
|
+
BuyClose: [],
|
|
65
|
+
ClCorpse: [f('model', 1, 'string'), f('team', 11, 'team'), f('target', 12, 'player')],
|
|
66
|
+
Crosshair: [f('shown', 1, 'boolean')],
|
|
67
|
+
CurWeapon: [f('active', 1, 'boolean'), f('weapon', 2, 'weapon'), f('clip', 3, 'number')],
|
|
68
|
+
Damage: [f('armor', 1, 'number'), f('damage', 2, 'number'), f('damageType', 3, 'damage'), f('origin', 4, 'vector')],
|
|
69
|
+
DeathMsg: [f('killer', 1, 'player'), f('victim', 2, 'player'), f('headshot', 3, 'boolean'), f('weapon', 4, 'string')],
|
|
70
|
+
Flashlight: [f('on', 1, 'boolean'), f('battery', 2, 'number')],
|
|
71
|
+
FlashBat: [f('battery', 1, 'number')],
|
|
72
|
+
Geiger: [f('range', 1, 'number')],
|
|
73
|
+
Health: [f('health', 1, 'number')],
|
|
74
|
+
HideWeapon: [f('flags', 1, 'hideHud')],
|
|
75
|
+
HostageK: [f('hostage', 1, 'number')],
|
|
76
|
+
HostagePos: [f('hostage', 2, 'number'), f('origin', 3, 'vector')],
|
|
77
|
+
HudText: [f('text', 1, 'string')],
|
|
78
|
+
HudTextArgs: [f('text', 1, 'string'), f('params', 4, 'texts')],
|
|
79
|
+
HudTextPro: [f('text', 1, 'string')],
|
|
80
|
+
InitHUD: [],
|
|
81
|
+
ItemPickup: [f('item', 1, 'string')],
|
|
82
|
+
ItemStatus: [f('nightVision', 1, 'bit', 1), f('defuseKit', 1, 'bit', 2)],
|
|
83
|
+
Location: [f('target', 1, 'player'), f('place', 2, 'string')],
|
|
84
|
+
Money: [f('amount', 1, 'number'), f('flash', 2, 'boolean')],
|
|
85
|
+
MOTD: [f('last', 1, 'boolean'), f('text', 2, 'string')],
|
|
86
|
+
NVGToggle: [f('on', 1, 'boolean')],
|
|
87
|
+
Radar: [f('target', 1, 'player'), f('origin', 2, 'vector')],
|
|
88
|
+
ReqState: [],
|
|
89
|
+
ResetHUD: [],
|
|
90
|
+
RoundTime: [f('seconds', 1, 'number')],
|
|
91
|
+
SayText: [f('sender', 1, 'player'), f('text', 2, 'string'), f('params', 3, 'texts')],
|
|
92
|
+
Scenario: [f('active', 1, 'boolean'), f('sprite', 2, 'string'), f('alpha', 3, 'number')],
|
|
93
|
+
ScoreAttrib: [f('target', 1, 'player'), f('flags', 2, 'scoreStatus')],
|
|
94
|
+
ScoreInfo: [f('target', 1, 'player'), f('frags', 2, 'number'), f('deaths', 3, 'number'), f('team', 5, 'team')],
|
|
95
|
+
ScreenFade: [
|
|
96
|
+
f('duration', 1, 'fixed', 4096),
|
|
97
|
+
f('hold', 2, 'fixed', 4096),
|
|
98
|
+
f('direction', 3, 'fadeDirection'),
|
|
99
|
+
f('modulate', 3, 'bit', 2),
|
|
100
|
+
f('stay', 3, 'bit', 4),
|
|
101
|
+
f('color', 4, 'color', 4),
|
|
102
|
+
],
|
|
103
|
+
ScreenShake: [f('amplitude', 1, 'fixed', 4096), f('duration', 2, 'fixed', 4096), f('frequency', 3, 'fixed', 256)],
|
|
104
|
+
SendAudio: [f('sender', 1, 'player'), f('sound', 2, 'string'), f('pitch', 3, 'number')],
|
|
105
|
+
ServerName: [f('serverName', 1, 'string')],
|
|
106
|
+
SetFOV: [f('fov', 1, 'number')],
|
|
107
|
+
ShowMenu: [f('more', 3, 'boolean'), f('text', 4, 'string')],
|
|
108
|
+
ShowTimer: [],
|
|
109
|
+
SpecHealth: [f('health', 1, 'number')],
|
|
110
|
+
SpecHealth2: [f('health', 1, 'number'), f('target', 2, 'player')],
|
|
111
|
+
Spectator: [f('target', 1, 'player'), f('spectator', 2, 'boolean')],
|
|
112
|
+
StatusIcon: [f('state', 1, 'statusIcon'), f('sprite', 2, 'string'), f('color', 3, 'color', 3)],
|
|
113
|
+
StatusText: [f('line', 1, 'number'), f('text', 2, 'string')],
|
|
114
|
+
StatusValue: [f('slot', 1, 'number'), f('value', 2, 'number')],
|
|
115
|
+
TaskTime: [f('seconds', 1, 'number'), f('active', 2, 'boolean'), f('fade', 3, 'number')],
|
|
116
|
+
TeamInfo: [f('target', 1, 'player'), f('team', 2, 'teamName')],
|
|
117
|
+
TeamScore: [f('team', 1, 'teamName'), f('score', 2, 'number')],
|
|
118
|
+
TextMsg: [f('destination', 1, 'destination'), f('text', 2, 'string'), f('params', 3, 'texts')],
|
|
119
|
+
Train: [f('speed', 1, 'number')],
|
|
120
|
+
TutorClose: [],
|
|
121
|
+
VGUIMenu: [f('menu', 1, 'vguiMenu')],
|
|
122
|
+
ViewMode: [],
|
|
123
|
+
WeaponList: [
|
|
124
|
+
f('classname', 1, 'string'),
|
|
125
|
+
f('ammo', 2, 'number'),
|
|
126
|
+
f('maxAmmo', 3, 'number'),
|
|
127
|
+
f('ammo2', 4, 'number'),
|
|
128
|
+
f('maxAmmo2', 5, 'number'),
|
|
129
|
+
f('slot', 6, 'number'),
|
|
130
|
+
f('position', 7, 'number'),
|
|
131
|
+
f('weapon', 8, 'weapon'),
|
|
132
|
+
],
|
|
133
|
+
WeapPickup: [f('weapon', 1, 'weapon')],
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/** Every message Counter-Strike 1.6 registers, by the name get_user_msgid takes. */
|
|
137
|
+
export const CLIENT_MESSAGES = `
|
|
138
|
+
ADStop AllowSpec AmmoPickup AmmoX ArmorType BarTime BarTime2 Battery BlinkAcct BombDrop BombPickup
|
|
139
|
+
BotProgress BotVoice Brass BuyClose ClCorpse Crosshair CurWeapon CZCareer CZCareerHUD Damage
|
|
140
|
+
DeathMsg Flashlight FlashBat Fog ForceCam GameMode GameTitle Geiger Health HideWeapon HLTV HostageK
|
|
141
|
+
HostagePos HudText HudTextArgs HudTextPro InitHUD ItemPickup ItemStatus Location Money MOTD
|
|
142
|
+
NVGToggle Radar ReceiveW ReloadSound ReqState ResetHUD RoundTime SayText Scenario ScoreAttrib
|
|
143
|
+
ScoreInfo ScreenFade ScreenShake SendAudio ServerName SetFOV ShadowIdx ShowMenu ShowTimer
|
|
144
|
+
SpecHealth SpecHealth2 Spectator StatusIcon StatusText StatusValue TaskTime TeamInfo TeamScore
|
|
145
|
+
TextMsg Train TutorClose TutorLine TutorState TutorText VGUIMenu ViewMode VoiceMask WeaponList
|
|
146
|
+
WeapPickup
|
|
147
|
+
`.trim().split(/\s+/);
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Every message's name in the player's words, by the game's: lowerCamelCase,
|
|
151
|
+
* without the protocol's `Msg`, an abbreviation spelled out (`CurWeapon` is
|
|
152
|
+
* `currentWeapon`, `SetFOV` `fov`) and an opaque name given its meaning
|
|
153
|
+
* (`SayText` is `chat`, `Battery` `armor`, `ShowMenu` `menu`). Messages that
|
|
154
|
+
* are one thing to an author share a name (`BarTime`, `BarTime2`).
|
|
155
|
+
*/
|
|
156
|
+
export const MESSAGE_NAMES: Record<string, string> = {
|
|
157
|
+
ADStop: 'adStop',
|
|
158
|
+
AllowSpec: 'allowSpectate',
|
|
159
|
+
AmmoPickup: 'ammoPickup',
|
|
160
|
+
AmmoX: 'ammo',
|
|
161
|
+
ArmorType: 'armorType',
|
|
162
|
+
BarTime: 'progressBar',
|
|
163
|
+
BarTime2: 'progressBar',
|
|
164
|
+
Battery: 'armor',
|
|
165
|
+
BlinkAcct: 'moneyBlink',
|
|
166
|
+
BombDrop: 'bombDrop',
|
|
167
|
+
BombPickup: 'bombPickup',
|
|
168
|
+
BotProgress: 'botProgress',
|
|
169
|
+
BotVoice: 'botVoice',
|
|
170
|
+
Brass: 'shellCasing',
|
|
171
|
+
BuyClose: 'closeBuyMenu',
|
|
172
|
+
ClCorpse: 'corpse',
|
|
173
|
+
Crosshair: 'crosshair',
|
|
174
|
+
CurWeapon: 'currentWeapon',
|
|
175
|
+
CZCareer: 'czCareer',
|
|
176
|
+
CZCareerHUD: 'czCareerHud',
|
|
177
|
+
Damage: 'damage',
|
|
178
|
+
DeathMsg: 'death',
|
|
179
|
+
Flashlight: 'flashlight',
|
|
180
|
+
FlashBat: 'flashlightBattery',
|
|
181
|
+
Fog: 'fog',
|
|
182
|
+
ForceCam: 'forceCamera',
|
|
183
|
+
GameMode: 'gameMode',
|
|
184
|
+
GameTitle: 'gameTitle',
|
|
185
|
+
Geiger: 'geiger',
|
|
186
|
+
Health: 'health',
|
|
187
|
+
HideWeapon: 'hideWeapon',
|
|
188
|
+
HLTV: 'hltv',
|
|
189
|
+
HostageK: 'hostageKilled',
|
|
190
|
+
HostagePos: 'hostagePosition',
|
|
191
|
+
HudText: 'hint',
|
|
192
|
+
HudTextArgs: 'hint',
|
|
193
|
+
HudTextPro: 'hint',
|
|
194
|
+
InitHUD: 'initHud',
|
|
195
|
+
ItemPickup: 'itemPickup',
|
|
196
|
+
ItemStatus: 'itemStatus',
|
|
197
|
+
Location: 'location',
|
|
198
|
+
Money: 'money',
|
|
199
|
+
MOTD: 'motd',
|
|
200
|
+
NVGToggle: 'nightVision',
|
|
201
|
+
Radar: 'radar',
|
|
202
|
+
ReceiveW: 'weather',
|
|
203
|
+
ReloadSound: 'reloadSound',
|
|
204
|
+
ReqState: 'requestState',
|
|
205
|
+
ResetHUD: 'resetHud',
|
|
206
|
+
RoundTime: 'roundTime',
|
|
207
|
+
SayText: 'chat',
|
|
208
|
+
Scenario: 'scenarioIcon',
|
|
209
|
+
ScoreAttrib: 'scoreAttribute',
|
|
210
|
+
ScoreInfo: 'score',
|
|
211
|
+
ScreenFade: 'screenFade',
|
|
212
|
+
ScreenShake: 'screenShake',
|
|
213
|
+
SendAudio: 'sound',
|
|
214
|
+
ServerName: 'serverName',
|
|
215
|
+
SetFOV: 'fov',
|
|
216
|
+
ShadowIdx: 'shadow',
|
|
217
|
+
ShowMenu: 'menu',
|
|
218
|
+
ShowTimer: 'showTimer',
|
|
219
|
+
SpecHealth: 'spectatedHealth',
|
|
220
|
+
SpecHealth2: 'spectatedHealth',
|
|
221
|
+
Spectator: 'spectator',
|
|
222
|
+
StatusIcon: 'statusIcon',
|
|
223
|
+
StatusText: 'statusText',
|
|
224
|
+
StatusValue: 'statusValue',
|
|
225
|
+
TaskTime: 'taskTime',
|
|
226
|
+
TeamInfo: 'team',
|
|
227
|
+
TeamScore: 'teamScore',
|
|
228
|
+
TextMsg: 'text',
|
|
229
|
+
Train: 'train',
|
|
230
|
+
TutorClose: 'tutorClose',
|
|
231
|
+
TutorLine: 'tutorLine',
|
|
232
|
+
TutorState: 'tutorState',
|
|
233
|
+
TutorText: 'tutorText',
|
|
234
|
+
VGUIMenu: 'vguiMenu',
|
|
235
|
+
ViewMode: 'viewMode',
|
|
236
|
+
VoiceMask: 'voiceMask',
|
|
237
|
+
WeaponList: 'weaponList',
|
|
238
|
+
WeapPickup: 'weaponPickup',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/** The names server.addMessageListener takes, each with the game's messages it hears, in the game's order. */
|
|
242
|
+
export const MESSAGE_GROUPS = new Map<string, string[]>();
|
|
243
|
+
for (const message of CLIENT_MESSAGES) {
|
|
244
|
+
const name = MESSAGE_NAMES[message];
|
|
245
|
+
if (name) MESSAGE_GROUPS.set(name, [...(MESSAGE_GROUPS.get(name) ?? []), message]);
|
|
246
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Several plugins compiled at once. asc is not reentrant and a compile holds
|
|
2
|
+
// about a gigabyte at its peak, so each runs in a process of its own
|
|
3
|
+
// (scripts/compile-worker.ts), as many at once as the CPUs and the memory
|
|
4
|
+
// budget allow: AMXTS_BUILD_MEMORY megabytes for all of them (3072), or
|
|
5
|
+
// AMXTS_BUILD_JOBS compiles. A single plugin, or room for one, is compiled in
|
|
6
|
+
// this process. Either way a plugin compiles to the same bytes.
|
|
7
|
+
import type { Plugin } from './compile';
|
|
8
|
+
import type { NativesBeside, PluginNative } from './plugin-natives';
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { availableParallelism } from 'node:os';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { setNativesBeside } from './plugin-natives';
|
|
14
|
+
|
|
15
|
+
const MB = 1024 * 1024;
|
|
16
|
+
|
|
17
|
+
/** A compile's peak, asc's and wamrc's together: measured at about a gigabyte, with room. */
|
|
18
|
+
export const COMPILE_MEMORY = 1280 * MB;
|
|
19
|
+
|
|
20
|
+
/** What the compiles of a build may hold together, unless AMXTS_BUILD_MEMORY says otherwise. */
|
|
21
|
+
export const BUILD_MEMORY = 3072 * MB;
|
|
22
|
+
|
|
23
|
+
const WORKER = join(dirname(fileURLToPath(import.meta.url)), 'compile-worker.ts');
|
|
24
|
+
|
|
25
|
+
/** How many compiles run at once: AMXTS_BUILD_JOBS, else as many as the memory budget holds, at most one per CPU. */
|
|
26
|
+
export function compileSlots(env: Record<string, string | undefined> = process.env, cpus = availableParallelism()): number {
|
|
27
|
+
const jobs = Number(env.AMXTS_BUILD_JOBS);
|
|
28
|
+
if (Number.isInteger(jobs) && jobs > 0) return jobs;
|
|
29
|
+
const budget = Number(env.AMXTS_BUILD_MEMORY) > 0 ? Number(env.AMXTS_BUILD_MEMORY) * MB : BUILD_MEMORY;
|
|
30
|
+
return Math.max(1, Math.min(cpus, Math.floor(budget / COMPILE_MEMORY)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* `run` on every item, at most `slots` at a time, each given the slot it runs
|
|
35
|
+
* in (0, 1, ...) and its index: the items start in order, and the results come
|
|
36
|
+
* back in it.
|
|
37
|
+
*/
|
|
38
|
+
export async function inSlots<T, R>(items: T[], slots: number, run: (item: T, slot: number, index: number) => Promise<R>): Promise<R[]> {
|
|
39
|
+
const results: R[] = Array.from({ length: items.length });
|
|
40
|
+
let next = 0;
|
|
41
|
+
const slot = async (number: number) => {
|
|
42
|
+
while (next < items.length) {
|
|
43
|
+
const index = next++;
|
|
44
|
+
results[index] = await run(items[index], number, index);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
await Promise.all(Array.from({ length: Math.min(slots, items.length) }, (_, number) => slot(number)));
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One plugin compiled: what went wrong, or null, and its natives. */
|
|
52
|
+
export interface Compiled {
|
|
53
|
+
problem: string | null;
|
|
54
|
+
natives: PluginNative[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What a worker is sent: the plugin, and the plugin cache it compiles through (scripts/plugin-cache.ts). */
|
|
58
|
+
export interface WorkerJob {
|
|
59
|
+
plugin: Plugin;
|
|
60
|
+
/** The project, whose cache it is; null for none. */
|
|
61
|
+
dir: string | null;
|
|
62
|
+
/** The includes the build writes itself. */
|
|
63
|
+
includes: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type WorkerResult = Compiled & { beside: NativesBeside };
|
|
67
|
+
|
|
68
|
+
/** A worker process: one job at a time, until it is closed. */
|
|
69
|
+
function worker() {
|
|
70
|
+
const child = spawn(process.execPath, [WORKER], { stdio: ['ignore', 'inherit', 'inherit', 'ipc'] });
|
|
71
|
+
let waiting: { resolve: (result: WorkerResult) => void; reject: (error: Error) => void } | null = null;
|
|
72
|
+
const answer = () => {
|
|
73
|
+
const now = waiting;
|
|
74
|
+
waiting = null;
|
|
75
|
+
return now;
|
|
76
|
+
};
|
|
77
|
+
child.on('message', (result: WorkerResult) => answer()?.resolve(result));
|
|
78
|
+
child.on('exit', code => answer()?.reject(new Error(`a compile process stopped (exit ${code})`)));
|
|
79
|
+
return {
|
|
80
|
+
compile: (job: WorkerJob) => new Promise<WorkerResult>((resolve, reject) => {
|
|
81
|
+
waiting = { resolve, reject };
|
|
82
|
+
child.send(job);
|
|
83
|
+
}),
|
|
84
|
+
close: () => child.connected && child.disconnect(),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface CompileAll {
|
|
89
|
+
/** The project and the includes its builds write: the plugin cache's (null: none). */
|
|
90
|
+
dir: string | null;
|
|
91
|
+
includes: string[];
|
|
92
|
+
/** The compile in this process: the build's plugin cache. */
|
|
93
|
+
here: (plugin: Plugin, natives: PluginNative[]) => Promise<string | null>;
|
|
94
|
+
started?: (index: number) => void;
|
|
95
|
+
finished?: (index: number, compiled: Compiled) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The plugins compiled, several at once when there is room - in this process
|
|
100
|
+
* when there is room for one. After a failure no other compile starts: those
|
|
101
|
+
* that did not are null.
|
|
102
|
+
*/
|
|
103
|
+
export async function compileAll(plugins: Plugin[], options: CompileAll): Promise<(Compiled | null)[]> {
|
|
104
|
+
const slots = Math.min(plugins.length, compileSlots());
|
|
105
|
+
const workers = slots > 1 ? Array.from({ length: slots }, worker) : [];
|
|
106
|
+
let failed = false;
|
|
107
|
+
const compile = async (plugin: Plugin, slot: number): Promise<Compiled> => {
|
|
108
|
+
if (workers.length === 0) {
|
|
109
|
+
const natives: PluginNative[] = [];
|
|
110
|
+
return { problem: await options.here(plugin, natives), natives };
|
|
111
|
+
}
|
|
112
|
+
const { beside, ...compiled } = await workers[slot].compile({ plugin, dir: options.dir, includes: options.includes });
|
|
113
|
+
setNativesBeside(compiled.natives, beside);
|
|
114
|
+
return compiled;
|
|
115
|
+
};
|
|
116
|
+
try {
|
|
117
|
+
return await inSlots(plugins, slots, async (plugin, slot, index) => {
|
|
118
|
+
if (failed) return null;
|
|
119
|
+
options.started?.(index);
|
|
120
|
+
const compiled = await compile(plugin, slot);
|
|
121
|
+
failed ||= compiled.problem !== null;
|
|
122
|
+
options.finished?.(index, compiled);
|
|
123
|
+
return compiled;
|
|
124
|
+
});
|
|
125
|
+
} finally {
|
|
126
|
+
for (const each of workers) each.close();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// A compile process of scripts/compile-pool.ts: compiles the plugins it is
|
|
2
|
+
// sent, one at a time, through the build's plugin cache (scripts/plugin-cache.ts),
|
|
3
|
+
// and answers each with what went wrong and the plugin's natives. It ends when
|
|
4
|
+
// the build disconnects.
|
|
5
|
+
import type { WorkerJob, WorkerResult } from './compile-pool';
|
|
6
|
+
import type { PluginNative } from './plugin-natives';
|
|
7
|
+
import { pluginCache } from './plugin-cache';
|
|
8
|
+
import { nativesBeside } from './plugin-natives';
|
|
9
|
+
|
|
10
|
+
const caches = new Map<string, ReturnType<typeof pluginCache>>();
|
|
11
|
+
|
|
12
|
+
process.on('message', async ({ plugin, dir, includes }: WorkerJob) => {
|
|
13
|
+
const key = JSON.stringify([dir, includes]);
|
|
14
|
+
if (!caches.has(key)) caches.set(key, pluginCache(dir, includes));
|
|
15
|
+
const natives: PluginNative[] = [];
|
|
16
|
+
let problem: string | null;
|
|
17
|
+
try {
|
|
18
|
+
problem = await caches.get(key)!.compile(plugin, natives);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
problem = String((error as Error).stack ?? error);
|
|
21
|
+
}
|
|
22
|
+
process.send!({ problem, natives, beside: nativesBeside(natives) } satisfies WorkerResult);
|
|
23
|
+
});
|