rbxts-transform-boost 0.1.0 → 1.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.
@@ -1,127 +0,0 @@
1
- // ── Vector3 / physics ────────────────────────────────────────────────────────
2
-
3
- export function integrate(pos: Vector3, vel: Vector3, acc: Vector3, dt: number): [Vector3, Vector3] {
4
- const newVel = vel.add(acc.mul(dt));
5
- const newPos = pos.add(newVel.mul(dt));
6
- return [newPos, newVel];
7
- }
8
-
9
- export function dot(a: Vector3, b: Vector3): number {
10
- return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
11
- }
12
-
13
- export function cross(a: Vector3, b: Vector3): Vector3 {
14
- return new Vector3(
15
- a.Y * b.Z - a.Z * b.Y,
16
- a.Z * b.X - a.X * b.Z,
17
- a.X * b.Y - a.Y * b.X,
18
- );
19
- }
20
-
21
- export function lerpVec3(a: Vector3, b: Vector3, t: number): Vector3 {
22
- return new Vector3(
23
- a.X + (b.X - a.X) * t,
24
- a.Y + (b.Y - a.Y) * t,
25
- a.Z + (b.Z - a.Z) * t,
26
- );
27
- }
28
-
29
- // ── Buffer / encoding ────────────────────────────────────────────────────────
30
-
31
- export function encodeFixed(buf: buffer, offset: number, value: number, scale: number): number {
32
- const fixed = math.floor(value * scale);
33
- const clamped = math.clamp(fixed, -32768, 32767);
34
- buffer.writei16(buf, offset, clamped);
35
- return offset + 2;
36
- }
37
-
38
- export function encodePacket(buf: buffer, x: number, y: number, z: number, scale: number): void {
39
- let off = 0;
40
- off = encodeFixed(buf, off, x, scale);
41
- off = encodeFixed(buf, off, y, scale);
42
- encodeFixed(buf, off, z, scale);
43
- }
44
-
45
- // ── Math / arithmetic ────────────────────────────────────────────────────────
46
-
47
- export function sumWeighted(values: Array<number>, weights: Array<number>): number {
48
- let total = 0;
49
- for (let i = 0; i < values.size(); i++) {
50
- total += values[i] * weights[i];
51
- }
52
- return total;
53
- }
54
-
55
- export function dotProduct(a: Array<number>, b: Array<number>): number {
56
- let sum = 0;
57
- for (let i = 0; i < a.size(); i++) {
58
- sum += a[i] * b[i];
59
- }
60
- return sum;
61
- }
62
-
63
- export function norm(values: Array<number>): number {
64
- let sq = 0;
65
- for (let i = 0; i < values.size(); i++) {
66
- sq += values[i] * values[i];
67
- }
68
- return math.sqrt(sq);
69
- }
70
-
71
- export function mathHeavy(x: number, y: number): number {
72
- return math.sin(x) * math.cos(y) + math.sqrt(x * x + y * y) + math.atan2(y, x);
73
- }
74
-
75
- export function fib(n: number): number {
76
- if (n <= 1) return n;
77
- let a = 0;
78
- let b = 1;
79
- for (let i = 2; i <= n; i++) {
80
- const tmp = a + b;
81
- a = b;
82
- b = tmp;
83
- }
84
- return b;
85
- }
86
-
87
- // ── CFrame ───────────────────────────────────────────────────────────────────
88
-
89
- export function cfLookAt(eye: Vector3, target: Vector3): CFrame {
90
- return CFrame.lookAt(eye, target);
91
- }
92
-
93
- export function cfChain(cf: CFrame, dt: number): CFrame {
94
- return cf.mul(CFrame.Angles(0, dt, 0));
95
- }
96
-
97
- // ── Services / instance ──────────────────────────────────────────────────────
98
-
99
- export function serviceWork(): string {
100
- const count = game.GetService("Players").GetPlayers().size();
101
- const running = game.GetService("RunService").IsRunning();
102
- return `${count}-${running}`;
103
- }
104
-
105
- export function multiService(): boolean {
106
- const players = game.GetService("Players");
107
- const rs = game.GetService("RunService");
108
- const ws = game.GetService("Workspace");
109
- return rs.IsRunning() && players.MaxPlayers > 0 && ws.Gravity > 0;
110
- }
111
-
112
- export function cameraWork(camera: Camera): number {
113
- const pos = camera.CFrame.Position;
114
- const look = camera.CFrame.LookVector;
115
- const fov = camera.FieldOfView;
116
- return pos.Magnitude + look.X + fov;
117
- }
118
-
119
- // ── String ───────────────────────────────────────────────────────────────────
120
-
121
- export function formatStats(label: string, value: number, unit: string): string {
122
- return `${label}: ${string.format("%.4f", value)} ${unit}`;
123
- }
124
-
125
- export function buildKey(prefix: string, id: number, suffix: string): string {
126
- return `${prefix}_${id}_${suffix}`;
127
- }
@@ -1,127 +0,0 @@
1
- // ── Vector3 / physics ────────────────────────────────────────────────────────
2
-
3
- export function integrate(pos: Vector3, vel: Vector3, acc: Vector3, dt: number): [Vector3, Vector3] {
4
- const newVel = vel.add(acc.mul(dt));
5
- const newPos = pos.add(newVel.mul(dt));
6
- return [newPos, newVel];
7
- }
8
-
9
- export function dot(a: Vector3, b: Vector3): number {
10
- return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
11
- }
12
-
13
- export function cross(a: Vector3, b: Vector3): Vector3 {
14
- return new Vector3(
15
- a.Y * b.Z - a.Z * b.Y,
16
- a.Z * b.X - a.X * b.Z,
17
- a.X * b.Y - a.Y * b.X,
18
- );
19
- }
20
-
21
- export function lerpVec3(a: Vector3, b: Vector3, t: number): Vector3 {
22
- return new Vector3(
23
- a.X + (b.X - a.X) * t,
24
- a.Y + (b.Y - a.Y) * t,
25
- a.Z + (b.Z - a.Z) * t,
26
- );
27
- }
28
-
29
- // ── Buffer / encoding ────────────────────────────────────────────────────────
30
-
31
- export function encodeFixed(buf: buffer, offset: number, value: number, scale: number): number {
32
- const fixed = math.floor(value * scale);
33
- const clamped = math.clamp(fixed, -32768, 32767);
34
- buffer.writei16(buf, offset, clamped);
35
- return offset + 2;
36
- }
37
-
38
- export function encodePacket(buf: buffer, x: number, y: number, z: number, scale: number): void {
39
- let off = 0;
40
- off = encodeFixed(buf, off, x, scale);
41
- off = encodeFixed(buf, off, y, scale);
42
- encodeFixed(buf, off, z, scale);
43
- }
44
-
45
- // ── Math / arithmetic ────────────────────────────────────────────────────────
46
-
47
- export function sumWeighted(values: Array<number>, weights: Array<number>): number {
48
- let total = 0;
49
- for (let i = 0; i < values.size(); i++) {
50
- total += values[i] * weights[i];
51
- }
52
- return total;
53
- }
54
-
55
- export function dotProduct(a: Array<number>, b: Array<number>): number {
56
- let sum = 0;
57
- for (let i = 0; i < a.size(); i++) {
58
- sum += a[i] * b[i];
59
- }
60
- return sum;
61
- }
62
-
63
- export function norm(values: Array<number>): number {
64
- let sq = 0;
65
- for (let i = 0; i < values.size(); i++) {
66
- sq += values[i] * values[i];
67
- }
68
- return math.sqrt(sq);
69
- }
70
-
71
- export function mathHeavy(x: number, y: number): number {
72
- return math.sin(x) * math.cos(y) + math.sqrt(x * x + y * y) + math.atan2(y, x);
73
- }
74
-
75
- export function fib(n: number): number {
76
- if (n <= 1) return n;
77
- let a = 0;
78
- let b = 1;
79
- for (let i = 2; i <= n; i++) {
80
- const tmp = a + b;
81
- a = b;
82
- b = tmp;
83
- }
84
- return b;
85
- }
86
-
87
- // ── CFrame ───────────────────────────────────────────────────────────────────
88
-
89
- export function cfLookAt(eye: Vector3, target: Vector3): CFrame {
90
- return CFrame.lookAt(eye, target);
91
- }
92
-
93
- export function cfChain(cf: CFrame, dt: number): CFrame {
94
- return cf.mul(CFrame.Angles(0, dt, 0));
95
- }
96
-
97
- // ── Services / instance ──────────────────────────────────────────────────────
98
-
99
- export function serviceWork(): string {
100
- const count = game.GetService("Players").GetPlayers().size();
101
- const running = game.GetService("RunService").IsRunning();
102
- return `${count}-${running}`;
103
- }
104
-
105
- export function multiService(): boolean {
106
- const players = game.GetService("Players");
107
- const rs = game.GetService("RunService");
108
- const ws = game.GetService("Workspace");
109
- return rs.IsRunning() && players.MaxPlayers > 0 && ws.Gravity > 0;
110
- }
111
-
112
- export function cameraWork(camera: Camera): number {
113
- const pos = camera.CFrame.Position;
114
- const look = camera.CFrame.LookVector;
115
- const fov = camera.FieldOfView;
116
- return pos.Magnitude + look.X + fov;
117
- }
118
-
119
- // ── String ───────────────────────────────────────────────────────────────────
120
-
121
- export function formatStats(label: string, value: number, unit: string): string {
122
- return `${label}: ${string.format("%.4f", value)} ${unit}`;
123
- }
124
-
125
- export function buildKey(prefix: string, id: number, suffix: string): string {
126
- return `${prefix}_${id}_${suffix}`;
127
- }
@@ -1,32 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "commonjs",
5
- "moduleDetection": "force",
6
- "moduleResolution": "Node",
7
- "strict": true,
8
- "noLib": true,
9
- "esModuleInterop": true,
10
- "allowSyntheticDefaultImports": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "rootDir": "./src",
13
- "outDir": "./out/src",
14
- "typeRoots": [
15
- "./node_modules/@rbxts"
16
- ],
17
- "plugins": [
18
- {
19
- "transform": "rbxts-transform-boost",
20
- "optimize": true,
21
- "hoist": true
22
- }
23
- ]
24
- },
25
- "include": [
26
- "./src/server",
27
- "./src/shared/fns.ts"
28
- ],
29
- "rbxts": {
30
- "includePath": "out/include"
31
- }
32
- }
@@ -1,24 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ESNext",
4
- "module": "commonjs",
5
- "moduleDetection": "force",
6
- "moduleResolution": "Node",
7
- "strict": true,
8
- "noLib": true,
9
- "esModuleInterop": true,
10
- "allowSyntheticDefaultImports": true,
11
- "forceConsistentCasingInFileNames": true,
12
- "rootDir": "./src",
13
- "outDir": "./out/src",
14
- "typeRoots": [
15
- "./node_modules/@rbxts"
16
- ],
17
- },
18
- "include": [
19
- "./src/shared/fns-bare.ts"
20
- ],
21
- "rbxts": {
22
- "includePath": "out/include"
23
- }
24
- }
package/rokit.toml DELETED
@@ -1,3 +0,0 @@
1
- [tools]
2
- rojo = "rojo-rbx/rojo@7.6.1"
3
- rotor = "uproot/rotor@2.2.0"
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env node
2
- // Replaces "-- !fn-native\n" markers (emitted by rbxts-transform-perf's native pass)
3
- // with bare "@native\n" Luau attributes in compiled output files.
4
- // Run after `rotor build` to make per-function @native actually take effect.
5
-
6
- const fs = require("fs");
7
- const path = require("path");
8
-
9
- const MARKER = "--!fn-native\n";
10
- const ATTRIBUTE = "@native\n";
11
-
12
- function processDir(dir) {
13
- if (!fs.existsSync(dir)) return;
14
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
15
- const full = path.join(dir, entry.name);
16
- if (entry.isDirectory()) {
17
- processDir(full);
18
- } else if (entry.name.endsWith(".luau") || entry.name.endsWith(".lua")) {
19
- const original = fs.readFileSync(full, "utf8");
20
- if (!original.includes(MARKER)) continue;
21
- const patched = original.replaceAll(MARKER, ATTRIBUTE);
22
- fs.writeFileSync(full, patched, "utf8");
23
- console.log(`@native patched: ${full}`);
24
- }
25
- }
26
- }
27
-
28
- const outDir = process.argv[2] ?? path.join(__dirname, "..", "test", "out");
29
- processDir(outDir);
package/src/config.ts DELETED
@@ -1,10 +0,0 @@
1
- export interface PluginConfig {
2
- // Prepend --!optimize 2 to every file that doesn't already have it.
3
- // Default: true
4
- optimize?: boolean;
5
-
6
- // Hoist repeated game.GetService() calls to module-level locals,
7
- // and hoist repeated property access chains within functions to locals.
8
- // Default: true
9
- hoist?: boolean;
10
- }
package/src/index.ts DELETED
@@ -1,25 +0,0 @@
1
- import type ts from "typescript";
2
- import type { PluginConfig } from "./config";
3
- import { nativePass } from "./passes/native";
4
- import { cachePass } from "./passes/cache";
5
- import { loopsPass } from "./passes/loops";
6
- import { annotatePass } from "./passes/annotate";
7
-
8
- export type { PluginConfig };
9
-
10
- export default function(
11
- program: ts.Program,
12
- config: PluginConfig = {},
13
- { ts }: { ts: typeof import("typescript") },
14
- ): ts.TransformerFactory<ts.SourceFile> {
15
- const { optimize = true, hoist = true } = config;
16
-
17
- return (ctx: ts.TransformationContext) => (sourceFile: ts.SourceFile): ts.SourceFile => {
18
- annotatePass(ts, program, sourceFile);
19
- let result = sourceFile;
20
- if (hoist) result = cachePass(ts, program, ctx, result);
21
- result = loopsPass(ts, program, ctx, result);
22
- if (optimize) result = nativePass(ts, ctx, result);
23
- return result;
24
- };
25
- }
@@ -1,207 +0,0 @@
1
- import type ts from "typescript";
2
- import * as fs from "fs";
3
- import * as path from "path";
4
-
5
- // Luau type names for TypeScript type strings rotor knows about
6
- const LUAU_TYPE: Record<string, string> = {
7
- number: "number",
8
- string: "string",
9
- boolean: "boolean",
10
- Vector3: "Vector3",
11
- Vector2: "Vector2",
12
- Vector2int16: "Vector2int16",
13
- Vector3int16: "Vector3int16",
14
- CFrame: "CFrame",
15
- UDim: "UDim",
16
- UDim2: "UDim2",
17
- Color3: "Color3",
18
- BrickColor: "BrickColor",
19
- TweenInfo: "TweenInfo",
20
- NumberRange: "NumberRange",
21
- NumberSequence: "NumberSequence",
22
- ColorSequence: "ColorSequence",
23
- Rect: "Rect",
24
- Region3: "Region3",
25
- Ray: "Ray",
26
- buffer: "buffer",
27
- // Roblox service types
28
- Instance: "Instance",
29
- BasePart: "BasePart",
30
- Part: "Part",
31
- Model: "Model",
32
- Player: "Player",
33
- Camera: "Camera",
34
- Workspace: "Workspace",
35
- RunService: "RunService",
36
- Players: "Players",
37
- // Luau numeric arrays — kept as {number} in Luau
38
- };
39
-
40
- type FnAnnotation = {
41
- params: Array<string | null>; // null = unknown/skip
42
- };
43
-
44
- // Global sidecar: outLuauPath → list of function annotations for that file
45
- const sidecar = new Map<string, Map<string, FnAnnotation>>();
46
- let hooked = false;
47
-
48
- function luauTypeForTsType(
49
- ts: typeof import("typescript"),
50
- checker: ts.TypeChecker,
51
- node: ts.ParameterDeclaration,
52
- ): string | null {
53
- if (node.type) {
54
- const mapped = mapTypeNode(ts, node.type);
55
- if (mapped) return mapped;
56
- }
57
- const type = checker.getTypeAtLocation(node);
58
- const name = checker.typeToString(type);
59
- return LUAU_TYPE[name] ?? null;
60
- }
61
-
62
- function mapTypeNode(ts: typeof import("typescript"), typeNode: ts.TypeNode): string | null {
63
- if (ts.isTypeReferenceNode(typeNode)) {
64
- const name = ts.isIdentifier(typeNode.typeName) ? typeNode.typeName.text : null;
65
- if (!name) return null;
66
- if (LUAU_TYPE[name]) return LUAU_TYPE[name];
67
- // Array<T> → {T}
68
- if ((name === "Array" || name === "ReadonlyArray") && typeNode.typeArguments?.length === 1) {
69
- const inner = mapTypeNode(ts, typeNode.typeArguments[0]);
70
- return inner ? `{${inner}}` : "{any}";
71
- }
72
- return null;
73
- }
74
- if (ts.isArrayTypeNode(typeNode)) {
75
- const inner = mapTypeNode(ts, typeNode.elementType);
76
- return inner ? `{${inner}}` : "{any}";
77
- }
78
- const kw: Partial<Record<number, string>> = {
79
- [ts.SyntaxKind.NumberKeyword]: "number",
80
- [ts.SyntaxKind.StringKeyword]: "string",
81
- [ts.SyntaxKind.BooleanKeyword]: "boolean",
82
- };
83
- if (typeNode.kind in kw) return kw[typeNode.kind]!;
84
- return null;
85
- }
86
-
87
- function outPathForSource(
88
- sourceFile: ts.SourceFile,
89
- program: ts.Program,
90
- ): string | null {
91
- const options = program.getCompilerOptions();
92
- const outDir = options.outDir;
93
- if (!outDir) return null;
94
-
95
- // Compute rootDir: explicit option or the common root of all source files
96
- const rootDir = options.rootDir
97
- ?? commonRoot(program.getRootFileNames());
98
- if (!rootDir) return null;
99
-
100
- const rel = path.relative(rootDir, sourceFile.fileName);
101
- if (rel.startsWith("..")) return null;
102
-
103
- // Change .ts / .tsx extension to .luau
104
- const luauRel = rel.replace(/\.tsx?$/, ".luau");
105
- return path.join(outDir, luauRel);
106
- }
107
-
108
- function commonRoot(files: readonly string[]): string | undefined {
109
- if (files.length === 0) return undefined;
110
- const parts = files[0].split(path.sep);
111
- let root = parts.slice(0, parts.length - 1);
112
- for (const f of files.slice(1)) {
113
- const fp = f.split(path.sep);
114
- let i = 0;
115
- while (i < root.length && i < fp.length - 1 && root[i] === fp[i]) i++;
116
- root = root.slice(0, i);
117
- }
118
- return root.join(path.sep) || undefined;
119
- }
120
-
121
- function collectAnnotations(
122
- ts: typeof import("typescript"),
123
- checker: ts.TypeChecker,
124
- sourceFile: ts.SourceFile,
125
- outPath: string,
126
- ): void {
127
- const fileMap = sidecar.get(outPath) ?? new Map<string, FnAnnotation>();
128
- sidecar.set(outPath, fileMap);
129
-
130
- function visit(node: ts.Node): void {
131
- if (ts.isFunctionDeclaration(node) && node.name) {
132
- const fnName = node.name.text;
133
- const params = node.parameters.map(p => luauTypeForTsType(ts, checker, p));
134
- if (params.some(p => p !== null)) {
135
- fileMap.set(fnName, { params });
136
- }
137
- }
138
- ts.forEachChild(node, visit);
139
- }
140
- visit(sourceFile);
141
- }
142
-
143
- function injectAnnotations(luauPath: string, fileMap: Map<string, FnAnnotation>): void {
144
- if (!fs.existsSync(luauPath)) return;
145
- let src = fs.readFileSync(luauPath, "utf8");
146
- let changed = false;
147
-
148
- for (const [fnName, ann] of fileMap) {
149
- if (ann.params.every(p => p === null)) continue;
150
-
151
- // Match: local function fnName(a, b, c)
152
- // Captures the param list so we can replace individual names
153
- const re = new RegExp(
154
- `(local function ${escapeRegex(fnName)}\\()([^)]*)(\\.\\.\\.\\))?\\)`,
155
- );
156
- src = src.replace(re, (_match: string, open: string, rawParams: string, vararg: string | undefined) => {
157
- const names = rawParams.split(",").map((s: string) => s.trim()).filter(Boolean);
158
- const annotated = names.map((name: string, i: number) => {
159
- // strip any existing annotation
160
- const bare = name.split(":")[0].trim();
161
- const typ = ann.params[i];
162
- return typ ? `${bare}: ${typ}` : bare;
163
- });
164
- if (vararg) annotated.push("...");
165
- changed = true;
166
- return `${open}${annotated.join(", ")})`;
167
- });
168
- }
169
-
170
- if (changed) fs.writeFileSync(luauPath, src, "utf8");
171
- }
172
-
173
- function escapeRegex(s: string): string {
174
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
175
- }
176
-
177
- function installWatcher(outDir: string): void {
178
- if (hooked) return;
179
- hooked = true;
180
- const seen = new Set<string>();
181
- const watcher = fs.watch(outDir, { recursive: true }, (_event, filename) => {
182
- if (!filename || !filename.endsWith(".luau")) return;
183
- const full = path.join(outDir, filename);
184
- if (seen.has(full)) return;
185
- const fileMap = sidecar.get(full);
186
- if (!fileMap) return;
187
- seen.add(full);
188
- if (fileMap.size > 0) {
189
- try { injectAnnotations(full, fileMap); } catch { /* ignore */ }
190
- }
191
- });
192
- watcher.unref();
193
- }
194
-
195
- export function annotatePass(
196
- ts: typeof import("typescript"),
197
- program: ts.Program,
198
- sourceFile: ts.SourceFile,
199
- ): void {
200
- const outPath = outPathForSource(sourceFile, program);
201
- if (!outPath) return;
202
-
203
- const checker = program.getTypeChecker();
204
- collectAnnotations(ts, checker, sourceFile, outPath);
205
- const outDir = program.getCompilerOptions().outDir!;
206
- installWatcher(outDir);
207
- }