Open
Conversation
…lliders. Expand SimulationState double-buffer to carry ball/flipper/gate transform data, and have ApplyMovements() read from the front buffer without acquiring _physicsLock. This eliminates the TryEnter frame drops (B1) and resolves the double-buffer race (Bug 1) by ensuring the main thread only reads from the stable front buffer. Fix the kinematic collider update gap. Approach: main thread writes kinematic transforms to a shared buffer (protected by a lightweight lock or atomic flag), and the sim thread reads them at the start of each tick.
…yMovements() 1. Removed the legacy fallback in ApplyMovements() — the old else branch that used Monitor.TryEnter(_physicsLock) to acquire the physics lock and call ApplyAllMovements() directly. That path was a leftover from before the triple buffer and would skip entire frames when the sim thread held the lock. 2. Added InvalidOperationException when _simulationState is null but ApplyMovements() is called in external timing mode. This is incoherent state — the startup sequence in SimulationThreadComponent.StartSimulation() always sets it before the first Update(), so hitting this means something is wired wrong. 3. Cleaned up xmldoc on both ApplyMovements() and ApplyMovementsFromSnapshot() to remove references to "legacy" and "fallback" paths.
Allocation Elimination (Hot Path)
1. Math.Sign() — zero-alloc bit test (Common/Math.cs)
Replaced a NativeArray allocation per call with math.asint() bit manipulation. This was called ~2.5M times/sec from narrow-phase collision detection.
2. PhysicsDynamicBroadPhase.FindOverlaps — stack-allocated removal buffer (Game/PhysicsDynamicBroadPhase.cs)
Replaced ToNativeArray(TempJob) with FixedList64Bytes<int> for collecting hash set removals during octree overlap queries. Eliminates a temp allocation per ball pair check.
3. InsideOfs.GetBitIndex() — bitmask free-bit find (Game/InsideOfs.cs)
Replaced GetValueArray(Allocator.TempJob) + O(64*n) Contains() scan with ulong bitmask + math.tzcnt() for O(1) free-bit lookup. Zero allocations.
4. InsideOfs.GetIdsOfBallsInsideItem() — stack return type (Game/InsideOfs.cs)
Changed return from List<int> (heap) to FixedList64Bytes<int> (stack, up to 15 ints). Updated all callers (PhysicsEngine, BumperApi, KickerColliderComponent).
Persistent Native Container Reuse
5. Persistent ball octree (PhysicsEngineContext.BallOctree)
Moved NativeOctree<int> for ball-to-ball collisions from per-tick TempJob alloc/dispose to a persistent field. Clear() + re-insert each cycle instead of create/destroy.
6. Persistent kinematic octree with dirty flag (PhysicsEngineContext.KinematicOctree)
Same pattern, plus only rebuilt when KinematicOctreeDirty is set (transforms actually changed). Reduces rebuild frequency from ~1kHz to ~60Hz.
7. Persistent PhysicsCycle (PhysicsEngineContext.PhysicsCycle)
Moved from per-tick creation to persistent field. Avoids per-tick allocation of the internal NativeList<ContactBufferElement>.
Redundant Work Reduction
8. InsideOfs double-lookup elimination (Game/InsideOfs.cs)
Replaced ContainsKey + [] patterns with TryGetValue in SetOutsideOfAll, IsInsideOf, GetInsideCount, IsEmpty, GetIdsOfBallsInsideItem.
9. PhysFactor float conversion (Engine/Common/Constants.cs + 6 files)
Changed PhysFactor from double to float, removing 8 redundant (float) casts. Avoids implicit double-precision promotion in Burst-compiled velocity code.
10. VPOrthonormalize float3 conversion (VPT/Ball/BallDisplacementPhysics.cs)
Replaced UnityEngine.Vector3 with Unity.Mathematics.float3 + math.cross()/math.normalize(). Eliminates Vector3↔float3 conversions on every ball displacement.
Safety
11. Max substep cap (PhysicsUpdate.cs)
Added PhysicsConstants.MaxSubSteps = 200. If physics falls behind (e.g. frame hitch), the loop caps iterations and skips time forward instead of cascading into a death spiral.
---
Net effect: The physics tick (running at 1kHz) is now allocation-free on its hot path. The three largest per-tick native allocations (two octrees + contacts list) are gone, replaced with persistent containers that clear and refill. The remaining improvements reduce redundant hash lookups and eliminate unnecessary type conversions in Burst-compiled code.
`PhysicsEngine` now exposes `MutateState(...)`, which executes immediately in single-threaded mode but routes state mutations through the existing sim-thread input-action queue in external-timing mode. This was applied to the main direct-mutation gameplay APIs for flippers, plungers, gates, bumpers, and drop targets. Remaining gaps still exist for kicker-related state writes, collider enable/disable, and kinematic transformation callbacks.
Implemented for the central collider enable/disable path. `PhysicsEngine.EnableCollider(...)` and `DisableCollider(...)` now go through `MutateState(...)`, so Unity lifecycle callers like `ColliderComponent.OnEnable()` / `OnDisable()` and gate lifter coil actions no longer write `DisabledCollisionItems` directly in external-timing mode. This still needs runtime validation on tables that toggle colliders frequently, but the ownership boundary is now correct for this path.
…sics mutation. `UpdateKinematicTransformsFromMainThread()` now only detects and stages changed matrices, while `ApplyPendingKinematicTransforms()` applies the matrix and invokes `OnTransformationChanged(...)` on the simulation thread under physics ownership. Single-threaded mode still performs the callback inline on the main thread, which is correct for that mode.
`PhysicsEngine` now records the Unity main thread and active simulation thread IDs, marks the simulation thread on every `ExecuteTick(...)`, and guards live state accessors (`BallState`, `FlipperState`, `GetBallsInsideOf`, etc.). In external-timing mode, access from unsupported threads now throws immediately, while Unity main-thread access emits a one-time warning per accessor to surface remaining migration work without breaking validated gameplay paths all at once.
`DrainExternalThreadCallbacks()` now copies queued physics events and due scheduled actions into reusable local buffers while `PhysicsLock` is held, then releases the lock before invoking `_player.OnEvent(...)` and managed scheduled callbacks. This removes arbitrary managed callback work from the sim-thread critical section without changing callback ordering.
`ProcessInputActions(...)` now copies queued `InputAction` delegates into a reusable local buffer while holding `InputActionsLock`, then releases the lock before executing the actions. This keeps producer contention low while preserving ordering and existing behavior in both threaded and single-threaded modes.
`UpdateKinematicTransformsFromMainThread()` now gathers all changed kinematic matrices into a reusable local batch first, then acquires `PendingKinematicLock` once to publish the whole batch into `PendingKinematicTransforms`. This removes per-collider lock churn while preserving existing change detection and staging semantics.
`PhysicsEngine` now warns on large `InputActions` / `ScheduledActions` backlogs, `SimulationThread` warns when the external switch queue backs up or starts dropping events. Existing hard bounds on the external switch queue and simulation-coil queue remain in place.
`SimulationThread` now updates `SetTimeFence(...)` whenever the simulation clock advances, instead of throttling updates to every 5 ms. This effectively moves the fence to simulation-tick cadence while still avoiding duplicate calls if the simulation clock does not advance.
PinMAME no longer hard-codes the sim-thread fast path to `c_flipper*`; it now queries `Player` / `CoilPlayer` to see whether the mapped destination coil actually supports simulation-thread dispatch. As part of that, gate lifter coils were upgraded to provide simulation-thread callbacks in addition to flippers, so physics-relevant gate-lift collision changes can now take the low-latency path as well.
`SimulationThread.WriteSharedState()` now calls into a new `IGamelogicSharedStateWriter` capability, and `PinMameGamelogicEngine` snapshots its current coil, lamp, and GI state into the triple-buffered `SimulationState` each sim tick. The existing event-driven main-thread playback remains in place for now, so this adds coherent shared-state publication without yet replacing the legacy event consumers.
Implemented a hybrid leaning toward the snapshot model for threaded PinMAME. `SimulationThreadComponent.ApplySimulationState(...)` now calls an `IGamelogicSharedStateApplier` capability on the main thread, and `PinMameGamelogicEngine` uses the shared snapshot to drive lamp/GI playback once threaded shared-state application is active. Non-threaded mode keeps the legacy `OnLampChanged` polling path, and coil playback remains event-driven because coils need edge-preserving behavior rather than frame-latched latest-state playback.
Implemented a lower-risk first pass using stable per-type snapshot ID lists. `PhysicsEngineThreading` now precomputes the animatable item IDs for flippers, gates, plungers, spinners, animated targets, and bumper ring/skirt channels once at startup, then snapshots those lists directly each tick instead of re-enumerating every native hash map. Ball snapshotting still enumerates the live ball map because balls are created and destroyed at runtime, but the fixed animation channels now avoid repeated whole-map sweeps.
Implemented a min-heap scheduler keyed by simulation microseconds. `PhysicsEngine.ScheduleAction(...)` now pushes actions into a heap, and both threaded and single-threaded drains pop only the due actions instead of reverse-scanning and removing from a flat list each frame. This reduces scheduling overhead from O(n) whole-list scans toward O(log n) insertion/removal for the actions that actually matter.
`ExecuteTick()` ran under `PhysicsLock`, but `WriteSharedState()` copied ball animations after that lock had already been released. If teardown/dispose slipped into that gap, `SnapshotAnimations()` could enumerate a disposed `BallStates` map.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a dedicated simulation-thread architecture (external-timing physics + coordinated game logic timing) and a lock-free shared-state pipeline so Unity’s main thread focuses on input staging, callback draining, and snapshot application.
Changes:
- Added simulation thread runtime + triple-buffered
SimulationStatesnapshots, plus new input polling and dispatch infrastructure. - Refactored many device APIs/components to mutate physics state via the new threading-safe pathways and to avoid coroutines/critical-section work.
- Updated editor/patcher render-pipeline conversion plumbing and related prefab/material adapter infrastructure.
Reviewed changes
Copilot reviewed 94 out of 105 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs | Adds drain-kicker hit handling path for trough entry. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/SwitchAnimationComponent.cs | Replaces coroutine animation with Update-driven state machine. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs | Removes coroutine animation, adds mesh prewarm/caching + alloc reductions. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocityPhysics.cs | Adjusts PhysFactor math to use float literal. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs | Updates damping pow to use float PhysFactor. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs | Routes state mutation through PhysicsEngine.MutateState. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs | Uses cached instance item id for transform-driven updates. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs | Removes verbose debug logging from kick path. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs | Moves drop target state mutation into MutateState. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationComponent.cs | Replaces coroutines with Update-driven animation state machine. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocityPhysics.cs | Adjusts PhysFactor math to use float operations. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterApi.cs | Adds simulation-thread coil callbacks and collider id caching. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs | Updates damping pow to use float PhysFactor; removes debug log. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs | Moves lift state mutation into PhysicsEngine.MutateState. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs | Updates torque and momentum scaling to use float PhysFactor. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs | Hooks visual movement into InputLatencyTracker. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs | Adds sim-thread coil callbacks and shifts state writes to MutateState. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs | Switches octree population to PhysicsPopulate.Populate(...). |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs | Moves bumper coil/switch effects into MutateState, reduces allocations. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs | Updates gravity integration scaling to use float PhysFactor. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs | Switches to runtime-ball register/unregister API. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs | Removes UnityEngine dependency by using math vector ops. |
| VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs | Adds existence guard before reading ball state for debug draw. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/SimulationThreadComponent.cs.meta | Adds meta for new simulation-thread component. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/SimulationThreadComponent.cs | Adds Unity component to start/stop/sync sim thread and apply shared state. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/SimulationThread.cs.meta | Adds meta for simulation thread implementation. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/SimulationState.cs.meta | Adds meta for triple-buffered shared state. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/SimulationState.cs | Implements lock-free triple-buffered shared snapshot state. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/NativeInputManager.cs.meta | Adds meta for native input manager. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/NativeInputManager.cs | Adds native input polling manager + forwarding to simulation thread. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/NativeInputApi.cs.meta | Adds meta for native input P/Invoke API. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/NativeInputApi.cs | Adds P/Invoke wrapper types/enums for native input library. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/InputEventBuffer.cs.meta | Adds meta for input ring buffer. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/InputEventBuffer.cs | Adds SPSC ring buffer for input events. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/GamelogicInputDispatchers.cs.meta | Adds meta for gamelogic dispatchers. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation/GamelogicInputDispatchers.cs | Adds direct vs main-thread-queued switch dispatch for GLEs. |
| VisualPinball.Unity/VisualPinball.Unity/Simulation.meta | Adds folder meta for new Simulation folder. |
| VisualPinball.Unity/VisualPinball.Unity/Rendering/RenderPipeline.cs | Updates render pipeline discovery and adds Debug.Log tracing. |
| VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs | Adds marshal attribute for bool field; minor formatting. |
| VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs | Fixes allocator usage for tracked collider-id lists. |
| VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs | Routes key switch dispatch through Player.DispatchSwitch for threading model. |
| VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs | Routes switch events and scheduled pulses via Player.DispatchSwitch. |
| VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs | Adds sim-thread component integration + switch/coil dispatch helpers. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs | Removes old job-based physics update implementation. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdate.cs.meta | Adds meta for new PhysicsUpdate helper. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdate.cs | Adds burst-compiled static physics tick executor with substep cap + animation update. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs | Adds marshal attribute for bool field; minor formatting. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs | Removes old job wrapper for octree population. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulate.cs.meta | Adds meta for new octree populate helper. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulate.cs | Adds static burst-compiled octree populate helper (+ optional function pointer). |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs | Switches to persistent octree rebuild approach for kinematic colliders. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngineThreading.cs.meta | Adds meta for new engine threading layer. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngineContext.cs.meta | Adds meta for new engine context class. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngineContext.cs | Introduces shared context object for engine state/containers/locks. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs | Makes ball octree rebuilding allocation-free; optimizes overlap filtering. |
| VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs | Uses persistent ball octree instead of per-tick allocating a new one. |
| VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs | Adds lamp updates with explicit source parameter. |
| VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs | Reduces allocations and replaces List<int> with fixed list return. |
| VisualPinball.Unity/VisualPinball.Unity/Game/InputLatencyTracker.cs.meta | Adds meta for new latency tracker. |
| VisualPinball.Unity/VisualPinball.Unity/Game/InputLatencyTracker.cs | Adds latency tracking utilities for input → visual movement. |
| VisualPinball.Unity/VisualPinball.Unity/Game/FramePacingGraph.cs.meta | Adds meta for new pacing graph asset/script. |
| VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs | Adds threading/time-fence/shared-state capability interfaces + display colors. |
| VisualPinball.Unity/VisualPinball.Unity/Game/DisplayPlayer.cs | Applies optional lit/unlit display colors on display updates. |
| VisualPinball.Unity/VisualPinball.Unity/Game/DeviceCoil.cs | Adds sim-thread coil dispatch support and main-thread suppression logic. |
| VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs | Adds sim-thread coil dispatch path + memory barrier and reset logic. |
| VisualPinball.Unity/VisualPinball.Unity/Event/EventTranslateComponent.cs | Replaces coroutine translation animation with Update-driven logic. |
| VisualPinball.Unity/VisualPinball.Unity/Common/Math.cs | Replaces temp NativeArray sign-bit hack with math.asint bit check. |
| VisualPinball.Unity/VisualPinball.Unity.Patcher/VisualPinball.Unity.Patcher.csproj | Adds editor project reference for new converter APIs. |
| VisualPinball.Unity/VisualPinball.Unity.Patcher/VisualPinball.Unity.Patcher.asmdef | Updates assembly references for editor converter usage. |
| VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/TomAndJerry.cs | Switches material adapter calls to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Mississippi.cs | Switches material adapter calls to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/JurassicPark.cs | Switches material adapter calls to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Patcher/Matcher/TablePatcher.cs | Switches prefab provider calls to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef | Removes patcher reference from editor assembly. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trough/TroughExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperExtensions.cs | Switches prefab provider usage to RenderPipelineConverter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/PrefabReferenceRebinder.cs.meta | Adds meta for new prefab rebinder tool. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/PrefabReferenceRebinder.cs | Adds editor tool for replacing missing prefab instances. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard/StandardRenderPipeline.cs.meta | Adds meta for standard converter pipeline. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard/StandardRenderPipeline.cs | Adds Standard pipeline converter implementation. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard/StandardPrefabProvider.cs.meta | Adds meta for prefab provider. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard/StandardPrefabProvider.cs | Adds prefab provider for built-in pipeline conversion. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard/StandardMaterialAdapter.cs.meta | Adds meta for material adapter. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard/StandardMaterialAdapter.cs | Adds material adapter for built-in pipeline conversion. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/Standard.meta | Adds folder meta for Standard converter folder. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/RenderPipelineConverter.cs.meta | Adds meta for converter pipeline selector. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/RenderPipelineConverter.cs | Adds render-pipeline converter discovery/selection entrypoint. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/IPrefabProvider.cs.meta | Adds meta for prefab provider interface. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/IPrefabProvider.cs | Adds prefab provider interface for editor conversion pipeline. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/IMaterialAdapter.cs.meta | Adds meta for material adapter interface. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering/IMaterialAdapter.cs | Adds material adapter interface for editor conversion pipeline. |
| VisualPinball.Unity/VisualPinball.Unity.Editor/Rendering.meta | Adds folder meta for new Rendering folder in editor assembly. |
| VisualPinball.Unity/Assets/Resources/Prefabs/DefaultBall.prefab | Updates prefab serialization and adds BallComponent block. |
| VisualPinball.Engine/Common/Constants.cs | Changes PhysFactor to float and adds MaxSubSteps constant. |
Comments suppressed due to low confidence (1)
VisualPinball.Unity/VisualPinball.Unity/Event/EventTranslateComponent.cs:146
OnExit()is now empty, so whenTwoWayis enabled the component never plays the exit animation (coil de-energized path callsOnExit()). This looks like a regression from the coroutine-based implementation. Implement the exit animation by setting_animationDuration/_animationCurveto the exit values and animating back toward the initial position (or otherwise restoring the pre-enter state).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
VisualPinball.Unity/VisualPinball.Unity/Simulation/NativeInputManager.cs
Show resolved
Hide resolved
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.
This PR moves VPE from a mostly main-thread / partially lock-coordinated physics model to a dedicated external-timing simulation-thread architecture.
The simulation thread now owns the high-frequency physics tick and related gamelogic coordination, while the Unity main thread primarily:
To support that split, the PR introduces a clearer threading contract, a shared simulation-state pipeline, and a series of correctness/performance fixes around cross-thread communication and PinMAME integration.
What Changed
PinMAME
PinMAME integration now follows the same simulation-thread ownership model. Switch inputs are observed and forwarded from the simulation side, the emulation time fence is updated from the simulation clock, and coil outputs can be dispatched back into physics-visible device handlers without bouncing through the Unity main thread first. Lamp, GI, and other PinMAME-driven outputs are published as shared state and then consumed on the Unity side during snapshot application, so ROM-driven game logic stays tightly aligned with simulation time while the main thread remains focused on presentation.
Input Polling
Input handling was also reworked to match the new threading model. Instead of relying on Unity's frame-rate-bound input path for gameplay-critical controls, VPE now uses a dedicated native polling path that samples inputs independently of rendering and feeds them into the simulation thread through a low-overhead buffer/dispatcher pipeline. That means flipper and switch inputs are captured closer to real time, processed on the same timing model as physics and PinMAME, and no longer have to wait for the next Unity frame before entering the simulation.
The Graphs
The main goal was latency reduction. Green line is between input capture to flipper transformation (right before frame gets rendered). This is during T2 gameplay.
Unity Profiler: