Skip to main content

Performance Guidelines

Core Rules

  1. Never use Flamework.createGuard<T>() on hot paths — extremely expensive at runtime
  2. One operation per heartbeat tick — queue-based, never create AND unload in same frame
  3. Defer expensive worktask.defer() chains for cell parsing, forest gen, cleanup
  4. Client batch rendering — 25 MeshPart clones per frame max
  5. Heartbeat throttling — ChunkService 0.25s accumulator, visibility 0.1s interval

Do's

  • Cache CollectionService results in Sets via component registration
  • Use octrees for spatial queries (baseplates, danger zones)
  • Batch network updates (25 entities per frame)
  • Use LOD system for distant meshes
  • Gate debug logging behind flag checks
  • Use PointToObjectSpace for rotation-aware bounds checks

Don'ts

  • Never call CollectionService.GetTagged() per frame
  • Never create AND unload chunks in the same heartbeat tick
  • Never use FindFirstChild in hot loops — cache references
  • Never send large packets over remotes — batch and throttle
  • Avoid WaitForChild in systems that run per-frame

Throttling Patterns

Accumulator

private accum = 0;
RunService.Heartbeat.Connect((dt) => {
this.accum += dt;
if (this.accum < 0.25) return;
this.accum = 0;
// work here
});

Queue Processing

// One item per tick
if (this.pendingQueue.size() > 0) {
const item = this.pendingQueue.remove(0)!;
this.processItem(item);
}

Batch Rendering

const BATCH_SIZE = 25;
const count = math.min(BATCH_SIZE, this.pending.size());
for (let i = 0; i < count; i++) {
// clone and parent mesh
}
task.defer(() => this.renderNextBatch());

Monitoring

Key MicroProfiler labels to watch:

  • Script::deferredThreads
  • Heartbeat::RunService.Heartbeat

Target: Stay under 8ms per frame on Apple M4 / Studio v707.