Fix iOS keyboard launch crash and canvas visibility#9
Merged
Conversation
The keyboard extension was hitting three independent failure modes
that together made it unusable: a hard jetsam kill on launch, a
canvas that collapsed to zero height immediately after appearing,
and a PPM language model whose size was dominated by ~311KB of
training text. Each is fixed below; all three were needed before
the keyboard would stay on screen for more than a few hundred
milliseconds.
Memory (jetsam on launch at 77MB ActiveHard cap)
- Trim keyboard training text from 311KB to the first 50 lines
(~24KB) in the Prepare Keyboard Data build phase. PPM trie now
fits comfortably under the cap.
- configureForLowMemory() sets LP_NODE_BUDGET=200, LP_LM_MAX_ORDER=4,
BP_LM_ADAPTIVE=0 on every keyboard launch so DasherApp's larger
values (fine for the host app) don't get used by the extension.
- didReceiveMemoryWarning() no longer calls bridge.reset() — that
rebuilds the node tree and spikes memory, the opposite of what
a memory warning needs.
Canvas visibility (collapsed to 0 height within ~10ms of launch)
- Add an explicit Auto Layout height constraint of 320pt on the
input view controller's view (priority .defaultHigh). Without
this, iOS computes the extension's required height as just the
toolbar (44pt) and shrinks the canvas away. This was the last
piece of the puzzle.
- CADisplayLink now uses a weak DisplayLinkProxy target (was
retaining self), and is invalidated in deinit. Previous setup
leaked the canvas every time the keyboard re-opened.
- displayLink preferredFramesPerSecond = 30, and tick() is gated
on a needsRedraw flag set by touch handlers. Stops the per-frame
NewFrame() expansion that grew memory until iOS shrunk us.
- viewWillAppear resets the bridge so each open starts at the root,
not wherever the previous session left the model.
Locale + UX
- On first run (no dasher_settings.xml in the App Group yet),
pick an alphabet from Locale.current rather than always
defaulting to 'English with limited punctuation'. Currently
maps 'de' to the German alphabet; everything else stays English.
Easy to extend as more alphabets are bundled.
- bridge.saveSettings() is called after the first-run alphabet
selection so subsequent launches honour the user's later
DasherApp choices regardless of system locale.
Diagnostics (kept; cheap, useful for future device bring-up)
- os_log breadcrumbs through DasherBridge.init, Realize(),
setScreenSize, layout pass, first CADisplayLink tick, first
draw, and canvas height changes. Subsystem filter in Console.app:
'at.dasher.Dasher.keyboard'.
DasherCore bump: 5533eb9 -> 3ddedde2
Pulls in #20 ('IsFileWriteable: probe via permission bits, not
append-open') which removes the sandbox-deny storm the keyboard
generated against every bundled alphabet/colour XML during Realize.
Signed-off-by: will wade <willwade@gmail.com>
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.
Problem
The iOS keyboard extension was unusable. Three independent failure modes stacked on top of each other:
exceeded mem limit: ActiveHard 77 MB (fatal)~1s after the system swapped to the Dasher keyboard. The host app silently fell back to the default keyboard.Each had to be fixed before the keyboard would stay on screen for more than a few hundred milliseconds.
Root causes (verified via Console.app on a physical device)
training_english_GB.txtproduced a PPM trie that, combined with UIKit overhead and the alphabet/colour XMLs, blew the 77MB keyboard extension memory cap.IsFileWriteable()in DasherCore opened every bundled file in append-write mode just to test writability, generating aSandbox: deny file-write-datafor each colour/alphabet XML duringRealize(). Fixed in DasherCore #20, now merged.UIInputViewController's view had no explicit height constraint, so iOS measured the extension's required height as just the toolbar (44pt) and collapsed the canvas.CADisplayLinkretained its target viatarget: self, leaking the canvas across open/close cycles and firingNewFrame()60–120 times/sec, expanding the model and growing memory until iOS shrunk us.What's in this PR
Memory
Prepare Keyboard Databuild phase (project.yml).configureForLowMemory()pinsLP_NODE_BUDGET=200,LP_LM_MAX_ORDER=4,BP_LM_ADAPTIVE=0so DasherApp's larger values don't leak into the extension.didReceiveMemoryWarning()no longer callsbridge.reset()(that rebuilds the node tree — opposite of what a memory warning needs).Canvas visibility
.defaultHighpriority) on the input view controller's view. This was the last piece that turned a flash-then-disappear into a stable, usable keyboard.CADisplayLinkuses a weakDisplayLinkProxy(no more retain cycle), is invalidated indeinit.preferredFramesPerSecond = 30, andtick()is gated on aneedsRedrawflag set by touch handlers — stops the per-frameNewFrame()expansion that grew memory until iOS shrunk us.viewWillAppearcallsbridge.reset()so each open starts at the root.Locale + UX
dasher_settings.xmlin the App Group), pick the alphabet fromLocale.currentrather than always defaulting to English. Currentlyde→ German alphabet; everything else stays English. Extensible as more alphabets are bundled.Diagnostics (kept — cheap, useful for future device bring-up)
os_logbreadcrumbs throughDasherBridge.init,Realize(),setScreenSize, the first layout pass, firstCADisplayLinktick, firstdraw, and canvas-height changes. Console.app filter: subsystemat.dasher.Dasher.keyboard.Submodule bump
DasherCore:5533eb9→3ddedde2— pulls in DasherCore #20 (IsFileWriteablerewrite), which removes the sandbox-deny storm the keyboard generated against every bundled alphabet/colour XML duringRealize(). That change is platform-neutral and strictly safer for every DasherCore frontend (Windows, GTK, WASM included).Settings sync (free win)
Most user-facing settings already sync automatically: both DasherApp and the keyboard read/write the same
dasher_settings.xmlin the shared App Group (group.at.dasher.Dasher). Alphabet, palette, speed, input filter, locale, font — all carry over. The only intentional overrides are the LM memory-pressure params above.Out of scope (deliberately)
control.xml+ action callbacks wired totextDocumentProxy.Verification
Built and tested on iPhone (iPhone 12-class, iOS 18). Keyboard now: