[Feature] Add UntoldViewOptions for runtime view settings in SwiftUI - #1131
Merged
untoldengine merged 1 commit intoAug 11, 2026
Merged
Conversation
Adds a runtime-tunable options struct for the SwiftUI host view, per the UntoldViewOptions proposal (docs/proposals/UntoldViewOptions.md): - UntoldViewOptions (Equatable, Sendable): preferredFramesPerSecond, isPaused, clearColor. Settable via UntoldView(renderer:options:) or the .preferredFramesPerSecond(_:)/.paused(_:)/.options(_:) modifiers. - SceneView's Coordinator now owns the renderer: a fallback renderer is created exactly once instead of on every SwiftUI body re-evaluation, and options are diffed against the last-applied copy so only changed properties touch the live MTKView. The renderer is never recreated. - UntoldView builds its scene content once, when the platform view is created (after the renderer exists, so mesh loading has a Metal device), instead of on every re-evaluation of the view struct. - SceneView.onInit is deferred accordingly: the block now runs once when the platform view is created rather than immediately at body-eval time on every re-evaluation. UntoldRendererConfig stays immutable, create-time only.
miogds
force-pushed
the
feature/untoldview_options
branch
from
August 6, 2026 17:53
e83234a to
9ce94c7
Compare
untoldengine
approved these changes
Aug 11, 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 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.
Implements the
UntoldViewOptionssplit we discussed:UntoldRendererConfigstays immutable/create-time, and runtime view-host settings (target FPS being the motivating case) get their own smallEquatablestruct that is diff-applied to the liveMTKView— no renderer recreation, noObservableconformance on the renderer. The full design rationale is included in this PR atdocs/proposals/UntoldViewOptions.md.What's in here
UntoldViewOptions—Equatable, Sendablestruct withpreferredFramesPerSecond,isPaused,clearColor. Usable two ways:When a bound SwiftUI value changes, the body re-evaluates,
updateNSView/updateUIViewruns, and the coordinator applies only the properties that differ from the last-applied options. Unrelated re-evaluations cost oneEquatablecompare and never touch the view.Stable renderer ownership — the "renderer lives in a stable reference holder" part:
SceneView'sCoordinatornow owns the renderer. Previously bothUntoldView.initandSceneView.initranrenderer ?? UntoldRenderer.create()on every SwiftUI re-evaluation of the view struct. Now the injected renderer is adopted (or a fallback created) exactly once, on firstmakeNSView/makeUIView, and never swapped.Scene content built once —
UntoldViewstores the@SceneBuilderclosure and runs it a single time when the platform view is created, after the renderer exists (sosetEntityMeshAsynchas a Metal device). Previously the builder re-ran on every re-evaluation — in the SceneBuilder demo that meant new entities and mesh reloads on every timer tick.SceneView.onInitsemantic change (intentional, please review) — the block used to execute immediately at body-evaluation time, on every re-evaluation, and required the renderer to already exist. It now runs exactly once, when the platform view is created and the renderer is ready. The block is also@MainActornow. This makesSceneView().onInit { ... }with a nil renderer safe.Scope rules going forward
Every property in
UntoldViewOptionsmust be applicable to the liveMTKView. Pipeline-affecting settings stay inUntoldRendererConfig; engine tunables (AA, post-FX, LOD, ...) stay in the engine settings API — no duplication, one source of truth each.Not included (follow-ups per the proposal)
renderScale(needs care:create()pinscontentsScale = 1.0)drawsOnDemand(enableSetNeedsDisplaymode)Testing
swift buildclean for the engine, all demos, and Sandbox — all existingSceneView(renderer:)/UntoldView(renderer:)call sites compile unchanged (new parameters are defaulted).UntoldViewOptionsTests(7 tests, no Metal device needed): struct semantics, modifier copy-on-write, first-apply sets everything, diff-apply leaves unchanged properties untouched, equal options are a no-op. All pass, plus existingSceneBuilderNodeTests.