cluaupp 1.5.0 → 1.5.1
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/CHANGELOG.md +47 -0
- package/README.md +10 -5
- package/api/CHANGELOG.md +3 -0
- package/api/chat.js +74 -0
- package/api/fixtures/roblox/README.md +6 -0
- package/api/fixtures/roblox/aux/sample.d.luau +6 -0
- package/api/fixtures/roblox/mini/Mini-API-Dump.json +72 -0
- package/api/generated/bench-latest.json +39 -0
- package/api/generated/capability-profile.json +76 -0
- package/api/generated/lsp-index.json +1 -0
- package/api/generated/luau/classes.d.luau +11221 -0
- package/api/generated/luau/datatypes.d.luau +327 -0
- package/api/generated/luau/enums.d.luau +4918 -0
- package/api/generated/luau/globals.d.luau +12 -0
- package/api/manifests/roblox-target.manifest.json +22 -0
- package/api/normalized/roblox-target.profile.json +237461 -0
- package/api/overrides/classes.json +8 -0
- package/api/overrides/compatibility.json +9 -0
- package/api/overrides/datatypes.json +1579 -0
- package/api/overrides/enums.json +8 -0
- package/api/overrides/globals.json +8 -0
- package/api/overrides/type-mappings.json +9 -0
- package/api/roblox-api.lock.json +9 -0
- package/data/Mini-API-Dump.json +1 -0
- package/editors/vscode/README.md +12 -10
- package/editors/vscode/extension.js +82 -0
- package/editors/vscode/icons/axiom.svg +4 -0
- package/editors/vscode/icons/bloom.svg +5 -0
- package/editors/vscode/icons/helm.svg +4 -0
- package/editors/vscode/icons/hive.svg +5 -0
- package/editors/vscode/icons/mint.svg +4 -0
- package/editors/vscode/icons/shift.svg +6 -0
- package/editors/vscode/language-configuration-schema.json +24 -0
- package/editors/vscode/package.json +172 -12
- package/editors/vscode/snippets/axiom.json +12 -0
- package/editors/vscode/snippets/bloom.json +12 -0
- package/editors/vscode/snippets/helm.json +12 -0
- package/editors/vscode/snippets/hive.json +12 -0
- package/editors/vscode/snippets/mint.json +12 -0
- package/editors/vscode/snippets/shift.json +17 -0
- package/editors/vscode/syntaxes/axiom.tmLanguage.json +5 -0
- package/editors/vscode/syntaxes/bloom.tmLanguage.json +5 -0
- package/editors/vscode/syntaxes/helm.tmLanguage.json +5 -0
- package/editors/vscode/syntaxes/hive.tmLanguage.json +5 -0
- package/editors/vscode/syntaxes/mint.tmLanguage.json +5 -0
- package/editors/vscode/syntaxes/schema.tmLanguage.json +89 -0
- package/editors/vscode/syntaxes/shift.tmLanguage.json +5 -0
- package/examples/game/.cluaupp/compile-cache/ReplicatedStorage__Shared__Constants__Datas__TemplateData.clh.json +1 -0
- package/examples/game/.cluaupp/compile-cache/ServerScriptService__Boot__DataBoot.server.clpp.json +1 -0
- package/examples/game/.cluaupp/compile-cache/ServerScriptService__Configuration__ServerConfig.clp.json +1 -0
- package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__Guard.server.clpp.json +1 -0
- package/examples/game/.cluaupp/compile-cache/ServerScriptService__Handlers__PlayerHandler.server.clpp.json +1 -0
- package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterCharacterScripts__Character__CharacterReady.client.clpp.json +1 -0
- package/examples/game/.cluaupp/compile-cache/StarterPlayer__StarterPlayerScripts__Controllers__DataController.client.clpp.json +1 -0
- package/examples/game/.cluaupp/compile-cache/index.json +12 -0
- package/examples/game/.cluaupp/datamodel-profile.json +94 -0
- package/examples/game/.vscode/settings.json +12 -1
- package/examples/game/include/clpp/datatypes.clh +337 -373
- package/examples/game/include/clpp/generated/datamodel.clh +25 -0
- package/examples/game/include/clpp/generated/enums.clh +1 -1
- package/examples/game/include/clpp/generated/instances.clh +41135 -41064
- package/examples/game/include/clpp/libs/axiom.clh +1 -1
- package/examples/game/include/clpp/libs/math.clh +14 -0
- package/examples/game/include/clpp/libs/spring.clh +17 -0
- package/examples/game/include/clpp/libs/ward.clh +1 -0
- package/examples/game/include/clpp/roblox.clh +3 -4
- package/examples/game/libs/ArrayIndexer/init.luau +209 -0
- package/examples/game/libs/Axiom/init.luau +608 -0
- package/examples/game/libs/Bloom/init.luau +433 -0
- package/examples/game/libs/Coil/init.luau +215 -0
- package/examples/game/libs/Crest/init.luau +426 -0
- package/examples/game/libs/Echo/init.luau +417 -0
- package/examples/game/libs/Ember/init.luau +365 -0
- package/examples/game/libs/Flare/init.luau +717 -0
- package/examples/game/libs/Gleam/init.luau +729 -0
- package/examples/game/libs/Guide/init.luau +215 -0
- package/examples/game/libs/Helm/init.luau +377 -0
- package/examples/game/libs/Hive/init.luau +241 -0
- package/examples/game/libs/Keep/Client.luau +114 -0
- package/examples/game/libs/Keep/Data.luau +883 -0
- package/examples/game/libs/Keep/Engine.luau +211 -0
- package/examples/game/libs/Keep/Enum.luau +6 -0
- package/examples/game/libs/Keep/Enums.luau +10 -0
- package/examples/game/libs/Keep/Path.luau +236 -0
- package/examples/game/libs/Keep/Replicator.luau +298 -0
- package/examples/game/libs/Keep/Server.luau +251 -0
- package/examples/game/libs/Keep/Store.luau +652 -0
- package/examples/game/libs/Keep/Trade.luau +455 -0
- package/examples/game/libs/Keep/init.luau +205 -0
- package/examples/game/libs/Lens/init.luau +609 -0
- package/examples/game/libs/Mint/init.luau +279 -0
- package/examples/game/libs/Net/init.luau +156 -0
- package/examples/game/libs/Net/src/codec.luau +224 -0
- package/examples/game/libs/Occlude/init.luau +117 -0
- package/examples/game/libs/Pin/init.luau +309 -0
- package/examples/game/libs/Promise/LICENSE +21 -0
- package/examples/game/libs/Promise/_impl/LICENSE +21 -0
- package/examples/game/libs/Promise/_impl/init.lua +1991 -0
- package/examples/game/libs/Promise/init.lua +1990 -0
- package/examples/game/libs/Promise/init.luau +13 -0
- package/examples/game/libs/Promise/types.luau +46 -0
- package/examples/game/libs/Roster/init.luau +686 -0
- package/examples/game/libs/SOURCES.md +31 -0
- package/examples/game/libs/Shift/init.luau +238 -0
- package/examples/game/libs/Spark/init.luau +487 -0
- package/examples/game/libs/Stage/init.luau +178 -0
- package/examples/game/libs/Sweep/init.luau +259 -0
- package/examples/game/libs/Trace/init.luau +213 -0
- package/examples/game/libs/Ward/init.luau +211 -0
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau +29 -0
- package/examples/game/out/ReplicatedStorage/Shared/Constants/Datas/TemplateData.luau.map.json +13 -0
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau +83 -0
- package/examples/game/out/ReplicatedStorage/Shared/Net/Net.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau +18 -0
- package/examples/game/out/ServerScriptService/Boot/DataBoot.server.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau +14 -0
- package/examples/game/out/ServerScriptService/Configuration/ServerConfig.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau +15 -0
- package/examples/game/out/ServerScriptService/Handlers/Guard.server.luau.map.json +13 -0
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau +50 -0
- package/examples/game/out/ServerScriptService/Handlers/PlayerHandler.server.luau.map.json +13 -0
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau +12 -0
- package/examples/game/out/StarterPlayer/StarterCharacterScripts/Character/CharacterReady.client.luau.map.json +13 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau +16 -0
- package/examples/game/out/StarterPlayer/StarterPlayerScripts/Controllers/DataController.client.luau.map.json +13 -0
- package/examples/game/src/ReplicatedStorage/Shared/Net/Net.clh +26 -0
- package/examples/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
- package/examples/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
- package/examples/game/stylua.toml +9 -0
- package/generated/api/build-profile.js +115 -0
- package/generated/api/cache.js +44 -0
- package/generated/api/coverage.js +32 -0
- package/generated/api/diff.js +125 -0
- package/generated/api/generators/clpp-headers.js +353 -0
- package/generated/api/generators/docs-reference.js +7 -0
- package/generated/api/generators/lsp-index.js +81 -0
- package/generated/api/generators/luau-defs.js +94 -0
- package/generated/api/ide.js +76 -0
- package/generated/api/identifier.js +47 -0
- package/generated/api/index.js +324 -0
- package/generated/api/loader.js +57 -0
- package/generated/api/merge.js +88 -0
- package/generated/api/model.js +5 -0
- package/generated/api/normalize.js +231 -0
- package/generated/api/overrides-validate.js +58 -0
- package/generated/api/perf.js +12 -0
- package/generated/api/registry.js +136 -0
- package/generated/api/release.js +44 -0
- package/generated/api/resolver.js +250 -0
- package/generated/api/sources/aux-types-diff.js +75 -0
- package/generated/api/sources/client-tracker.js +11 -0
- package/generated/api/sources/creator-docs.js +42 -0
- package/generated/api/sources/luau-types.js +34 -0
- package/generated/api/sources/mini-dump.js +17 -0
- package/generated/api/sources/studio-api.js +8 -0
- package/generated/api/sources/studio-apiv2.js +11 -0
- package/generated/api/sources/studio-full-api.js +8 -0
- package/generated/api/studio-smoke.js +80 -0
- package/generated/api/type-parse.js +49 -0
- package/generated/api/understander.js +51 -0
- package/generated/api/validate.js +135 -0
- package/generated/api-cli.js +421 -0
- package/generated/cli.js +16 -5
- package/generated/clpp/contract.js +3 -3
- package/generated/clpp/format-luau.js +236 -0
- package/generated/clpp/index.js +13 -1
- package/generated/clpp/modules.js +227 -0
- package/generated/clpp/postprocess.js +86 -5
- package/generated/clpp/rewrite-janitor.js +145 -0
- package/generated/clpp/runner.js +14 -2
- package/generated/compile.js +82 -0
- package/generated/flare/parse.js +12 -1
- package/generated/intellisense.js +57 -1
- package/generated/native/index.js +217 -16
- package/generated/target/authority-check.js +136 -0
- package/generated/target/authority-graph.js +74 -0
- package/generated/target/build-cache.js +112 -0
- package/generated/target/capabilities.js +89 -0
- package/generated/target/capability-profile.js +30 -0
- package/generated/target/component-contracts.js +60 -0
- package/generated/target/context-check.js +160 -0
- package/generated/target/datamodel-check.js +170 -0
- package/generated/target/datamodel-generate.js +74 -0
- package/generated/target/datamodel.js +233 -0
- package/generated/target/diagnostics.js +63 -0
- package/generated/target/doctor.js +152 -0
- package/generated/target/flare-version.js +58 -0
- package/generated/target/get-service.js +69 -0
- package/generated/target/lifetime-check.js +47 -0
- package/generated/target/luau-stamp.js +39 -0
- package/generated/target/optimizer-advise.js +88 -0
- package/generated/target/optimizer-apply.js +44 -0
- package/generated/target/optimizer-soa.js +254 -0
- package/generated/target/parallel-check.js +136 -0
- package/generated/target/pgo.js +148 -0
- package/generated/target/project-docs.js +69 -0
- package/generated/target/project-graph.js +71 -0
- package/generated/target/roblox.js +63 -0
- package/generated/target/security-check.js +41 -0
- package/generated/target/source-map.js +25 -0
- package/generated/target/studio-bridge.js +112 -0
- package/generated/utils/project.js +178 -8
- package/generated/utils/rojo-mapper.js +11 -3
- package/include/clpp/datatypes.clh +337 -373
- package/include/clpp/generated/enums.clh +1 -1
- package/include/clpp/generated/instances.clh +41135 -41064
- package/include/clpp/libs/axiom.clh +1 -1
- package/include/clpp/libs/hive.clh +1 -0
- package/include/clpp/libs/keep.clh +2 -0
- package/include/clpp/libs/math.clh +14 -0
- package/include/clpp/libs/spring.clh +17 -0
- package/include/clpp/libs/ward.clh +1 -0
- package/include/clpp/roblox.clh +3 -4
- package/package.json +10 -3
- package/plugins/CluauppNav/Plugin.luau +207 -0
- package/plugins/CluauppNav/ProfileLogger.luau +27 -0
- package/plugins/CluauppNav/README.md +24 -0
- package/runtime/Axiom/init.luau +10 -7
- package/runtime/Coil/init.luau +8 -2
- package/runtime/Crest/init.luau +1 -0
- package/runtime/Flare/init.luau +2 -1
- package/runtime/Gleam/init.luau +15 -4
- package/runtime/Hive/init.luau +36 -14
- package/runtime/Pin/init.luau +5 -2
- package/runtime/Roster/init.luau +2 -0
- package/runtime/Spark/init.luau +3 -0
- package/runtime/Sweep/init.luau +2 -0
- package/runtime/Trace/init.luau +3 -0
- package/runtime/Ward/init.luau +38 -5
- package/scripts/postinstall-editor.js +17 -0
- package/skills/README.md +13 -0
- package/skills/clpp-hive-flare/SKILL.md +79 -0
- package/skills/clpp-language/SKILL.md +123 -0
- package/skills/clpp-language/compiler.md +89 -0
- package/skills/clpp-style/SKILL.md +95 -0
- package/src/api/build-profile.ts +138 -0
- package/src/api/cache.ts +45 -0
- package/src/api/coverage.ts +46 -0
- package/src/api/diff.ts +143 -0
- package/src/api/generators/clpp-headers.ts +377 -0
- package/src/api/generators/docs-reference.ts +11 -0
- package/src/api/generators/lsp-index.ts +106 -0
- package/src/api/generators/luau-defs.ts +98 -0
- package/src/api/ide.ts +87 -0
- package/src/api/identifier.ts +47 -0
- package/src/api/index.ts +269 -0
- package/src/api/loader.ts +60 -0
- package/src/api/merge.ts +126 -0
- package/src/api/model.ts +283 -0
- package/src/api/normalize.ts +257 -0
- package/src/api/overrides-validate.ts +58 -0
- package/src/api/perf.ts +15 -0
- package/src/api/registry.ts +162 -0
- package/src/api/release.ts +47 -0
- package/src/api/resolver.ts +255 -0
- package/src/api/sources/aux-types-diff.ts +81 -0
- package/src/api/sources/client-tracker.ts +10 -0
- package/src/api/sources/creator-docs.ts +37 -0
- package/src/api/sources/luau-types.ts +30 -0
- package/src/api/sources/mini-dump.ts +50 -0
- package/src/api/sources/studio-api.ts +7 -0
- package/src/api/sources/studio-apiv2.ts +10 -0
- package/src/api/sources/studio-full-api.ts +7 -0
- package/src/api/studio-smoke.ts +82 -0
- package/src/api/type-parse.ts +48 -0
- package/src/api/understander.ts +72 -0
- package/src/api/validate.ts +160 -0
- package/src/api-cli.ts +493 -0
- package/src/api.generated.js +37 -22
- package/src/cli.ts +21 -6
- package/src/clpp/contract.ts +34 -3
- package/src/clpp/format-luau.ts +262 -0
- package/src/clpp/index.ts +13 -2
- package/src/clpp/modules.ts +252 -0
- package/src/clpp/postprocess.ts +100 -5
- package/src/clpp/rewrite-janitor.ts +156 -0
- package/src/clpp/runner.ts +14 -2
- package/src/compile.ts +87 -0
- package/src/flare/parse.ts +14 -1
- package/src/flare/types.ts +2 -0
- package/src/intellisense.ts +58 -1
- package/src/native/index.ts +225 -16
- package/src/target/authority-check.ts +162 -0
- package/src/target/authority-graph.ts +80 -0
- package/src/target/build-cache.ts +129 -0
- package/src/target/capabilities.ts +115 -0
- package/src/target/capability-profile.ts +25 -0
- package/src/target/component-contracts.ts +64 -0
- package/src/target/context-check.ts +177 -0
- package/src/target/datamodel-check.ts +188 -0
- package/src/target/datamodel-generate.ts +78 -0
- package/src/target/datamodel.ts +261 -0
- package/src/target/diagnostics.ts +67 -0
- package/src/target/doctor.ts +171 -0
- package/src/target/flare-version.ts +61 -0
- package/src/target/get-service.ts +85 -0
- package/src/target/lifetime-check.ts +45 -0
- package/src/target/luau-stamp.ts +33 -0
- package/src/target/optimizer-advise.ts +93 -0
- package/src/target/optimizer-apply.ts +63 -0
- package/src/target/optimizer-soa.ts +270 -0
- package/src/target/parallel-check.ts +135 -0
- package/src/target/pgo.ts +157 -0
- package/src/target/project-docs.ts +68 -0
- package/src/target/project-graph.ts +75 -0
- package/src/target/roblox.ts +78 -0
- package/src/target/security-check.ts +40 -0
- package/src/target/source-map.ts +36 -0
- package/src/target/studio-bridge.ts +140 -0
- package/src/types.ts +14 -0
- package/src/utils/project.ts +183 -9
- package/src/utils/rojo-mapper.ts +12 -3
- package/stylua.toml +9 -0
- package/templates/game/.vscode/settings.json +7 -1
- package/templates/game/include/clpp/datatypes.clh +337 -373
- package/templates/game/include/clpp/generated/enums.clh +1 -1
- package/templates/game/include/clpp/generated/instances.clh +41135 -41064
- package/templates/game/include/clpp/libs/axiom.clh +1 -1
- package/templates/game/include/clpp/libs/ward.clh +1 -0
- package/templates/game/include/clpp/roblox.clh +3 -4
- package/templates/game/src/ServerScriptService/Boot/DataBoot.server.clpp +1 -1
- package/templates/game/src/ServerScriptService/Handlers/PlayerHandler.server.clpp +1 -1
- package/templates/game/stylua.toml +9 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Cluaupp Optimizer advisor (v1) — suggestions only; does not rewrite Luau yet.
|
|
4
|
+
* Philosophy: write idiomatic CL++; compiler advises best representation.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.adviseOptimizer = adviseOptimizer;
|
|
8
|
+
exports.adviceAsDiagnostics = adviceAsDiagnostics;
|
|
9
|
+
const diagnostics_js_1 = require("./diagnostics.js");
|
|
10
|
+
function adviseOptimizer(source, fileName) {
|
|
11
|
+
const clean = (0, diagnostics_js_1.stripCommentsKeepStrings)(source);
|
|
12
|
+
const advice = [];
|
|
13
|
+
const structRe = /struct\s+([A-Za-z_]\w*)\s*\{([^}]*)\}/g;
|
|
14
|
+
let m;
|
|
15
|
+
while ((m = structRe.exec(clean))) {
|
|
16
|
+
const name = m[1];
|
|
17
|
+
const body = m[2];
|
|
18
|
+
const fields = body.split(";").map((s) => s.trim()).filter(Boolean);
|
|
19
|
+
if (fields.length < 2)
|
|
20
|
+
continue;
|
|
21
|
+
const numeric = fields.filter((f) => /^(float|double|int|i32|f32|f64|bool)\b/.test(f) || /\bVector3\b/.test(f));
|
|
22
|
+
const ratio = numeric.length / fields.length;
|
|
23
|
+
if (ratio >= 0.75 && fields.length >= 3) {
|
|
24
|
+
advice.push({
|
|
25
|
+
kind: "storage",
|
|
26
|
+
target: name,
|
|
27
|
+
message: `Hot numeric struct ${name} (${numeric.length}/${fields.length} fields) — candidate for SoA / dense storage (Optimizer Level 1–3)`,
|
|
28
|
+
severity: "info",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
const bools = fields.filter((f) => /^bool\b/.test(f));
|
|
32
|
+
if (bools.length >= 4) {
|
|
33
|
+
advice.push({
|
|
34
|
+
kind: "layout",
|
|
35
|
+
target: name,
|
|
36
|
+
message: `${name} has ${bools.length} bool fields — candidate for internal bitset packing`,
|
|
37
|
+
severity: "info",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const fnRe = /(?:void|int|float|double|bool|auto)\s+([A-Za-z_]\w*)\s*\([^)]*\)\s*\{/g;
|
|
42
|
+
while ((m = fnRe.exec(clean))) {
|
|
43
|
+
const name = m[1];
|
|
44
|
+
if (name === "init")
|
|
45
|
+
continue;
|
|
46
|
+
const start = m.index + m[0].length;
|
|
47
|
+
let depth = 1;
|
|
48
|
+
let i = start;
|
|
49
|
+
for (; i < clean.length && depth > 0; i++) {
|
|
50
|
+
if (clean[i] === "{")
|
|
51
|
+
depth++;
|
|
52
|
+
else if (clean[i] === "}")
|
|
53
|
+
depth--;
|
|
54
|
+
}
|
|
55
|
+
const body = clean.slice(start, i);
|
|
56
|
+
const apiHits = (body.match(/GetService|WaitForChild|Instance|FindFirstChild|FireServer|FireClient/g) || []).length;
|
|
57
|
+
const numericHits = (body.match(/\b(sin|cos|sqrt|Vector3|CFrame|math::)\b|[+\-*/]=|[+\-*/]/g) || []).length;
|
|
58
|
+
if (numericHits >= 6 && apiHits <= 1 && body.length > 40) {
|
|
59
|
+
advice.push({
|
|
60
|
+
kind: "native",
|
|
61
|
+
target: name,
|
|
62
|
+
message: `${name} looks numeric-hot / low Instance API — good @native / --!native candidate (CLUAU-PERF-001)`,
|
|
63
|
+
severity: "info",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (apiHits === 0 && /for\s*\(/.test(body) && !/shared|static/.test(body)) {
|
|
67
|
+
advice.push({
|
|
68
|
+
kind: "parallel",
|
|
69
|
+
target: name,
|
|
70
|
+
message: `${name} may be parallelizable (independent loop, no shared Instance API) — review ThreadSafety`,
|
|
71
|
+
severity: "info",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
void fileName;
|
|
76
|
+
void diagnostics_js_1.lineColAt;
|
|
77
|
+
return advice;
|
|
78
|
+
}
|
|
79
|
+
function adviceAsDiagnostics(advice, fileName) {
|
|
80
|
+
return advice.map((a) => ({
|
|
81
|
+
code: a.kind === "native" ? "CLUAU_PERF001" : a.kind === "parallel" ? "CLUAU_PAR_ADVISE" : "CLUAU_OPT001",
|
|
82
|
+
message: a.message,
|
|
83
|
+
line: 1,
|
|
84
|
+
column: 1,
|
|
85
|
+
severity: a.severity === "warning" ? "warning" : "info",
|
|
86
|
+
file: fileName,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Apply Optimizer transforms to CL++ source:
|
|
4
|
+
* - `#pragma native` on numeric-hot functions
|
|
5
|
+
* - `#pragma layout soa` + SoA Luau modules when --layout
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.applyOptimizerHints = applyOptimizerHints;
|
|
9
|
+
const optimizer_advise_js_1 = require("./optimizer-advise.js");
|
|
10
|
+
const pgo_js_1 = require("./pgo.js");
|
|
11
|
+
const optimizer_soa_js_1 = require("./optimizer-soa.js");
|
|
12
|
+
function applyOptimizerHints(source, fileName, profile = null, opts = {}) {
|
|
13
|
+
const advice = (0, pgo_js_1.rankAdvice)((0, optimizer_advise_js_1.adviseOptimizer)(source, fileName), profile);
|
|
14
|
+
const nativeTargets = new Set(advice.filter((a) => a.kind === "native").map((a) => a.target));
|
|
15
|
+
let out = source;
|
|
16
|
+
const applied = [];
|
|
17
|
+
for (const name of nativeTargets) {
|
|
18
|
+
const re = new RegExp(`((?:^|\\n)(?:\\s*))((?:void|int|float|double|bool|auto)\\s+${name}\\s*\\()`, "m");
|
|
19
|
+
const m = out.match(re);
|
|
20
|
+
if (!m)
|
|
21
|
+
continue;
|
|
22
|
+
const before = out.slice(0, m.index + m[1].length);
|
|
23
|
+
if (/#pragma\s+native\s*$/m.test(before.slice(-80)) ||
|
|
24
|
+
/#pragma\s+native/.test(out.slice(Math.max(0, m.index - 40), m.index))) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
out = out.replace(re, `$1#pragma native\n$1$2`);
|
|
28
|
+
applied.push(`#pragma native → ${name}`);
|
|
29
|
+
}
|
|
30
|
+
let soaPlans;
|
|
31
|
+
if (opts.layout) {
|
|
32
|
+
const layout = (0, optimizer_soa_js_1.applyLayoutPragmas)(out, fileName, profile);
|
|
33
|
+
out = layout.source;
|
|
34
|
+
applied.push(...layout.applied);
|
|
35
|
+
soaPlans = layout.plans;
|
|
36
|
+
if (opts.projectRoot && layout.plans.length) {
|
|
37
|
+
const files = (0, optimizer_soa_js_1.writeSoaArtifacts)(opts.projectRoot, layout.plans, { useBuffer: opts.useBuffer });
|
|
38
|
+
for (const f of files) {
|
|
39
|
+
applied.push(`SoA emit → ${f.replace(/\\/g, "/")}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { source: out, changed: applied.length > 0 || out !== source, applied, soaPlans };
|
|
44
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Opt-in SoA / dense storage codegen.
|
|
4
|
+
* Triggered by `#pragma layout soa StructName` (or optimize --apply --layout).
|
|
5
|
+
* Emits a Luau ModuleScript companion — does not silently rewrite gameplay structs.
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.parseSoaFields = parseSoaFields;
|
|
12
|
+
exports.findStructBody = findStructBody;
|
|
13
|
+
exports.listLayoutTargets = listLayoutTargets;
|
|
14
|
+
exports.planSoa = planSoa;
|
|
15
|
+
exports.generateSoaLuau = generateSoaLuau;
|
|
16
|
+
exports.applyLayoutPragmas = applyLayoutPragmas;
|
|
17
|
+
exports.writeSoaArtifacts = writeSoaArtifacts;
|
|
18
|
+
exports.collectSoaPlansFromProject = collectSoaPlansFromProject;
|
|
19
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
21
|
+
const optimizer_advise_js_1 = require("./optimizer-advise.js");
|
|
22
|
+
const pgo_js_1 = require("./pgo.js");
|
|
23
|
+
const FIELD_RE = /^(?:float|f32|double|f64)\s+([A-Za-z_]\w*)|^(?:int|i32)\s+([A-Za-z_]\w*)|^bool\s+([A-Za-z_]\w*)|^Vector3\s+([A-Za-z_]\w*)/;
|
|
24
|
+
function parseSoaFields(structBody) {
|
|
25
|
+
const fields = [];
|
|
26
|
+
for (const piece of structBody.split(";")) {
|
|
27
|
+
const line = piece.trim().replace(/=.*/, "").trim();
|
|
28
|
+
if (!line)
|
|
29
|
+
continue;
|
|
30
|
+
const m = line.match(FIELD_RE);
|
|
31
|
+
if (!m)
|
|
32
|
+
continue;
|
|
33
|
+
if (m[1])
|
|
34
|
+
fields.push({ name: m[1], kind: "f32" });
|
|
35
|
+
else if (m[2])
|
|
36
|
+
fields.push({ name: m[2], kind: "i32" });
|
|
37
|
+
else if (m[3])
|
|
38
|
+
fields.push({ name: m[3], kind: "bool" });
|
|
39
|
+
else if (m[4])
|
|
40
|
+
fields.push({ name: m[4], kind: "Vector3" });
|
|
41
|
+
}
|
|
42
|
+
return fields;
|
|
43
|
+
}
|
|
44
|
+
function findStructBody(source, structName) {
|
|
45
|
+
const re = new RegExp(`struct\\s+${structName}\\s*\\{([^}]*)\\}`);
|
|
46
|
+
const m = source.match(re);
|
|
47
|
+
return m ? m[1] : null;
|
|
48
|
+
}
|
|
49
|
+
function listLayoutTargets(source) {
|
|
50
|
+
const names = new Set();
|
|
51
|
+
const re = /#pragma\s+layout\s+soa\s+([A-Za-z_]\w*)/g;
|
|
52
|
+
let m;
|
|
53
|
+
while ((m = re.exec(source))) {
|
|
54
|
+
names.add(m[1]);
|
|
55
|
+
}
|
|
56
|
+
return [...names];
|
|
57
|
+
}
|
|
58
|
+
function planSoa(source, structName) {
|
|
59
|
+
const body = findStructBody(source, structName);
|
|
60
|
+
if (!body)
|
|
61
|
+
return null;
|
|
62
|
+
const fields = parseSoaFields(body);
|
|
63
|
+
if (fields.length < 2)
|
|
64
|
+
return null;
|
|
65
|
+
return { structName, fields };
|
|
66
|
+
}
|
|
67
|
+
function defaultFor(kind) {
|
|
68
|
+
if (kind === "f32")
|
|
69
|
+
return "0";
|
|
70
|
+
if (kind === "i32")
|
|
71
|
+
return "0";
|
|
72
|
+
if (kind === "bool")
|
|
73
|
+
return "false";
|
|
74
|
+
return "Vector3.zero";
|
|
75
|
+
}
|
|
76
|
+
/** Generate a standalone Luau SoA module (table-columns; buffer pack optional for floats). */
|
|
77
|
+
function generateSoaLuau(plan, useBuffer = false) {
|
|
78
|
+
const { structName, fields } = plan;
|
|
79
|
+
const cols = fields
|
|
80
|
+
.map((f) => {
|
|
81
|
+
if (useBuffer && (f.kind === "f32" || f.kind === "i32")) {
|
|
82
|
+
return `\t\t${f.name} = buffer.create(0), -- grown on ensure`;
|
|
83
|
+
}
|
|
84
|
+
return `\t\t${f.name} = table.create(capacity),`;
|
|
85
|
+
})
|
|
86
|
+
.join("\n");
|
|
87
|
+
const pushLines = fields
|
|
88
|
+
.map((f) => {
|
|
89
|
+
if (useBuffer && f.kind === "f32") {
|
|
90
|
+
return `\tbuffer.writef32(store.${f.name}, (i - 1) * 4, row.${f.name} or 0)`;
|
|
91
|
+
}
|
|
92
|
+
if (useBuffer && f.kind === "i32") {
|
|
93
|
+
return `\tbuffer.writei32(store.${f.name}, (i - 1) * 4, row.${f.name} or 0)`;
|
|
94
|
+
}
|
|
95
|
+
return `\tstore.${f.name}[i] = row.${f.name}`;
|
|
96
|
+
})
|
|
97
|
+
.join("\n");
|
|
98
|
+
const getLines = fields
|
|
99
|
+
.map((f) => {
|
|
100
|
+
if (useBuffer && f.kind === "f32") {
|
|
101
|
+
return `\t\t${f.name} = buffer.readf32(store.${f.name}, (i - 1) * 4),`;
|
|
102
|
+
}
|
|
103
|
+
if (useBuffer && f.kind === "i32") {
|
|
104
|
+
return `\t\t${f.name} = buffer.readi32(store.${f.name}, (i - 1) * 4),`;
|
|
105
|
+
}
|
|
106
|
+
return `\t\t${f.name} = store.${f.name}[i],`;
|
|
107
|
+
})
|
|
108
|
+
.join("\n");
|
|
109
|
+
const ensureBuf = useBuffer
|
|
110
|
+
? fields
|
|
111
|
+
.filter((f) => f.kind === "f32" || f.kind === "i32")
|
|
112
|
+
.map((f) => `
|
|
113
|
+
local need = capacity * 4
|
|
114
|
+
if buffer.len(store.${f.name}) < need then
|
|
115
|
+
local nb = buffer.create(need)
|
|
116
|
+
buffer.copy(nb, 0, store.${f.name})
|
|
117
|
+
store.${f.name} = nb
|
|
118
|
+
end`)
|
|
119
|
+
.join("\n")
|
|
120
|
+
: "";
|
|
121
|
+
return `--!native
|
|
122
|
+
--!optimize 2
|
|
123
|
+
--!strict
|
|
124
|
+
-- cluaupp generated SoA for ${structName} — do not edit by hand.
|
|
125
|
+
-- Use via: local ${structName}Soa = require(...${structName}Soa)
|
|
126
|
+
|
|
127
|
+
export type ${structName}Row = {
|
|
128
|
+
${fields.map((f) => `\t${f.name}: ${f.kind === "f32" ? "number" : f.kind === "i32" ? "number" : f.kind === "bool" ? "boolean" : "Vector3"},`).join("\n")}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type ${structName}Store = {
|
|
132
|
+
len: number,
|
|
133
|
+
capacity: number,
|
|
134
|
+
${fields.map((f) => `\t${f.name}: ${useBuffer && (f.kind === "f32" || f.kind === "i32") ? "buffer" : "{}"},`).join("\n")}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
local ${structName}Soa = {}
|
|
138
|
+
|
|
139
|
+
function ${structName}Soa.new(capacity: number): ${structName}Store
|
|
140
|
+
capacity = math.max(1, capacity or 64)
|
|
141
|
+
local store = {
|
|
142
|
+
len = 0,
|
|
143
|
+
capacity = capacity,
|
|
144
|
+
${cols}
|
|
145
|
+
}
|
|
146
|
+
return store
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
function ${structName}Soa.ensure(store: ${structName}Store, capacity: number)
|
|
150
|
+
if capacity <= store.capacity then
|
|
151
|
+
return
|
|
152
|
+
end
|
|
153
|
+
store.capacity = capacity
|
|
154
|
+
${ensureBuf}
|
|
155
|
+
${useBuffer ? "" : fields.map((f) => `\t-- table.create columns grow on index assign`).join("\n")}
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
function ${structName}Soa.push(store: ${structName}Store, row: ${structName}Row): number
|
|
159
|
+
local i = store.len + 1
|
|
160
|
+
${structName}Soa.ensure(store, i)
|
|
161
|
+
store.len = i
|
|
162
|
+
${pushLines}
|
|
163
|
+
return i
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
function ${structName}Soa.get(store: ${structName}Store, i: number): ${structName}Row
|
|
167
|
+
return {
|
|
168
|
+
${getLines}
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
function ${structName}Soa.set(store: ${structName}Store, i: number, row: ${structName}Row)
|
|
173
|
+
${pushLines}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
return ${structName}Soa
|
|
177
|
+
`;
|
|
178
|
+
}
|
|
179
|
+
/** Insert `#pragma layout soa Name` before advised storage structs. */
|
|
180
|
+
function applyLayoutPragmas(source, fileName, profile = null) {
|
|
181
|
+
const advice = (0, pgo_js_1.rankAdvice)((0, optimizer_advise_js_1.adviseOptimizer)(source, fileName), profile);
|
|
182
|
+
const targets = new Set(advice.filter((a) => a.kind === "storage").map((a) => a.target));
|
|
183
|
+
const applied = [];
|
|
184
|
+
const plans = [];
|
|
185
|
+
let out = source;
|
|
186
|
+
for (const name of targets) {
|
|
187
|
+
if (new RegExp(`#pragma\\s+layout\\s+soa\\s+${name}\\b`).test(out)) {
|
|
188
|
+
const plan = planSoa(out, name);
|
|
189
|
+
if (plan)
|
|
190
|
+
plans.push(plan);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const re = new RegExp(`((?:^|\\n)(?:\\s*))(struct\\s+${name}\\s*\\{)`, "m");
|
|
194
|
+
if (!re.test(out))
|
|
195
|
+
continue;
|
|
196
|
+
out = out.replace(re, `$1#pragma layout soa ${name}\n$1$2`);
|
|
197
|
+
applied.push(`#pragma layout soa → ${name}`);
|
|
198
|
+
const plan = planSoa(out, name);
|
|
199
|
+
if (plan)
|
|
200
|
+
plans.push(plan);
|
|
201
|
+
}
|
|
202
|
+
for (const name of listLayoutTargets(out)) {
|
|
203
|
+
if (plans.some((p) => p.structName === name))
|
|
204
|
+
continue;
|
|
205
|
+
const plan = planSoa(out, name);
|
|
206
|
+
if (plan)
|
|
207
|
+
plans.push(plan);
|
|
208
|
+
}
|
|
209
|
+
return { source: out, changed: applied.length > 0, applied, plans };
|
|
210
|
+
}
|
|
211
|
+
function writeSoaArtifacts(projectRoot, plans, opts = {}) {
|
|
212
|
+
if (!plans.length)
|
|
213
|
+
return [];
|
|
214
|
+
const outDir = node_path_1.default.join(projectRoot, ".cluaupp", "generated", "soa");
|
|
215
|
+
node_fs_1.default.mkdirSync(outDir, { recursive: true });
|
|
216
|
+
const written = [];
|
|
217
|
+
for (const plan of plans) {
|
|
218
|
+
const file = node_path_1.default.join(outDir, `${plan.structName}Soa.luau`);
|
|
219
|
+
node_fs_1.default.writeFileSync(file, generateSoaLuau(plan, opts.useBuffer === true), "utf8");
|
|
220
|
+
written.push(file);
|
|
221
|
+
}
|
|
222
|
+
return written;
|
|
223
|
+
}
|
|
224
|
+
/** Collect SoA plans from all sources that already have layout pragmas. */
|
|
225
|
+
function collectSoaPlansFromProject(projectRoot, rootDir = "src") {
|
|
226
|
+
const srcDir = node_path_1.default.join(projectRoot, rootDir);
|
|
227
|
+
if (!node_fs_1.default.existsSync(srcDir))
|
|
228
|
+
return [];
|
|
229
|
+
const plans = [];
|
|
230
|
+
const seen = new Set();
|
|
231
|
+
const walk = (dir) => {
|
|
232
|
+
for (const entry of node_fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
233
|
+
const full = node_path_1.default.join(dir, entry.name);
|
|
234
|
+
if (entry.isDirectory()) {
|
|
235
|
+
walk(full);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
if (!/\.(clpp|clp|clh)$/i.test(entry.name))
|
|
239
|
+
continue;
|
|
240
|
+
const source = node_fs_1.default.readFileSync(full, "utf8");
|
|
241
|
+
for (const name of listLayoutTargets(source)) {
|
|
242
|
+
if (seen.has(name))
|
|
243
|
+
continue;
|
|
244
|
+
const plan = planSoa(source, name);
|
|
245
|
+
if (plan) {
|
|
246
|
+
seen.add(name);
|
|
247
|
+
plans.push(plan);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
walk(srcDir);
|
|
253
|
+
return plans;
|
|
254
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Parallel Luau / ThreadSafety diagnostics (CLUAU-PAR*).
|
|
4
|
+
* Flags Unsafe mutations inside parallel { } / desynchronize regions.
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.checkParallelSafety = checkParallelSafety;
|
|
11
|
+
exports.assertParallelSafe = assertParallelSafe;
|
|
12
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const package_info_js_1 = require("../package-info.js");
|
|
15
|
+
const diagnostics_js_1 = require("./diagnostics.js");
|
|
16
|
+
let cachedUnsafeMembers = null;
|
|
17
|
+
function loadUnsafeMemberNames() {
|
|
18
|
+
if (cachedUnsafeMembers) {
|
|
19
|
+
return cachedUnsafeMembers;
|
|
20
|
+
}
|
|
21
|
+
const set = new Set(["Parent", "Name"]); // Parent writes always unsafe in parallel
|
|
22
|
+
const profilePath = node_path_1.default.join(package_info_js_1.projectRoot, "api", "normalized", "roblox-target.profile.json");
|
|
23
|
+
if (node_fs_1.default.existsSync(profilePath)) {
|
|
24
|
+
try {
|
|
25
|
+
const profile = JSON.parse(node_fs_1.default.readFileSync(profilePath, "utf8"));
|
|
26
|
+
for (const cls of Object.values(profile.classes || {})) {
|
|
27
|
+
for (const prop of Object.values(cls.properties || {})) {
|
|
28
|
+
if (prop.threadSafety === "Unsafe") {
|
|
29
|
+
set.add(prop.name);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (const method of Object.values(cls.methods || {})) {
|
|
33
|
+
if (method.threadSafety === "Unsafe") {
|
|
34
|
+
set.add(method.name);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* fallback set only */
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
cachedUnsafeMembers = set;
|
|
44
|
+
return set;
|
|
45
|
+
}
|
|
46
|
+
function extractParallelRegions(source) {
|
|
47
|
+
const regions = [];
|
|
48
|
+
const reParallel = /\bparallel\s*\{/g;
|
|
49
|
+
let m;
|
|
50
|
+
while ((m = reParallel.exec(source))) {
|
|
51
|
+
const open = m.index + m[0].length - 1;
|
|
52
|
+
let depth = 0;
|
|
53
|
+
for (let i = open; i < source.length; i++) {
|
|
54
|
+
if (source[i] === "{")
|
|
55
|
+
depth++;
|
|
56
|
+
else if (source[i] === "}") {
|
|
57
|
+
depth--;
|
|
58
|
+
if (depth === 0) {
|
|
59
|
+
regions.push({ start: m.index, body: source.slice(open + 1, i) });
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const reDesync = /task\s*::\s*desynchronize\s*\(\s*\)/g;
|
|
66
|
+
while ((m = reDesync.exec(source))) {
|
|
67
|
+
const rest = source.slice(m.index);
|
|
68
|
+
const sync = rest.search(/task\s*::\s*synchronize\s*\(\s*\)/);
|
|
69
|
+
const body = sync >= 0 ? rest.slice(0, sync) : rest.slice(0, Math.min(rest.length, 800));
|
|
70
|
+
regions.push({ start: m.index, body });
|
|
71
|
+
}
|
|
72
|
+
if (/\bConnectParallel\b/.test(source)) {
|
|
73
|
+
regions.push({ start: source.indexOf("ConnectParallel"), body: source });
|
|
74
|
+
}
|
|
75
|
+
return regions;
|
|
76
|
+
}
|
|
77
|
+
function checkParallelSafety(source, fileName = "input.clpp") {
|
|
78
|
+
const clean = (0, diagnostics_js_1.stripCommentsKeepStrings)(source);
|
|
79
|
+
const unsafe = loadUnsafeMemberNames();
|
|
80
|
+
const out = [];
|
|
81
|
+
const regions = extractParallelRegions(clean);
|
|
82
|
+
if (!regions.length) {
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
for (const region of regions) {
|
|
86
|
+
const parentAssign = /\.Parent\s*=/g;
|
|
87
|
+
let pm;
|
|
88
|
+
while ((pm = parentAssign.exec(region.body))) {
|
|
89
|
+
const { line, column } = (0, diagnostics_js_1.lineColAt)(clean, region.start + pm.index);
|
|
90
|
+
out.push({
|
|
91
|
+
code: "CLUAU_PAR001",
|
|
92
|
+
message: "Unsafe Instance write `.Parent =` inside parallel / desynchronized region",
|
|
93
|
+
line,
|
|
94
|
+
column,
|
|
95
|
+
severity: "error",
|
|
96
|
+
file: fileName,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const memberWrite = /\.([A-Za-z_]\w*)\s*=/g;
|
|
100
|
+
while ((pm = memberWrite.exec(region.body))) {
|
|
101
|
+
const name = pm[1];
|
|
102
|
+
if (name === "Parent")
|
|
103
|
+
continue;
|
|
104
|
+
if (!unsafe.has(name))
|
|
105
|
+
continue;
|
|
106
|
+
const { line, column } = (0, diagnostics_js_1.lineColAt)(clean, region.start + pm.index);
|
|
107
|
+
out.push({
|
|
108
|
+
code: "CLUAU_PAR002",
|
|
109
|
+
message: `API member \`.${name}\` is ThreadSafety=Unsafe inside parallel region`,
|
|
110
|
+
line,
|
|
111
|
+
column,
|
|
112
|
+
severity: "error",
|
|
113
|
+
file: fileName,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const destroy = /\.Destroy\s*\(/g;
|
|
117
|
+
while ((pm = destroy.exec(region.body))) {
|
|
118
|
+
const { line, column } = (0, diagnostics_js_1.lineColAt)(clean, region.start + pm.index);
|
|
119
|
+
out.push({
|
|
120
|
+
code: "CLUAU_PAR003",
|
|
121
|
+
message: "`Destroy()` is Unsafe inside parallel region",
|
|
122
|
+
line,
|
|
123
|
+
column,
|
|
124
|
+
severity: "error",
|
|
125
|
+
file: fileName,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function assertParallelSafe(source, fileName) {
|
|
132
|
+
const errors = checkParallelSafety(source, fileName).filter((d) => d.severity === "error");
|
|
133
|
+
if (!errors.length)
|
|
134
|
+
return;
|
|
135
|
+
throw new Error(`Parallel safety:\n${errors.map((e) => ` ${e.line}:${e.column} ${e.code} ${e.message}`).join("\n")}`);
|
|
136
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Profile-Guided Optimization (PGO) — format, load, Studio ingest.
|
|
4
|
+
* cluaupp.profile.json weights optimizer advice toward hot symbols.
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.PGO_SCHEMA_VERSION = void 0;
|
|
11
|
+
exports.defaultProfilePath = defaultProfilePath;
|
|
12
|
+
exports.loadProfile = loadProfile;
|
|
13
|
+
exports.writeProfileTemplate = writeProfileTemplate;
|
|
14
|
+
exports.ingestProfile = ingestProfile;
|
|
15
|
+
exports.rankAdvice = rankAdvice;
|
|
16
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
exports.PGO_SCHEMA_VERSION = 1;
|
|
19
|
+
function defaultProfilePath(projectRoot) {
|
|
20
|
+
return node_path_1.default.join(projectRoot, "cluaupp.profile.json");
|
|
21
|
+
}
|
|
22
|
+
function loadProfile(file) {
|
|
23
|
+
if (!node_fs_1.default.existsSync(file)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
|
|
28
|
+
if (!raw || typeof raw.hot !== "object") {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return { schemaVersion: exports.PGO_SCHEMA_VERSION, hot: raw.hot, structs: raw.structs, generatedAt: raw.generatedAt };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Write an empty / template profile for games to fill from Studio logging. */
|
|
38
|
+
function writeProfileTemplate(projectRoot) {
|
|
39
|
+
const file = defaultProfilePath(projectRoot);
|
|
40
|
+
const template = {
|
|
41
|
+
schemaVersion: exports.PGO_SCHEMA_VERSION,
|
|
42
|
+
generatedAt: new Date().toISOString(),
|
|
43
|
+
hot: {
|
|
44
|
+
CalculateDamage: 0,
|
|
45
|
+
EnemyUpdate: 0,
|
|
46
|
+
},
|
|
47
|
+
structs: {
|
|
48
|
+
EnemyState: 0,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
node_fs_1.default.writeFileSync(file, JSON.stringify(template, null, "\t") + "\n", "utf8");
|
|
52
|
+
return file;
|
|
53
|
+
}
|
|
54
|
+
function parseEvents(raw) {
|
|
55
|
+
const trimmed = raw.trim();
|
|
56
|
+
if (!trimmed)
|
|
57
|
+
return [];
|
|
58
|
+
if (trimmed.startsWith("[")) {
|
|
59
|
+
const arr = JSON.parse(trimmed);
|
|
60
|
+
if (!Array.isArray(arr))
|
|
61
|
+
return [];
|
|
62
|
+
return arr.filter((e) => e && typeof e.symbol === "string");
|
|
63
|
+
}
|
|
64
|
+
if (trimmed.startsWith("{") && !trimmed.includes("\n")) {
|
|
65
|
+
const one = JSON.parse(trimmed);
|
|
66
|
+
if (typeof one.symbol === "string") {
|
|
67
|
+
return [one];
|
|
68
|
+
}
|
|
69
|
+
if (one.hot && typeof one.hot === "object") {
|
|
70
|
+
return Object.entries(one.hot).map(([symbol, count]) => ({
|
|
71
|
+
symbol,
|
|
72
|
+
kind: "hot",
|
|
73
|
+
count: Number(count) || 0,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const events = [];
|
|
78
|
+
for (const line of trimmed.split(/\r?\n/)) {
|
|
79
|
+
const t = line.trim();
|
|
80
|
+
if (!t || t.startsWith("#"))
|
|
81
|
+
continue;
|
|
82
|
+
try {
|
|
83
|
+
const e = JSON.parse(t);
|
|
84
|
+
if (e && typeof e.symbol === "string")
|
|
85
|
+
events.push(e);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
/* skip bad line */
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return events;
|
|
92
|
+
}
|
|
93
|
+
/** Merge Studio/playtest events into cluaupp.profile.json (additive counts). */
|
|
94
|
+
function ingestProfile(projectRoot, eventsPath) {
|
|
95
|
+
const eventsFile = node_path_1.default.resolve(eventsPath);
|
|
96
|
+
if (!node_fs_1.default.existsSync(eventsFile)) {
|
|
97
|
+
throw new Error(`cluaupp profile ingest: missing ${eventsFile}`);
|
|
98
|
+
}
|
|
99
|
+
const events = parseEvents(node_fs_1.default.readFileSync(eventsFile, "utf8"));
|
|
100
|
+
const profilePath = defaultProfilePath(projectRoot);
|
|
101
|
+
const existing = loadProfile(profilePath) || {
|
|
102
|
+
schemaVersion: exports.PGO_SCHEMA_VERSION,
|
|
103
|
+
hot: {},
|
|
104
|
+
structs: {},
|
|
105
|
+
};
|
|
106
|
+
const hot = { ...(existing.hot || {}) };
|
|
107
|
+
const structs = { ...(existing.structs || {}) };
|
|
108
|
+
let merged = 0;
|
|
109
|
+
for (const e of events) {
|
|
110
|
+
const n = typeof e.count === "number" && Number.isFinite(e.count) ? e.count : 1;
|
|
111
|
+
if (e.kind === "struct") {
|
|
112
|
+
structs[e.symbol] = (structs[e.symbol] || 0) + n;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
hot[e.symbol] = (hot[e.symbol] || 0) + n;
|
|
116
|
+
}
|
|
117
|
+
merged++;
|
|
118
|
+
}
|
|
119
|
+
const profile = {
|
|
120
|
+
schemaVersion: exports.PGO_SCHEMA_VERSION,
|
|
121
|
+
generatedAt: new Date().toISOString(),
|
|
122
|
+
hot,
|
|
123
|
+
structs,
|
|
124
|
+
};
|
|
125
|
+
node_fs_1.default.writeFileSync(profilePath, JSON.stringify(profile, null, "\t") + "\n", "utf8");
|
|
126
|
+
return { profilePath, profile, merged };
|
|
127
|
+
}
|
|
128
|
+
function rankAdvice(advice, profile) {
|
|
129
|
+
if (!profile) {
|
|
130
|
+
return advice;
|
|
131
|
+
}
|
|
132
|
+
return [...advice]
|
|
133
|
+
.sort((a, b) => {
|
|
134
|
+
const wa = profile.hot[a.target] || profile.structs?.[a.target] || 0;
|
|
135
|
+
const wb = profile.hot[b.target] || profile.structs?.[b.target] || 0;
|
|
136
|
+
return wb - wa;
|
|
137
|
+
})
|
|
138
|
+
.map((a) => {
|
|
139
|
+
const w = profile.hot[a.target] || profile.structs?.[a.target];
|
|
140
|
+
if (!w)
|
|
141
|
+
return a;
|
|
142
|
+
return {
|
|
143
|
+
...a,
|
|
144
|
+
message: `${a.message} [PGO weight=${w}]`,
|
|
145
|
+
severity: w > 1000 ? "warning" : a.severity,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|