Skip to content

Add support for PWM coils and multiple DMD frame formats.#38

Merged
freezy merged 4 commits into
masterfrom
dmd
Jul 6, 2026
Merged

Add support for PWM coils and multiple DMD frame formats.#38
freezy merged 4 commits into
masterfrom
dmd

Conversation

@freezy

@freezy freezy commented Jul 6, 2026

Copy link
Copy Markdown
Member

No description provided.

freezy added 3 commits June 27, 2026 17:40
Implement IDisplayFrameFormatPreference and IRomNameProvider on the
PinMAME engine. Clients can now request a DMD frame format (2/4/8 bit),
which maps to PinMAME's raw vs. brightness DMD mode via reflection and
selects the reported frame format accordingly.

Snapshot each DMD and segmented display frame into a fresh array before
queueing it for the main thread, so a follow-up native callback can no
longer overwrite or tear a frame that is still being read.

Drop frames with a null native pointer instead of marshalling from
IntPtr.Zero, logging a warning when it happens.
The solenoid callback now carries the raw PinMAME value (0/1 for binary
coils, 0-255 for PWM-integrated/modulated coils on WPC/SAM ROMs). Detect
the modulated case (any value > 1) and normalize to a [0..1] strength for
the coil pipeline's OnCoilChanged, so magnet coils that the ROM PWMs (e.g.
Iron Man) drive a proportional pull. The sim-thread dispatch and shared
snapshot stay boolean on/off.

Requires the pinmame-dotnet feature/modulated-coils build (PinMameDotNet.dll
carrying the int solenoid value); ship as a PinMame NuGet bump.
1.0.3 carries the solenoid value through OnSolenoidUpdated (int, not a
collapsed bool), which the modulated-coil strength path depends on. Native
stays 3.7.0-beta1 (unchanged).
@freezy freezy self-assigned this Jul 6, 2026
@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds two major features to the PinMAME game logic engine: PWM (modulated) coil support and multi-format DMD frame output. The solenoid callback signature changes from bool to int, a _solenoidsModulated flag normalises values to 0–1, and the new IDisplayFrameFormatPreference / IRomNameProvider interfaces allow display consumers to request Dmd2/Dmd4/Dmd8 frame formats at runtime. It also replaces the previously reflection-based DMD mode API with a direct _pinMame.SetDmdMode() call available in PinMAME 1.0.4, and fixes a data-race where shared _frameBuffer entries were mutated while already queued for the main-thread dispatch.

  • PWM coils: OnSolenoidUpdated now receives int value (0–255); a volatile bool _solenoidsModulated latch upgrades the whole coil pipeline to normalised floats once any value exceeds 1. The latch is correctly reset under _outputStateLock in both Stop and OnGameEnded.
  • DMD frame formats: New RequestDisplayFrameFormat method writes the requested format to volatile DisplayFrameFormat _requestedDmdFrameFormat and immediately applies the raw/brightness mode to PinMAME. GetRequestedDmdDisplayFrameFormat derives the actual per-frame format from layout metadata when raw mode is active.
  • Frame snapshot fix: Both UpdateDmd and UpdateSegDisp now copy native data into a freshly allocated array before enqueueing, preventing the native callback from overwriting data already dispatched to the main thread.

Confidence Score: 5/5

The change is safe to merge: cross-thread fields are properly marked volatile, the data-race in the frame-buffer path is fixed by snapshotting to a fresh array, null-pointer guards are in place, and the reflection workaround is replaced with a direct public API call.

All changed code paths are logically sound. The volatile fields prevent visibility issues, the frame snapshot avoids torn reads, and the solenoid normalisation handles both binary and PWM coil values correctly. The only note is a minor style inconsistency in the copy-count argument for seg displays.

No files require special attention.

Important Files Changed

