redscript-mc 1.2.12 → 1.2.13
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.
|
@@ -201,8 +201,16 @@ function applyFunctionOptimization(files) {
|
|
|
201
201
|
commands: entry.commands,
|
|
202
202
|
})));
|
|
203
203
|
const commandMap = new Map(optimized.functions.map(fn => [fn.name, fn.commands]));
|
|
204
|
+
// Filter out files for functions that were removed (inlined trivial functions)
|
|
205
|
+
const optimizedNames = new Set(optimized.functions.map(fn => fn.name));
|
|
204
206
|
return {
|
|
205
|
-
files: files
|
|
207
|
+
files: files
|
|
208
|
+
.filter(file => {
|
|
209
|
+
const functionName = toFunctionName(file);
|
|
210
|
+
// Keep non-function files and functions that weren't removed
|
|
211
|
+
return !functionName || optimizedNames.has(functionName);
|
|
212
|
+
})
|
|
213
|
+
.map(file => {
|
|
206
214
|
const functionName = toFunctionName(file);
|
|
207
215
|
if (!functionName)
|
|
208
216
|
return file;
|
|
@@ -23,6 +23,7 @@ function createEmptyOptimizationStats() {
|
|
|
23
23
|
setblockSavedCommands: 0,
|
|
24
24
|
deadCodeRemoved: 0,
|
|
25
25
|
constantFolds: 0,
|
|
26
|
+
inlinedTrivialFunctions: 0,
|
|
26
27
|
totalCommandsBefore: 0,
|
|
27
28
|
totalCommandsAfter: 0,
|
|
28
29
|
};
|
|
@@ -330,11 +331,105 @@ function batchSetblocks(functions) {
|
|
|
330
331
|
stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0);
|
|
331
332
|
return { functions: optimized, stats };
|
|
332
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Inline trivial functions:
|
|
336
|
+
* 1. Functions that only contain a single `function` call → inline the call
|
|
337
|
+
* 2. Empty functions (no commands) → remove and eliminate all calls to them
|
|
338
|
+
*/
|
|
339
|
+
function inlineTrivialFunctions(functions) {
|
|
340
|
+
const FUNCTION_CMD_RE = /^function ([^:]+):(.+)$/;
|
|
341
|
+
// Find trivial functions (only a single function call, no other commands)
|
|
342
|
+
const trivialMap = new Map(); // fn name -> target fn name
|
|
343
|
+
const emptyFunctions = new Set(); // functions with no commands
|
|
344
|
+
// System functions that should never be removed
|
|
345
|
+
const SYSTEM_FUNCTIONS = new Set(['__tick', '__load']);
|
|
346
|
+
for (const fn of functions) {
|
|
347
|
+
// Never remove system functions
|
|
348
|
+
if (SYSTEM_FUNCTIONS.has(fn.name) || fn.name.startsWith('__trigger_')) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
const nonCommentCmds = fn.commands.filter(cmd => !cmd.cmd.startsWith('#'));
|
|
352
|
+
if (nonCommentCmds.length === 0 && fn.name.includes('/')) {
|
|
353
|
+
// Empty control-flow block (e.g., main/merge_5) - mark for removal
|
|
354
|
+
// Only remove if it's a sub-block (contains /), not a top-level function
|
|
355
|
+
emptyFunctions.add(fn.name);
|
|
356
|
+
}
|
|
357
|
+
else if (nonCommentCmds.length === 1 && fn.name.includes('/')) {
|
|
358
|
+
const match = nonCommentCmds[0].cmd.match(FUNCTION_CMD_RE);
|
|
359
|
+
if (match) {
|
|
360
|
+
// This function only calls another function
|
|
361
|
+
trivialMap.set(fn.name, match[2]);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// Resolve chains: if A -> B -> C, then A -> C
|
|
366
|
+
// Also handle: A -> B where B is empty → A is effectively empty
|
|
367
|
+
let changed = true;
|
|
368
|
+
while (changed) {
|
|
369
|
+
changed = false;
|
|
370
|
+
for (const [from, to] of trivialMap) {
|
|
371
|
+
if (emptyFunctions.has(to)) {
|
|
372
|
+
// Target is empty, so this function is effectively empty too
|
|
373
|
+
trivialMap.delete(from);
|
|
374
|
+
emptyFunctions.add(from);
|
|
375
|
+
changed = true;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
const finalTarget = trivialMap.get(to);
|
|
379
|
+
if (finalTarget && finalTarget !== to) {
|
|
380
|
+
trivialMap.set(from, finalTarget);
|
|
381
|
+
changed = true;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const totalRemoved = trivialMap.size + emptyFunctions.size;
|
|
387
|
+
if (totalRemoved === 0) {
|
|
388
|
+
return { functions, stats: {} };
|
|
389
|
+
}
|
|
390
|
+
// Set of all functions to remove
|
|
391
|
+
const removedNames = new Set([...trivialMap.keys(), ...emptyFunctions]);
|
|
392
|
+
// Rewrite all function calls to skip trivial wrappers or remove empty calls
|
|
393
|
+
const result = [];
|
|
394
|
+
for (const fn of functions) {
|
|
395
|
+
// Skip removed functions
|
|
396
|
+
if (removedNames.has(fn.name)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
// Rewrite function calls in this function
|
|
400
|
+
const rewrittenCmds = [];
|
|
401
|
+
for (const cmd of fn.commands) {
|
|
402
|
+
// Check if this is a call to an empty function
|
|
403
|
+
const emptyCallMatch = cmd.cmd.match(/^(?:execute .* run )?function ([^:]+):([^\s]+)$/);
|
|
404
|
+
if (emptyCallMatch) {
|
|
405
|
+
const targetFn = emptyCallMatch[2];
|
|
406
|
+
if (emptyFunctions.has(targetFn)) {
|
|
407
|
+
// Skip calls to empty functions entirely
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
// Rewrite calls to trivial wrapper functions
|
|
412
|
+
const rewritten = cmd.cmd.replace(/function ([^:]+):([^\s]+)/g, (match, ns, fnPath) => {
|
|
413
|
+
const target = trivialMap.get(fnPath);
|
|
414
|
+
return target ? `function ${ns}:${target}` : match;
|
|
415
|
+
});
|
|
416
|
+
rewrittenCmds.push({ ...cmd, cmd: rewritten });
|
|
417
|
+
}
|
|
418
|
+
result.push({ name: fn.name, commands: rewrittenCmds });
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
functions: result,
|
|
422
|
+
stats: { inlinedTrivialFunctions: totalRemoved }
|
|
423
|
+
};
|
|
424
|
+
}
|
|
333
425
|
function optimizeCommandFunctions(functions) {
|
|
334
426
|
const initial = cloneFunctions(functions);
|
|
335
427
|
const stats = createEmptyOptimizationStats();
|
|
336
428
|
stats.totalCommandsBefore = initial.reduce((sum, fn) => sum + fn.commands.length, 0);
|
|
337
|
-
|
|
429
|
+
// First pass: inline trivial functions
|
|
430
|
+
const inlined = inlineTrivialFunctions(initial);
|
|
431
|
+
mergeOptimizationStats(stats, inlined.stats);
|
|
432
|
+
const licm = applyLICM(inlined.functions);
|
|
338
433
|
mergeOptimizationStats(stats, licm.stats);
|
|
339
434
|
const cse = applyCSE(licm.functions);
|
|
340
435
|
mergeOptimizationStats(stats, cse.stats);
|
package/package.json
CHANGED
|
@@ -218,19 +218,28 @@ function applyFunctionOptimization(
|
|
|
218
218
|
})))
|
|
219
219
|
const commandMap = new Map(optimized.functions.map(fn => [fn.name, fn.commands]))
|
|
220
220
|
|
|
221
|
+
// Filter out files for functions that were removed (inlined trivial functions)
|
|
222
|
+
const optimizedNames = new Set(optimized.functions.map(fn => fn.name))
|
|
223
|
+
|
|
221
224
|
return {
|
|
222
|
-
files: files
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
225
|
+
files: files
|
|
226
|
+
.filter(file => {
|
|
227
|
+
const functionName = toFunctionName(file)
|
|
228
|
+
// Keep non-function files and functions that weren't removed
|
|
229
|
+
return !functionName || optimizedNames.has(functionName)
|
|
230
|
+
})
|
|
231
|
+
.map(file => {
|
|
232
|
+
const functionName = toFunctionName(file)
|
|
233
|
+
if (!functionName) return file
|
|
234
|
+
const commands = commandMap.get(functionName)
|
|
235
|
+
if (!commands) return file
|
|
236
|
+
const lines = file.content.split('\n')
|
|
237
|
+
const header = lines.filter(line => line.trim().startsWith('#'))
|
|
238
|
+
return {
|
|
239
|
+
...file,
|
|
240
|
+
content: [...header, ...commands.map(command => command.cmd)].join('\n'),
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
234
243
|
stats: optimized.stats,
|
|
235
244
|
}
|
|
236
245
|
}
|
|
@@ -10,6 +10,7 @@ export interface OptimizationStats {
|
|
|
10
10
|
setblockSavedCommands: number
|
|
11
11
|
deadCodeRemoved: number
|
|
12
12
|
constantFolds: number
|
|
13
|
+
inlinedTrivialFunctions: number
|
|
13
14
|
totalCommandsBefore: number
|
|
14
15
|
totalCommandsAfter: number
|
|
15
16
|
}
|
|
@@ -40,6 +41,7 @@ export function createEmptyOptimizationStats(): OptimizationStats {
|
|
|
40
41
|
setblockSavedCommands: 0,
|
|
41
42
|
deadCodeRemoved: 0,
|
|
42
43
|
constantFolds: 0,
|
|
44
|
+
inlinedTrivialFunctions: 0,
|
|
43
45
|
totalCommandsBefore: 0,
|
|
44
46
|
totalCommandsAfter: 0,
|
|
45
47
|
}
|
|
@@ -394,12 +396,122 @@ export function batchSetblocks(functions: CommandFunction[]): { functions: Comma
|
|
|
394
396
|
return { functions: optimized, stats }
|
|
395
397
|
}
|
|
396
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Inline trivial functions:
|
|
401
|
+
* 1. Functions that only contain a single `function` call → inline the call
|
|
402
|
+
* 2. Empty functions (no commands) → remove and eliminate all calls to them
|
|
403
|
+
*/
|
|
404
|
+
function inlineTrivialFunctions(functions: CommandFunction[]): { functions: CommandFunction[]; stats: Partial<OptimizationStats> } {
|
|
405
|
+
const FUNCTION_CMD_RE = /^function ([^:]+):(.+)$/
|
|
406
|
+
|
|
407
|
+
// Find trivial functions (only a single function call, no other commands)
|
|
408
|
+
const trivialMap = new Map<string, string>() // fn name -> target fn name
|
|
409
|
+
const emptyFunctions = new Set<string>() // functions with no commands
|
|
410
|
+
|
|
411
|
+
// System functions that should never be removed
|
|
412
|
+
const SYSTEM_FUNCTIONS = new Set(['__tick', '__load'])
|
|
413
|
+
|
|
414
|
+
for (const fn of functions) {
|
|
415
|
+
// Never remove system functions
|
|
416
|
+
if (SYSTEM_FUNCTIONS.has(fn.name) || fn.name.startsWith('__trigger_')) {
|
|
417
|
+
continue
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const nonCommentCmds = fn.commands.filter(cmd => !cmd.cmd.startsWith('#'))
|
|
421
|
+
if (nonCommentCmds.length === 0 && fn.name.includes('/')) {
|
|
422
|
+
// Empty control-flow block (e.g., main/merge_5) - mark for removal
|
|
423
|
+
// Only remove if it's a sub-block (contains /), not a top-level function
|
|
424
|
+
emptyFunctions.add(fn.name)
|
|
425
|
+
} else if (nonCommentCmds.length === 1 && fn.name.includes('/')) {
|
|
426
|
+
const match = nonCommentCmds[0].cmd.match(FUNCTION_CMD_RE)
|
|
427
|
+
if (match) {
|
|
428
|
+
// This function only calls another function
|
|
429
|
+
trivialMap.set(fn.name, match[2])
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Resolve chains: if A -> B -> C, then A -> C
|
|
435
|
+
// Also handle: A -> B where B is empty → A is effectively empty
|
|
436
|
+
let changed = true
|
|
437
|
+
while (changed) {
|
|
438
|
+
changed = false
|
|
439
|
+
for (const [from, to] of trivialMap) {
|
|
440
|
+
if (emptyFunctions.has(to)) {
|
|
441
|
+
// Target is empty, so this function is effectively empty too
|
|
442
|
+
trivialMap.delete(from)
|
|
443
|
+
emptyFunctions.add(from)
|
|
444
|
+
changed = true
|
|
445
|
+
} else {
|
|
446
|
+
const finalTarget = trivialMap.get(to)
|
|
447
|
+
if (finalTarget && finalTarget !== to) {
|
|
448
|
+
trivialMap.set(from, finalTarget)
|
|
449
|
+
changed = true
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const totalRemoved = trivialMap.size + emptyFunctions.size
|
|
456
|
+
if (totalRemoved === 0) {
|
|
457
|
+
return { functions, stats: {} }
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Set of all functions to remove
|
|
461
|
+
const removedNames = new Set([...trivialMap.keys(), ...emptyFunctions])
|
|
462
|
+
|
|
463
|
+
// Rewrite all function calls to skip trivial wrappers or remove empty calls
|
|
464
|
+
const result: CommandFunction[] = []
|
|
465
|
+
|
|
466
|
+
for (const fn of functions) {
|
|
467
|
+
// Skip removed functions
|
|
468
|
+
if (removedNames.has(fn.name)) {
|
|
469
|
+
continue
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Rewrite function calls in this function
|
|
473
|
+
const rewrittenCmds: typeof fn.commands = []
|
|
474
|
+
for (const cmd of fn.commands) {
|
|
475
|
+
// Check if this is a call to an empty function
|
|
476
|
+
const emptyCallMatch = cmd.cmd.match(/^(?:execute .* run )?function ([^:]+):([^\s]+)$/)
|
|
477
|
+
if (emptyCallMatch) {
|
|
478
|
+
const targetFn = emptyCallMatch[2]
|
|
479
|
+
if (emptyFunctions.has(targetFn)) {
|
|
480
|
+
// Skip calls to empty functions entirely
|
|
481
|
+
continue
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Rewrite calls to trivial wrapper functions
|
|
486
|
+
const rewritten = cmd.cmd.replace(
|
|
487
|
+
/function ([^:]+):([^\s]+)/g,
|
|
488
|
+
(match, ns, fnPath) => {
|
|
489
|
+
const target = trivialMap.get(fnPath)
|
|
490
|
+
return target ? `function ${ns}:${target}` : match
|
|
491
|
+
}
|
|
492
|
+
)
|
|
493
|
+
rewrittenCmds.push({ ...cmd, cmd: rewritten })
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
result.push({ name: fn.name, commands: rewrittenCmds })
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
functions: result,
|
|
501
|
+
stats: { inlinedTrivialFunctions: totalRemoved }
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
397
505
|
export function optimizeCommandFunctions(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
|
|
398
506
|
const initial = cloneFunctions(functions)
|
|
399
507
|
const stats = createEmptyOptimizationStats()
|
|
400
508
|
stats.totalCommandsBefore = initial.reduce((sum, fn) => sum + fn.commands.length, 0)
|
|
401
509
|
|
|
402
|
-
|
|
510
|
+
// First pass: inline trivial functions
|
|
511
|
+
const inlined = inlineTrivialFunctions(initial)
|
|
512
|
+
mergeOptimizationStats(stats, inlined.stats)
|
|
513
|
+
|
|
514
|
+
const licm = applyLICM(inlined.functions)
|
|
403
515
|
mergeOptimizationStats(stats, licm.stats)
|
|
404
516
|
|
|
405
517
|
const cse = applyCSE(licm.functions)
|