feat: in-place resize via term.update() - #113
Open
natemoo-re wants to merge 3 commits into
Open
Conversation
|
Size Reduced — -15.6 KB 102.7 KB unpacked |
commit: |
Replaces the create-a-new-Term resize story (old 7.4) with a synchronous update transaction: reallocate renderer state in place within the existing wasm instance and linear memory, so a resized Term stays valid and keeps rendering at the new dimensions. Accepts a discriminated options bag: explicit dimensions or an event array (resize events coalesce last-wins; non-resize events ignored). The event shape is structural, deliberately assignable from input's ResizeEvent without introducing a renderer->input dependency (11.4). After a non-no-op update the next render is a complete redraw. Growth-only memory (high-water mark on downsize) is documented as accepted behavior. Output views are invalidated by update() since memory.grow detaches buffers; 7.3's validity window is widened accordingly.
term.update() changes a Term's dimensions in place: it re-inits renderer state in the existing wasm instance and linear memory rather than building a new Term. statePtr and opsBuf move as the state region is re-laid-out, so they become getters; the standalone [heap: state][opsBuf] allocation is factored into a reusable layout() that both the constructor and update() call. The options bag is a discriminated union of explicit dimensions or an event array (last resize wins; non-resize ignored). Invalid dimensions throw; a same-size update is a no-op. A real resize discards JS-side pointer and timing state and the wasm diff buffers, so the next render is a full redraw.
Covers redraw-after-update, same-size no-op, invalid-dimension throws, event-array coalescing (last resize wins, non-resize ignored, empty = no-op), pointer-state reset, layout at the new dimensions, and downsize-then-upsize past the original size (growth-only memory).
natemoo-re
force-pushed
the
nm/feat/resize
branch
from
September 1, 2026 00:47
92a8ef5 to
3d1937f
Compare
natemoo-re
marked this pull request as ready for review
September 1, 2026 12:56
Collaborator
|
@natemoo-re There's a bit of a dance at the beginning of initialization where we ask clay how much memory it needs, then we allocate it, and then we hand it the memory. This is what allows Clay to not need an allocator and not need Do we need to re-allocate the clay arena as well? |
Member
Author
|
@cowboyd great call out! we might? I'll have to double check. I do have a local branch that does some optimization to clamp the clay arena to the worst case scenario for the given terminal size, which layers nicely on top of this. I can push up the draft. |
natemoo-re
added a commit
that referenced
this pull request
Sep 1, 2026
Clay preallocates its arena from a fixed default of 8192 max elements, so an 80x24 terminal reserved 4.85 MB of linear memory before rendering anything. Element capacity drives nearly all of Clay_MinMemorySize (~600 B/element on wasm32); the measure-text word cache is second-order. Derive the cap from the grid instead: 2x cells, clamped to [2048, 8192]. Leaf elements occupy at least one cell and internal nodes with branching >= 2 can at most double the leaf count, so 2x cells bounds realistic trees while leaving headroom for floating elements and wrapper chains. The ceiling keeps large grids at exactly today's capacity; the floor protects tiny panes. Arena for 80x24 drops 4.85 MB -> 2.39 MB (-51%); 20x5 bottoms out at 1.23 MB; 200x50 is unchanged. Render benches are flat. The capacity setters run at the top of both clayterm_size() and init() so the size the host queries always matches what init consumes, for the first init and for in-place re-init alike: Clay_MinMemorySize prefers the live context's caps and Clay_Initialize inherits them from the old context, so the update() resize flow (#113) sizes correctly in both directions. Verified against a local merge of nm/feat/resize: full suite passes, and renders stay error-free across 80x24 -> 200x50 -> 20x5 -> 132x43 in-place resizes. Exceeding the cap degrades the same way it always has, just at the new boundary: Clay stops opening elements and the frame surfaces an error through RenderResult.errors.
natemoo-re
added a commit
that referenced
this pull request
Sep 1, 2026
* ref(opt): scale Clay element capacity with the terminal grid Clay preallocates its arena from a fixed default of 8192 max elements, so an 80x24 terminal reserved 4.85 MB of linear memory before rendering anything. Element capacity drives nearly all of Clay_MinMemorySize (~600 B/element on wasm32); the measure-text word cache is second-order. Derive the cap from the grid instead: 2x cells, clamped to [2048, 8192]. Leaf elements occupy at least one cell and internal nodes with branching >= 2 can at most double the leaf count, so 2x cells bounds realistic trees while leaving headroom for floating elements and wrapper chains. The ceiling keeps large grids at exactly today's capacity; the floor protects tiny panes. Arena for 80x24 drops 4.85 MB -> 2.39 MB (-51%); 20x5 bottoms out at 1.23 MB; 200x50 is unchanged. Render benches are flat. The capacity setters run at the top of both clayterm_size() and init() so the size the host queries always matches what init consumes, for the first init and for in-place re-init alike: Clay_MinMemorySize prefers the live context's caps and Clay_Initialize inherits them from the old context, so the update() resize flow (#113) sizes correctly in both directions. Verified against a local merge of nm/feat/resize: full suite passes, and renders stay error-free across 80x24 -> 200x50 -> 20x5 -> 132x43 in-place resizes. Exceeding the cap degrades the same way it always has, just at the new boundary: Clay stops opening elements and the frame surfaces an error through RenderResult.errors. * chore: add changeset for grid-scaled arena
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.
previously, handling a terminal resize meant throwing away the
Termand building a new one at the new dimensions — a fresh wasm instance every time, with per-frame diff state lost in the swap.now,
term.update()resizes aTermin place. it takes either explicit{ width, height }or{ events }, so the resize events frominput.scan()can be passed straight through (last resize wins; non-resize events are ignored).details: no new wasm module or instance is ever created — the existing instance re-lays-out its renderer state and grows its linear memory only as needed. memory only grows, keeping the high-water mark until the
Termis discarded. a real resize discards diff and pointer state so the nextrender()is a complete redraw; a same-size update is a no-op, and invalid dimensions throw.this also flips the original stacking:
update()lands first as the primitive, and terminfo (#106) can then evolve to pass its capability data through the same call.