Filename Overview
VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs Core engine changes: PWM coil normalisation, DMD frame-format preference interface, frame snapshot fix, IntPtr.Zero guards, and correct volatile usage on cross-thread fields.
VisualPinball.Engine.PinMAME/VisualPinball.Engine.PinMAME.csproj PinMAME library bumped from 1.0.2 to 1.0.4 to gain the public SetDmdMode API that replaces the old reflection-based workaround.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Display consumer calls RequestDisplayFrameFormat] --> B{format is Dmd2 or Dmd4?}
    B -- Yes --> C[_requestedDmdFrameFormat = format\nApplyDmdModePreference]
    B -- No, Dmd8 --> D[_requestedDmdFrameFormat = Dmd8\nApplyDmdModePreference]
    B -- Other --> E[Return early — not a DMD format]

    C --> F[_pinMame.SetDmdMode Raw]
    D --> G[_pinMame.SetDmdMode Brightness]

    H[PinMAME native callback\nOnDisplayUpdated] --> I{displayLayout.IsDmd?}
    I -- Yes --> J[UpdateDmd]
    I -- No --> K[UpdateSegDisp]

    J --> L{_requestedDmdFrameFormat == Dmd8?}
    L -- Yes --> M[Report format = Dmd8]
    L -- No --> N[GetRawDmdDisplayFrameFormat\nbased on layout.Levels and layout.Depth]
    N --> O{Levels ≤ 4 or Depth ≤ 2}
    O -- Yes --> P[Report format = Dmd2]
    O -- No --> Q{Levels ≤ 16 or Depth ≤ 4}
    Q -- Yes --> R[Report format = Dmd4]
    Q -- No --> S[Report format = Dmd8]

    M & P & R & S --> T[Snapshot to fresh byte-array\nEnqueue OnDisplayUpdateFrame]
    K --> U[GetDisplayFrameFormat\nEnqueue OnDisplayUpdateFrame]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Display consumer calls RequestDisplayFrameFormat] --> B{format is Dmd2 or Dmd4?}
    B -- Yes --> C[_requestedDmdFrameFormat = format\nApplyDmdModePreference]
    B -- No, Dmd8 --> D[_requestedDmdFrameFormat = Dmd8\nApplyDmdModePreference]
    B -- Other --> E[Return early — not a DMD format]

    C --> F[_pinMame.SetDmdMode Raw]
    D --> G[_pinMame.SetDmdMode Brightness]

    H[PinMAME native callback\nOnDisplayUpdated] --> I{displayLayout.IsDmd?}
    I -- Yes --> J[UpdateDmd]
    I -- No --> K[UpdateSegDisp]

    J --> L{_requestedDmdFrameFormat == Dmd8?}
    L -- Yes --> M[Report format = Dmd8]
    L -- No --> N[GetRawDmdDisplayFrameFormat\nbased on layout.Levels and layout.Depth]
    N --> O{Levels ≤ 4 or Depth ≤ 2}
    O -- Yes --> P[Report format = Dmd2]
    O -- No --> Q{Levels ≤ 16 or Depth ≤ 4}
    Q -- Yes --> R[Report format = Dmd4]
    Q -- No --> S[Report format = Dmd8]

    M & P & R & S --> T[Snapshot to fresh byte-array\nEnqueue OnDisplayUpdateFrame]
    K --> U[GetDisplayFrameFormat\nEnqueue OnDisplayUpdateFrame]
Loading

Reviews (2): Last reviewed commit: "pinmame: Use public SetDmdMode API and b..." | Re-trigger Greptile

Comment thread VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs Outdated
Comment thread VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs Outdated
Comment thread VisualPinball.Engine.PinMAME.Unity/Runtime/PinMameGamelogicEngine.cs Outdated
The 1.0.4 binding exposes SetDmdMode publicly, so drop the
reflection-based fallback and call it directly. Guard against a
null PinMame instance and mark the DMD format and modulation
flags volatile since they're read across threads.
@freezy freezy merged commit 6f79d38 into master Jul 6, 2026
15 checks passed
@freezy freezy deleted the dmd branch July 6, 2026 16:25
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.

1 participant