Repository navigation
perf: faster rendering with identical output, lifecycle and library fixes, bun toolchain - #1
Merged
Conversation
Import the typescript-eslint plugin's default export (its configs live there) and turn off core no-undef for TS files, as typescript-eslint recommends.
Resize the canvas only when its size changes, set the font once per frame, skip redundant fillStyle writes and fillText for whitespace, and cache glyph widths for centered text (invalidated on font change and on web font load). Renderers are now created per component instance.
Compute with plain numbers instead of vec2 helpers that allocate per call. Same operation order, so results are bit-identical.
Project vertices into reused objects, precompute per-edge terms in pre and take one square root per cell. Distances are bit-identical to the previous version.
- Run boot when the program starts and size the buffer to the grid - Keep buffer, frame count and userData across pause/resume - Ignore frame callbacks after a run stops; let loop return an unsubscribe - Schedule capped frames on a fixed timeline so fps is accurate - Compare settings structurally so inline objects don't restart the program - Give each text renderer its own back buffer - Use a single rAF id and build the context once per frame
Includes React 19.3, Vite 6.4, turbo 2.11, typescript-eslint 8.71, tsup 8.5, and TypeScript 5.9.
Upgrade @eslint/js 10, globals 17, eslint-plugin-react-hooks 7 and eslint-plugin-react-refresh 0.5. Drop eslint-plugin-react, which was never configured and does not support ESLint 10. Avoid a no-useless-assignment hit in the render loop.
Drop the unused baseUrl (deprecated in TS 6). tsup's declaration build injects baseUrl itself, so the library tsconfig sets ignoreDeprecations until the build moves off tsup.
Replace pnpm (lockfile and workspace file) with bun workspaces; the root package.json already declared them. Release scripts call the binaries directly.
- Canvas renderer: draws the same as the pre-rewrite renderer (kept as a test oracle) across settings, device pixel ratios and font loads, using a recording 2D context - sdf: bit-identical to the previous implementation, including edge cases - Frame cap: accurate rate at 60/120/144/240 Hz with jitter, no burst after stalls - Text renderer: row output, partial updates, per-instance state - Component lifecycle: boot, pause/resume, settings and userData updates, external loops, unmount Adds bun test (happy-dom), typecheck scripts and turbo tasks.
Rows are written with innerHTML, so a cell char like "<img ...>" or a color containing a quote was parsed as markup. Escape both; beginHTML/endHTML stay raw.
Rewrite the hand-written module declarations against their implementations, type canvasSize/canvasOffset and context.settings.element, and drop the duplicate types/index.d.ts. Add a test that declared exports match runtime exports, and a consumer fixture compiled against dist with skipLibCheck off (check:types).
- Declare Apache-2.0 (matching LICENSE and the upstream play.core) and ship the LICENSE in the npm package - Point changesets at master and stop versioning the private examples app - Release the render loop changes as a minor: they change behaviour
keydown was listened for on a pre/canvas that could never take focus, so program.keyDown never ran; make the element focusable when the program handles keys. Re-measure character metrics on document.fonts loadingdone.
…wnload - drawBox merged the raw style (and a misspelled fontWeight) into text cells, clearing the box colors under the text - buffer helpers indexed with fractional/NaN coordinates, adding stray keys - distSqVec3 ignored z - exportFrame threw on filenames without an extension - .jpg was saved as text/jpeg
turbo dev started the demo app while tsup --watch was wiping dist, so Vite cached a failed resolution of react-ascii-play and served 500s. Build the library before dev tasks start and don't clean dist in watch mode.
Without the per-frame resize, an invalid backgroundColor left fillStyle at the previous frame's last cell color and painted the whole canvas with it. Reset it to the default first, as the resize did.
Instances without a userData prop shared one module-level object, so state a program stored in it leaked between instances and across remounts.
Restore inputs the implementation supports: sortAscii takes a string or an array, setBuffer/setRectBuffer take a cell or a character. Use typescript-eslint's eslint-recommended overrides so overload declarations don't trip no-redeclare.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the renderer fast without changing what it draws, fixes the render loop lifecycle and several library bugs, and modernises the toolchain. The torus demo used ~13 ms of a 16.7 ms frame and ran at ~17 FPS under 4× CPU throttling; it now runs at ~35–38 FPS there. Output stays identical: the canvas renderer is tested against a frozen copy of the original, and SDF results are bit-identical. Also switches the repo to bun (package manager and tests), adds a test suite and CI, and fixes the published typings and package license.
Behaviour changes, released as a minor via changesets:
bootnow runs; it never did before.userData. Changingprogram, settings orlooprestarts and runsboot.fpsmatches the display, never above the cap.loopmay return an unsubscribe function.beginHTML/endHTMLstay raw).keyDownworks: the element is focusable when the program defines it.loadJsonis typedPromise<unknown>instead ofany.Changes
fillStyle, caches glyph widths for centered text.sdBox/sdSegment.keyDownevents.drawBoxcolors and font weight under text, fractional/NaN buffer coordinates,distSqVec3z,exportFramewithout extension, JPEG MIME type.canvasSize,canvasOffsetandcontext.settings.element; removed the duplicate types file.dist.bun run devno longer races tsup's watch build.masterand skips the private examples app.AGENTS.mdwith commands, the invariants the tests enforce, and release rules.Checklist
bun lintpasses