Bottleneck
Currently, a procedurally generated custom terrain system must use baked mesh “Content” to avoid Editable* memory budget limits. CreateMeshPartAsync must then be used to apply this mesh to a part, because MeshPart.MeshContent is not assignable and MeshPart:ApplyMesh() does not accept Content.
The issue is that CreateMeshPartAsync, even with CollisionFidelity.Box and CanCollide = false, adds a fixed 22ms cost + 0.27ms per 1k triangles. At 60fps a frame lasts ~16.6ms meaning rendering a single mesh, even an empty mesh, causes a noticeable lag spike.
Failed Workarounds
I attempted to work around this lag spike with various methods. I will explain why each did not work.
Parallelism / Actors: AssetService is not usable from a parallel context.
ApplyMesh with a Content: MeshPart:ApplyMesh() requires another MeshPart as its source. It rejects a Content. The only way to obtain that MeshPart is with CreateMeshPartAsync, which is the source of the lag spike to begin with.
Assigning MeshPart.MeshContent directly: This is script-writegated. Assigning it raises “The current thread cannot write ‘MeshContent’ (lacking capability)”
Making meshes smaller: The cost is fixed per call, so making a mesh smaller actually causes more lag. Making meshes larger shifts the burden onto CreateDataMdelContentAsync and also increases lag. More details on frame costs will be below. In testing, 32³ sized chunks’ meshes increased lag by ~6x compared to 16³ sized.
Time-slicing: The call cannot be split across frames.
Caching/reuse: For procedurally generated terrain, each mesh is unique. There is nothing to reuse.
Measurements
| Step | Cost |
|---|---|
| CreateDataModelContentAsync (bake) | ≈ 7 ms fixed + 3.6 ms per 1k triangles |
| CreateMeshPartAsync (apply) | ≈ 22 ms fixed + 0.27 ms per 1k triangles |
| MeshPart:ApplyMesh(src) | ≈ 0.01 ms (free) |
Note: Default / Hull CollisionFidelity add another 10ms. The ~22ms figure is with Box.
Solution Options
- Make MeshPart.MeshContent assignable from a normal script context.
- Let MeshPart:ApplyMesh() accept a Content in addition to a MeshPart.
- Provide a parallel-safe / non-blocking variant of CreateMeshPartAsync, callable from an Actor in parallel, or one that yields while the expensive work happens off the main thread.
What Would This Enable?
This change would single-handedly make the difference between whether procedural mesh terrain is viable on Roblox without lag, or if it is not possible. Any experience that streams baked mesh content frequently would benefit substantially.