Skip to content

[Feature] Add UntoldViewOptions for runtime view settings in SwiftUI - #17

Merged
miogds merged 8 commits into
developfrom
feature/untoldview_options
Aug 11, 2026
Merged

[Feature] Add UntoldViewOptions for runtime view settings in SwiftUI#17
miogds merged 8 commits into
developfrom
feature/untoldview_options

Conversation

@miogds

@miogds miogds commented Aug 5, 2026

Copy link
Copy Markdown
Member

Implements the UntoldViewOptions proposal (doc included in this PR at docs/proposals/UntoldViewOptions.md), as agreed with Harold: UntoldRendererConfig stays immutable/create-time; runtime view-host settings get their own small Equatable struct that is diff-applied to the live MTKView without ever recreating the renderer.

What's in here

UntoldViewOptionsEquatable, Sendable struct with preferredFramesPerSecond, isPaused, clearColor. Usable two ways:

UntoldView(renderer: renderer, options: UntoldViewOptions(preferredFramesPerSecond: 30)) { ... }

// or modifier style (same copy-on-write pattern as .onUpdate):
UntoldView(renderer: renderer) { ... }
    .preferredFramesPerSecond(fps)
    .paused(inMenu)

When a bound SwiftUI value changes, the body re-evaluates, updateNSView/updateUIView runs, and the coordinator applies only the properties that differ from the last-applied options. Unrelated re-evaluations cost one Equatable compare.

Stable renderer ownership (the prerequisite from the proposal)SceneView's Coordinator now owns the renderer. Previously both UntoldView.init and SceneView.init ran renderer ?? UntoldRenderer.create() on every SwiftUI re-evaluation. Now the injected renderer is adopted (or a fallback created) exactly once, on first makeNSView/makeUIView, and never swapped.

Scene content built onceUntoldView stores the @SceneBuilder closure and runs it a single time when the platform view is created, after the renderer exists (so setEntityMeshAsync has a Metal device). Previously the builder re-ran on every re-evaluation — in the SceneBuilder demo that meant new entities + mesh reloads every timer tick.

SceneView.onInit semantic 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. Signature also gained @MainActor on the block. This is what makes SceneView().onInit { ... } with a nil renderer actually safe.

Not included (follow-ups per the proposal)

  • renderScale (needs care: create() pins contentsScale = 1.0)
  • drawsOnDemand (enableSetNeedsDisplay mode)

Testing

  • swift build clean for the engine, demos, and Sandbox (all SceneView(renderer:) call sites unchanged — new params are defaulted).
  • New UntoldViewOptionsTests (7 tests): 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 existing SceneBuilderNodeTests.
  • swiftformat --lint clean on changed files (local 0.62.1; CI's pinned 0.60.1 will confirm).

Javier Segura and others added 8 commits August 5, 2026 16:10
…dinator

PR B of the physics plugin readiness plan (discussion untoldengine#1116), built on the
EngineExtension infrastructure from untoldengine#1127 instead of a custom runFrame
seam:

- PhysicsCoordinator (EngineExtension) steps the installed backend once
  per fixed substep: body-set diffing against the RigidBodyComponent +
  ColliderComponent query, batch kinematic writes, step, batch transform
  read-back into LocalTransformComponent, event drain into an inert sink
  (subscriptions arrive in the events PR).
- PhysicsBackendRegistry schedules/unschedules the coordinator in
  EngineExtensionRegistry on install/uninstall and locks for runtime on
  the first simulated substep.
- Gravity for the built-in integrator now reads from the global
  PhysicsWorldConfiguration (default unchanged at (0, -9.8, 0)), so
  legacy and backend-owned bodies share one gravity.

With no backend installed nothing is scheduled and behavior is unchanged;
the built-in integrator keeps running legacy entities either way.
[Chores] Formatted files

[Patch] Fixed the guassian-mesh occlusion

[Patch] Improved API function call
Replace the nine open-coded per-mesh vertex buffer bind sites in the
model-family and shadow-family passes with bindModelVertexStreams /
bindShadowVertexStreams encoder helpers. Batched-geometry sites keep
their raw batch-buffer binds.

Also add a reader regression test covering unknown core-range chunk
types so future format chunks cannot brick older runtimes.

Groundwork for the deformation compute pass: the helpers are the single
point where deformed position/normal buffers will replace the base
streams.
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
miogds force-pushed the feature/untoldview_options branch from e83234a to 9ce94c7 Compare August 6, 2026 17:53
@miogds
miogds merged commit d238b2c into develop Aug 11, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants