Skip to main content
Version: 0.x.x

Performance

Canvas Tile Engine is designed for large grid scenes, but the best strategy depends on whether your content is dynamic, static, visible all at once, or spread across a large world.

Automatic Optimizations

Viewport Culling

Draw methods skip items outside the visible viewport plus a small buffer. This applies to rectangles, circles, text, images, lines, and paths.

Spatial Indexing

For arrays larger than 500 items, dynamic rect, circle, text, and image layers build an RBush spatial index. Viewport queries then avoid scanning the whole list each frame.

engine.drawRect(largeArrayOfTiles, 1);

Use one draw call with an array instead of thousands of draw calls.

Renderer Batching

  • Canvas2D reduces repeated style changes while iterating visible items.
  • WebGL batches geometry into GPU draw calls.
  • Skia caches parsed colors and fonts and records frames as pictures.

Static Drawing

Static helpers are best for large non-changing layers.

engine.drawStaticRect(items, "terrain", 0);
engine.drawStaticCircle(markers, "markers", 1);
engine.drawStaticImage(images, "decorations", 1);

Renderer behavior differs:

RendererStatic helper behavior
Canvas2DPre-renders to an offscreen canvas and blits visible portions.
WebGLDelegates to dynamic batched drawing; no offscreen cache is kept.
SkiaRecords a reusable SkPicture.
ServerUses an offscreen @napi-rs/canvas cache.

Clear caches when underlying static data changes:

engine.clearStaticCache("terrain");
engine.clearStaticCache();

React Stability

React draw components compare items by reference. Keep large arrays stable with useMemo, useState, or external state.

const terrainItems = useMemo(() => buildTerrain(terrain), [terrain]);

<CanvasTileEngine.StaticRect items={terrainItems} cacheKey="terrain" layer={0} />;

Avoid inline arrays for large layers:

// Avoid for large data: new array identity on every render.
<CanvasTileEngine.Rect items={data.map(toRect)} layer={1} />;

Practical Rules

  • Batch items: drawRect(items) is better than calling drawRect(item) in a loop.
  • Use dynamic draw calls for frequently changing layers.
  • Use static draw calls for large, non-changing terrain or minimap layers.
  • Keep sprite animation frame lists stable.
  • In WebGL, call renderer.invalidateTexture(source) after mutating an image/canvas source without changing its dimensions.
  • Prefer server renderer.encode() over toBuffer() in request handlers to avoid blocking.