Mandelbrot Set: fix the hang, centre the zoom, add held-key panning - #241
Merged
Conversation
Fixes #160 Closes #17 The fractal was computed inside the draw callback, so all 8192 pixels were iterated on the GUI service thread, and the event loop asked for a fresh frame at least ten times a second whether or not the view had moved (view_port_update ran on the 100 ms queue timeout path too). The draw callback held the state mutex for the whole computation and the event loop needed that same mutex before it could act on an event, so the app could consume at most one button press per frame - and every press it consumed immediately queued another frame. Once enough pixels reached the 50-iteration cap, presses arriving faster than the frame rate (key auto-repeat while panning does exactly this) filled the 8-slot queue, at which point the input callback's FuriWaitForever put blocked the GUI service thread itself. - Compute on the app thread, only when the view actually changed, and publish as an XBM bitmap drawn with a single canvas_draw_xbm instead of up to 8192 canvas_draw_dot calls - Show a coarse 4x4 preview first, then refine at full resolution in bands, polling the queue between scanlines so a slow picture is abandoned as soon as a key is pressed - Skip the iteration loop for the main cardioid and the period-2 bulb, the two large solid regions that always cost the full budget - Never wait in the input callback: dropping a pan step is cheaper than parking the GUI service thread on a full queue - Zoom around the middle of the screen and halve or double the span per step; the old step subtracted a fixed amount from the span, which drove it through zero and mirrored the picture after ~23 presses - Hold Ok to zoom out (previously impossible), capped at 16384x where float32 runs out of mantissa, with the zoom factor shown briefly so reaching either limit is visible - Arrows react to press, long and repeat, panning by a fixed fraction of the screen; the window is kept within reach of the set so panning cannot strand the view in empty space - Square pixels (the picture was squashed horizontally by about a tenth) and an iteration budget that grows with zoom depth - Point fap_weburl at this repo and drop the same dead link from ReadMe.md, keeping the credit: github.com/Possibly-Matt now 404s - Add CHANGELOG.md backfilled from repo history; bump to 1.4, and raise the stack to 2 KB for the zoom indicator's snprintf Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Repository owner
locked as resolved and limited conversation to collaborators
Jul 25, 2026
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Closes #160
Closes #17
The hang (#160)
The fractal was computed inside the draw callback, so all 8192 pixels were iterated on the GUI service thread. Three things compounded:
view_port_update()ran on every loop iteration including the 100 ms queue timeout, so the app demanded a fresh frame at least ten times a second whether or not anything had moved.plugin_state->mutexfor the entire computation, and the event loop needed that same mutex before it could act on an event - so the app could consume at most one button press per frame, and each press it consumed immediately queued another frame.FuriWaitForeverinto an 8-slot queue.Once enough pixels reached the 50-iteration cap, frame time exceeded the redraw interval and the GUI thread never went idle. Presses arriving faster than that - key auto-repeat while panning does exactly this - fill the queue, at which point the input callback blocks the GUI service thread itself. That matches the report: zoom a few times, hold a direction, everything stops.
Worth recording since it was my first theory and it was wrong: the
2.0/4.0/128.0double literals cost nothing. GCC narrows them - the old binary disassembles to purevmul.f32/vfma.f32with zero__aeabi_d*calls. The bottleneck was the threading, not the arithmetic.The fix
canvas_draw_xbminstead of up to 8192canvas_draw_dotcalls.Controls (#17)
All three requests in the issue, plus zoom-out which the app never had:
x2...x16384 max) so hitting either limit is visible rather than looking like the app stopped reacting.Also: square pixels (
ratio = 128/64was applied on top of an already-wider x range, squashing the picture by about a tenth) and an iteration budget that grows with zoom depth, since a fixed 50 turned deep views into a solid blob.Metadata
fap_weburlpointed atgithub.com/Possibly-Matt, which now 404s - the account is gone. Repointed at this repo (followingeth_troubleshooter's precedent) and the same dead link dropped from the ReadMe row, keeping the credit as plain text. This was the second half of #160. AddsCHANGELOG.mdbackfilled from repo history, bumpsfap_versionto 1.4, raises the stack 1 KB -> 2 KB for the zoom indicator'ssnprintf.Verification
Builds clean under standalone ufbt (API 88.0),
ufbt formatapplied, recompiled with the project's exact flags with no diagnostics. Deployed and launched on hardware.No unit tests in this repo, so I modelled the renderer exactly in Python with float32 rounding and checked it three ways:
img/1.pngandimg/2.pngstill show the old border frame and framing; they need real device captures rather than model-rendered stand-ins.🤖 Generated with Claude Code