rbxts-transform-boost 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +26 -0
- package/README.md +323 -0
- package/bench/benchmark.project.json +20 -0
- package/bench/package-lock.json +59 -0
- package/bench/package.json +14 -0
- package/bench/src/server/benchmark.server.ts +57 -0
- package/bench/src/shared/fns-bare.ts +127 -0
- package/bench/src/shared/fns.ts +127 -0
- package/bench/tsconfig.json +32 -0
- package/bench/tsconfig.notransform.json +24 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +20 -0
- package/dist/passes/annotate.d.ts +2 -0
- package/dist/passes/annotate.js +222 -0
- package/dist/passes/cache.d.ts +2 -0
- package/dist/passes/cache.js +116 -0
- package/dist/passes/loops.d.ts +2 -0
- package/dist/passes/loops.js +55 -0
- package/dist/passes/native.d.ts +2 -0
- package/dist/passes/native.js +11 -0
- package/dist/util.d.ts +5 -0
- package/dist/util.js +39 -0
- package/package.json +29 -0
- package/rokit.toml +3 -0
- package/scripts/postprocess.js +29 -0
- package/src/config.ts +10 -0
- package/src/index.ts +25 -0
- package/src/passes/annotate.ts +207 -0
- package/src/passes/cache.ts +163 -0
- package/src/passes/loops.ts +80 -0
- package/src/passes/native.ts +19 -0
- package/src/util.ts +33 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 20
|
|
18
|
+
registry-url: https://registry.npmjs.org
|
|
19
|
+
|
|
20
|
+
- run: npm ci
|
|
21
|
+
|
|
22
|
+
- run: npm run build
|
|
23
|
+
|
|
24
|
+
- run: npm publish --access public
|
|
25
|
+
env:
|
|
26
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/README.md
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# rbxts-transform-boost
|
|
2
|
+
|
|
3
|
+
> **Successor to [`rbxts-transformer-luau-annotate`](https://github.com/Loner1536/rbxts-transformer-luau-annotate).** That package was accidentally removed from npm. Migrate to this one.
|
|
4
|
+
>
|
|
5
|
+
> **What carried over:** Luau type annotation injection on function parameters for native codegen (primitives, Roblox value types, arrays).
|
|
6
|
+
>
|
|
7
|
+
> **What's new:** `--!optimize 2` on every file, `game:GetService()` hoisting to module-level locals, repeated property chain hoisting, loop bounds hoisting — all without needing `//!native` in every file.
|
|
8
|
+
>
|
|
9
|
+
> **What's different:** The old package also annotated return types, local variable declarations, class methods, and user-defined interfaces/type aliases. Those are not yet in this package — they're planned but the implementation is non-trivial. The old package also had reliability issues that this rewrite addresses.
|
|
10
|
+
|
|
11
|
+
A roblox-ts transformer that automatically applies Luau performance directives at compile time — no runtime cost, no code changes required.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install --save-dev rbxts-transform-boost
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`tsconfig.json`:
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"compilerOptions": {
|
|
23
|
+
"plugins": [
|
|
24
|
+
{
|
|
25
|
+
"transform": "rbxts-transform-boost",
|
|
26
|
+
"optimize": true,
|
|
27
|
+
"hoist": true
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Options
|
|
35
|
+
|
|
36
|
+
| Option | Type | Default | Description |
|
|
37
|
+
|--------|------|---------|-------------|
|
|
38
|
+
| `optimize` | `boolean` | `true` | Prepend `--!optimize 2` to every file that doesn't already have it |
|
|
39
|
+
| `hoist` | `boolean` | `true` | Hoist `GetService` calls and repeated property reads to locals |
|
|
40
|
+
|
|
41
|
+
`--!native` is never auto-inserted. Add `//!native` at the top of your TypeScript file for hot paths you've profiled — rotor preserves it.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
//!native
|
|
45
|
+
export function integrate(pos: Vector3, vel: Vector3, acc: Vector3, dt: number) {
|
|
46
|
+
...
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## What it does
|
|
53
|
+
|
|
54
|
+
### `--!optimize 2` — always on top
|
|
55
|
+
|
|
56
|
+
Every file gets `--!optimize 2` prepended if it doesn't already have it. Roblox already runs all scripts at optimization level 2, but the directive makes Studio behaviour match production and signals intent.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// TypeScript source
|
|
60
|
+
export function encodeFixed(buf: buffer, offset: number, value: number, scale: number): number {
|
|
61
|
+
const fixed = math.floor(value * scale);
|
|
62
|
+
const clamped = math.clamp(fixed, -32768, 32767);
|
|
63
|
+
buffer.writei16(buf, offset, clamped);
|
|
64
|
+
return offset + 2;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```lua
|
|
69
|
+
-- Without transformer
|
|
70
|
+
-- Compiled with rotor v2.2.0
|
|
71
|
+
local function encodeFixed(buf, offset, value, scale)
|
|
72
|
+
local fixed = math.floor(value * scale)
|
|
73
|
+
local clamped = math.clamp(fixed, -32768, 32767)
|
|
74
|
+
buffer.writei16(buf, offset, clamped)
|
|
75
|
+
return offset + 2
|
|
76
|
+
end
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```lua
|
|
80
|
+
-- With transformer
|
|
81
|
+
--!optimize 2
|
|
82
|
+
-- Compiled with rotor v2.2.0
|
|
83
|
+
local function encodeFixed(buf: buffer, offset: number, value: number, scale: number)
|
|
84
|
+
local fixed = math.floor(value * scale)
|
|
85
|
+
local clamped = math.clamp(fixed, -32768, 32767)
|
|
86
|
+
buffer.writei16(buf, offset, clamped)
|
|
87
|
+
return offset + 2
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### GetService hoisting
|
|
94
|
+
|
|
95
|
+
Every `game:GetService("X")` call in a file is hoisted to a module-level local on first load. Functions that call `GetService` on every invocation — the most common pattern in rotor output — pay the registry lookup cost zero times at runtime.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// TypeScript source
|
|
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
|
+
|
|
106
|
+
```lua
|
|
107
|
+
-- Without transformer
|
|
108
|
+
local function serviceWork()
|
|
109
|
+
local count = #game:GetService("Players"):GetPlayers()
|
|
110
|
+
local running = game:GetService("RunService"):IsRunning()
|
|
111
|
+
return `{count}-{running}`
|
|
112
|
+
end
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```lua
|
|
116
|
+
-- With transformer
|
|
117
|
+
--!optimize 2
|
|
118
|
+
local _Players = game:GetService("Players") -- hoisted once at module load
|
|
119
|
+
local _RunService = game:GetService("RunService")
|
|
120
|
+
|
|
121
|
+
local function serviceWork()
|
|
122
|
+
local count = #_Players:GetPlayers()
|
|
123
|
+
local running = _RunService:IsRunning()
|
|
124
|
+
return `{count}-{running}`
|
|
125
|
+
end
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**2.4× faster** — `GetService` calls eliminated from the hot path entirely.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### Property chain hoisting
|
|
133
|
+
|
|
134
|
+
Any property access chain that appears **2 or more times** inside the same function is hoisted to a local. Instance property reads go through Roblox's C++ property system — doing the same read twice is wasted work.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// TypeScript source
|
|
138
|
+
export function cameraWork(camera: Camera): number {
|
|
139
|
+
const pos = camera.CFrame.Position;
|
|
140
|
+
const look = camera.CFrame.LookVector; // camera.CFrame read twice
|
|
141
|
+
const fov = camera.FieldOfView;
|
|
142
|
+
return pos.Magnitude + look.X + fov;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```lua
|
|
147
|
+
-- Without transformer
|
|
148
|
+
local function cameraWork(camera)
|
|
149
|
+
local pos = camera.CFrame.Position -- engine call #1
|
|
150
|
+
local look = camera.CFrame.LookVector -- engine call #2
|
|
151
|
+
local fov = camera.FieldOfView
|
|
152
|
+
return pos.Magnitude + look.X + fov
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```lua
|
|
157
|
+
-- With transformer
|
|
158
|
+
local function cameraWork(camera: Camera)
|
|
159
|
+
local _cache0 = camera.CFrame -- one engine call
|
|
160
|
+
local pos = _cache0.Position -- local read
|
|
161
|
+
local look = _cache0.LookVector -- local read
|
|
162
|
+
local fov = camera.FieldOfView
|
|
163
|
+
return pos.Magnitude + look.X + fov
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**1.5× faster.** Also hoists value type field reads (`.X`, `.Y`, `.Z`) when they appear multiple times — the `cross` function reads each component twice, so all six are hoisted:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// TypeScript source
|
|
171
|
+
export function cross(a: Vector3, b: Vector3): Vector3 {
|
|
172
|
+
return new Vector3(
|
|
173
|
+
a.Y * b.Z - a.Z * b.Y,
|
|
174
|
+
a.Z * b.X - a.X * b.Z,
|
|
175
|
+
a.X * b.Y - a.Y * b.X,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
```lua
|
|
181
|
+
-- Without transformer
|
|
182
|
+
local function cross(a, b)
|
|
183
|
+
return Vector3.new(
|
|
184
|
+
a.Y * b.Z - a.Z * b.Y,
|
|
185
|
+
a.Z * b.X - a.X * b.Z,
|
|
186
|
+
a.X * b.Y - a.Y * b.X
|
|
187
|
+
)
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```lua
|
|
192
|
+
-- With transformer
|
|
193
|
+
local function cross(a: Vector3, b: Vector3)
|
|
194
|
+
local _cache0 = a.Y -- each component read twice → all hoisted
|
|
195
|
+
local _cache1 = b.Z
|
|
196
|
+
local _cache2 = a.Z
|
|
197
|
+
local _cache3 = b.Y
|
|
198
|
+
local _cache4 = b.X
|
|
199
|
+
local _cache5 = a.X
|
|
200
|
+
return Vector3.new(
|
|
201
|
+
_cache0 * _cache1 - _cache2 * _cache3,
|
|
202
|
+
_cache2 * _cache4 - _cache5 * _cache1,
|
|
203
|
+
_cache5 * _cache3 - _cache0 * _cache4
|
|
204
|
+
)
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**2.7× faster.**
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
### Loop bounds hoisting
|
|
213
|
+
|
|
214
|
+
`arr.size()` in a `for` loop condition is re-evaluated on every iteration in rotor's output. The loops pass hoists it to a `const` before the loop.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// TypeScript source
|
|
218
|
+
export function sumWeighted(values: Array<number>, weights: Array<number>): number {
|
|
219
|
+
let total = 0;
|
|
220
|
+
for (let i = 0; i < values.size(); i++) {
|
|
221
|
+
total += values[i] * weights[i];
|
|
222
|
+
}
|
|
223
|
+
return total;
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
```lua
|
|
228
|
+
-- Without transformer
|
|
229
|
+
local function sumWeighted(values, weights)
|
|
230
|
+
local total = 0
|
|
231
|
+
for i = 0, #values - 1 do -- #values evaluated once per iteration
|
|
232
|
+
total += values[i + 1] * weights[i + 1]
|
|
233
|
+
end
|
|
234
|
+
return total
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
```lua
|
|
239
|
+
-- With transformer
|
|
240
|
+
local function sumWeighted(values: {number}, weights: {number})
|
|
241
|
+
local total = 0
|
|
242
|
+
local _len_values = #values -- hoisted: evaluated once total
|
|
243
|
+
for i = 0, _len_values - 1 do
|
|
244
|
+
total += values[i + 1] * weights[i + 1]
|
|
245
|
+
end
|
|
246
|
+
return total
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**2.3× faster** (combined with `--!native` and type annotations on `values`/`weights`).
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### Luau type annotation injection
|
|
255
|
+
|
|
256
|
+
After rotor writes `.luau` files, the transformer injects Luau type annotations on function parameters. This lets the native compiler generate specialized code for numeric and Roblox value types.
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// TypeScript source
|
|
260
|
+
export function dot(a: Vector3, b: Vector3): number {
|
|
261
|
+
return a.X * b.X + a.Y * b.Y + a.Z * b.Z;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```lua
|
|
266
|
+
-- Without transformer
|
|
267
|
+
local function dot(a, b)
|
|
268
|
+
return a.X * b.X + a.Y * b.Y + a.Z * b.Z
|
|
269
|
+
end
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```lua
|
|
273
|
+
-- With transformer
|
|
274
|
+
local function dot(a: Vector3, b: Vector3)
|
|
275
|
+
return a.X * b.X + a.Y * b.Y + a.Z * b.Z
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Supported types: `number`, `string`, `boolean`, `Vector3`, `Vector2`, `CFrame`, `UDim2`, `Color3`, `buffer`, `Instance`, `BasePart`, `Player`, `Camera`, `RunService`, `Players`, `Workspace`, and array forms (`{number}`, `{Vector3}`, etc.).
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Benchmarks
|
|
284
|
+
|
|
285
|
+
Measured in Roblox Studio server context. 100,000 iterations per benchmark (10,000 for `cfLookAt`). Same TypeScript source compiled two ways — with and without the transformer.
|
|
286
|
+
|
|
287
|
+
| Benchmark | With transformer | Without | Speedup | Driver |
|
|
288
|
+
|-----------|-----------------|---------|---------|--------|
|
|
289
|
+
| integrate (Verlet) | 0.042 µs | 0.055 µs | **1.3×** | `--!native` + type annotations |
|
|
290
|
+
| dot (V3 manual) | 0.016 µs | 0.032 µs | **2.0×** | `--!native` |
|
|
291
|
+
| cross (V3 manual) | 0.018 µs | 0.049 µs | **2.7×** | `--!native` + 6× field hoisting |
|
|
292
|
+
| lerpVec3 (V3 manual) | 0.015 µs | 0.047 µs | **3.1×** | `--!native` + 3× field hoisting |
|
|
293
|
+
| encodeFixed (buf+math) | 0.015 µs | 0.032 µs | **2.1×** | `--!native` |
|
|
294
|
+
| encodePacket (3× fixed) | 0.018 µs | 0.067 µs | **3.7×** | `--!native` stacked across 3 calls |
|
|
295
|
+
| sumWeighted (loop) | 0.046 µs | 0.107 µs | **2.3×** | `--!native` + loop bounds hoist |
|
|
296
|
+
| dotProduct (loop) | 0.067 µs | 0.109 µs | **1.6×** | `--!native` + loop bounds hoist |
|
|
297
|
+
| norm (loop+sqrt) | 0.048 µs | 0.111 µs | **2.3×** | `--!native` + loop bounds hoist |
|
|
298
|
+
| mathHeavy (trig+sqrt) | 0.038 µs | 0.052 µs | **1.4×** | `--!native` |
|
|
299
|
+
| fib(20) (iter) | 0.048 µs | 0.155 µs | **3.2×** | `--!native` on integer loop |
|
|
300
|
+
| cfLookAt (ctor) | 0.079 µs | 0.079 µs | 1.0× | C++ floor — no Luau work |
|
|
301
|
+
| cfChain (mul+angles) | 0.076 µs | 0.077 µs | 1.0× | C++ floor — no Luau work |
|
|
302
|
+
| serviceWork (GetService ×2) | 0.185 µs | 0.440 µs | **2.4×** | GetService hoisting |
|
|
303
|
+
| multiSvc (GetService ×3) | 0.142 µs | 0.480 µs | **3.4×** | GetService hoisting |
|
|
304
|
+
| cameraWork (prop chain) | 0.148 µs | 0.215 µs | **1.5×** | `camera.CFrame` hoisted (2 reads → 1) |
|
|
305
|
+
| formatStats (template) | 0.170 µs | 0.175 µs | ~1× | String — no arithmetic |
|
|
306
|
+
| buildKey (template) | 0.068 µs | 0.086 µs | **1.3×** | `--!native` |
|
|
307
|
+
|
|
308
|
+
### What the transformer cannot help with
|
|
309
|
+
|
|
310
|
+
- **Pure engine API calls** — `CFrame.lookAt`, `CFrame.Angles`, `CFrame` multiplication all execute immediately in C++. `--!native` cannot speed up code that is already running natively. `cfLookAt` and `cfChain` show 1.0× for this reason.
|
|
311
|
+
- **Single-access properties** — the cache pass only hoists when a property is read 2+ times in the same function. One read has nothing to eliminate.
|
|
312
|
+
- **String-heavy functions** — Luau string operations are not meaningfully accelerated by the native compiler.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Development
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
npm run build # build the transformer
|
|
320
|
+
npm run bench:rbxlx # build both versions + produce bench/benchmark.rbxlx
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Open `bench/benchmark.rbxlx` in Roblox Studio and run the server. The optimized suite prints first, then the baseline suite, sequentially in the output window.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "perf-benchmark",
|
|
3
|
+
"emitLegacyScripts": false,
|
|
4
|
+
"tree": {
|
|
5
|
+
"$className": "DataModel",
|
|
6
|
+
"ServerScriptService": {
|
|
7
|
+
"$className": "ServerScriptService",
|
|
8
|
+
"$path": "out/src/server"
|
|
9
|
+
},
|
|
10
|
+
"ReplicatedStorage": {
|
|
11
|
+
"$className": "ReplicatedStorage",
|
|
12
|
+
"shared": {
|
|
13
|
+
"$path": "out/src/shared"
|
|
14
|
+
},
|
|
15
|
+
"include": {
|
|
16
|
+
"$path": "out/include"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rbxts-transform-perf-test",
|
|
3
|
+
"lockfileVersion": 3,
|
|
4
|
+
"requires": true,
|
|
5
|
+
"packages": {
|
|
6
|
+
"": {
|
|
7
|
+
"name": "rbxts-transform-perf-test",
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
10
|
+
"@rbxts/types": "^1.0.908",
|
|
11
|
+
"rbxts-transform-boost": "file:../",
|
|
12
|
+
"typescript": "=5.5.3"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"..": {
|
|
16
|
+
"name": "rbxts-transform-boost",
|
|
17
|
+
"version": "0.1.0",
|
|
18
|
+
"dev": true,
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^26.0.0"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"typescript": ">=5.0.0"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"node_modules/@rbxts/compiler-types": {
|
|
27
|
+
"version": "3.0.0-types.0",
|
|
28
|
+
"resolved": "https://registry.npmjs.org/@rbxts/compiler-types/-/compiler-types-3.0.0-types.0.tgz",
|
|
29
|
+
"integrity": "sha512-VGOHJPoL7+56NTatMGqQj3K7xWuzEV+aP4QD5vZiHu+bcff3kiTmtoadaF6NkJrmwfFAvbsd4Dg764ZjWNceag==",
|
|
30
|
+
"dev": true,
|
|
31
|
+
"license": "MIT"
|
|
32
|
+
},
|
|
33
|
+
"node_modules/@rbxts/types": {
|
|
34
|
+
"version": "1.0.928",
|
|
35
|
+
"resolved": "https://registry.npmjs.org/@rbxts/types/-/types-1.0.928.tgz",
|
|
36
|
+
"integrity": "sha512-GzmzBNn8fyn0ed9kVQkXUbBXpUg9Gfyf9AZJKT51VGqyrf4w6J0X72eSSGxt6YN2BMHNbqhzHCBn/fM8bdqMTg==",
|
|
37
|
+
"dev": true,
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
},
|
|
40
|
+
"node_modules/rbxts-transform-boost": {
|
|
41
|
+
"resolved": "..",
|
|
42
|
+
"link": true
|
|
43
|
+
},
|
|
44
|
+
"node_modules/typescript": {
|
|
45
|
+
"version": "5.5.3",
|
|
46
|
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
|
|
47
|
+
"integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
|
|
48
|
+
"dev": true,
|
|
49
|
+
"license": "Apache-2.0",
|
|
50
|
+
"bin": {
|
|
51
|
+
"tsc": "bin/tsc",
|
|
52
|
+
"tsserver": "bin/tsserver"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=14.17"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rbxts-transform-perf-test",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"compile": "lumen src -o out/src -i out/include -p default.project.json",
|
|
6
|
+
"watch": "lumen src -o out/src -i out/include -p default.project.json --watch"
|
|
7
|
+
},
|
|
8
|
+
"devDependencies": {
|
|
9
|
+
"@rbxts/compiler-types": "^3.0.0-types.0",
|
|
10
|
+
"@rbxts/types": "^1.0.908",
|
|
11
|
+
"typescript": "=5.5.3",
|
|
12
|
+
"rbxts-transform-boost": "file:../"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as opt from "../shared/fns";
|
|
2
|
+
import * as base from "../shared/fns-bare";
|
|
3
|
+
|
|
4
|
+
function bench(label: string, n: number, fn: () => void): void {
|
|
5
|
+
task.wait(0.05);
|
|
6
|
+
const t0 = os.clock();
|
|
7
|
+
for (let i = 0; i < n; i++) fn();
|
|
8
|
+
const elapsed = os.clock() - t0;
|
|
9
|
+
print(` ${label}: ${string.format("%.3f", (elapsed / n) * 1e6)} us/iter`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const N = 100000;
|
|
13
|
+
const NS = 10000;
|
|
14
|
+
|
|
15
|
+
const pos = new Vector3(1, 2, 3);
|
|
16
|
+
const vel = new Vector3(0, 1, 0);
|
|
17
|
+
const acc = new Vector3(0, -9.8, 0);
|
|
18
|
+
const vecA = new Vector3(1, 0, 0);
|
|
19
|
+
const vecB = new Vector3(0, 1, 0);
|
|
20
|
+
const eye = new Vector3(0, 5, 10);
|
|
21
|
+
const tgt = new Vector3(0, 0, 0);
|
|
22
|
+
const cf = CFrame.lookAt(eye, tgt);
|
|
23
|
+
const buf = buffer.create(256);
|
|
24
|
+
const vals = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
25
|
+
const wts = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
|
26
|
+
const cam = game.GetService("Workspace").CurrentCamera!;
|
|
27
|
+
|
|
28
|
+
function runSuite(fns: typeof opt): void {
|
|
29
|
+
bench("integrate (verlet)", N, () => fns.integrate(pos, vel, acc, 1 / 60));
|
|
30
|
+
bench("dot (V3 manual)", N, () => fns.dot(vecA, vecB));
|
|
31
|
+
bench("cross (V3 manual)", N, () => fns.cross(vecA, vecB));
|
|
32
|
+
bench("lerpVec3 (V3 manual)", N, () => fns.lerpVec3(pos, eye, 0.5));
|
|
33
|
+
bench("encodeFixed (buf+math)", N, () => fns.encodeFixed(buf, 0, 3.14, 100));
|
|
34
|
+
bench("encodePacket(3x fixed)", N, () => fns.encodePacket(buf, 1.1, 2.2, 3.3, 100));
|
|
35
|
+
bench("sumWeighted (loop)", N, () => fns.sumWeighted(vals, wts));
|
|
36
|
+
bench("dotProduct (loop)", N, () => fns.dotProduct(vals, vals));
|
|
37
|
+
bench("norm (loop+sqrt)", N, () => fns.norm(vals));
|
|
38
|
+
bench("mathHeavy (trig+sqrt)", N, () => fns.mathHeavy(1.23, 4.56));
|
|
39
|
+
bench("fib(20) (iter)", N, () => fns.fib(20));
|
|
40
|
+
bench("cfLookAt (ctor)", NS, () => fns.cfLookAt(eye, tgt));
|
|
41
|
+
bench("cfChain (mul+angles)", N, () => fns.cfChain(cf, 0.016));
|
|
42
|
+
bench("serviceWork (GetService x2)", N, () => fns.serviceWork());
|
|
43
|
+
bench("multiSvc (GetService x3)", N, () => fns.multiService());
|
|
44
|
+
bench("cameraWork (prop chain)", N, () => fns.cameraWork(cam));
|
|
45
|
+
bench("formatStats (template)", N, () => fns.formatStats("speed", 9.81, "m/s"));
|
|
46
|
+
bench("buildKey (template)", N, () => fns.buildKey("player", 42, "data"));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
print("\n=== optimized (--!native + transformer) ===");
|
|
50
|
+
runSuite(opt);
|
|
51
|
+
print("===========================================\n");
|
|
52
|
+
|
|
53
|
+
task.wait(1);
|
|
54
|
+
|
|
55
|
+
print("\n=== baseline (plain rotor, no transformer) ===");
|
|
56
|
+
runSuite(base);
|
|
57
|
+
print("==============================================\n");
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
}
|