Skip to content

Commit bcba911

Browse files
committed
fix: Switch dispatch issues.
1 parent d7734d8 commit bcba911

10 files changed

Lines changed: 421 additions & 134 deletions

File tree

VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ private void AssignCoilMapping(CoilMapping coilMapping, bool isLampCoil)
120120
}
121121
}
122122

123-
private void AssignCoilMapping(string id, CoilMapping coilMapping, bool isLampCoil)
124-
{
123+
private void AssignCoilMapping(string id, CoilMapping coilMapping, bool isLampCoil)
124+
{
125125
if (!_coilAssignments.ContainsKey(id)) {
126126
_coilAssignments[id] = new List<CoilDestConfig>();
127127
}
@@ -130,20 +130,19 @@ private void AssignCoilMapping(string id, CoilMapping coilMapping, bool isLampCo
130130
w.DestinationDeviceItem == coilMapping.DeviceItem &&
131131
w.IsDynamic) != null;
132132

133-
_coilAssignments[id].Add(new CoilDestConfig(coilMapping.Device, coilMapping.DeviceItem, isLampCoil, hasDynamicWire));
134-
CoilStatuses[id] = false;
135-
}
136-
137-
private void HandleCoilEvent(object sender, CoilEventArgs coilEvent)
138-
{
139-
// always save state
140-
CoilStatuses[coilEvent.Id] = coilEvent.IsEnabled;
133+
_coilAssignments[id].Add(new CoilDestConfig(coilMapping.Device, coilMapping.DeviceItem, isLampCoil, hasDynamicWire));
134+
CoilStatuses[id] = false;
135+
}
136+
137+
private void HandleCoilEvent(object sender, CoilEventArgs coilEvent)
138+
{
139+
// always save state
140+
CoilStatuses[coilEvent.Id] = coilEvent.IsEnabled;
141141

142142
// trigger coil if mapped
143-
if (_coilAssignments.ContainsKey(coilEvent.Id)) {
144-
foreach (var destConfig in _coilAssignments[coilEvent.Id]) {
145-
146-
if (destConfig.HasDynamicWire) {
143+
if (_coilAssignments.ContainsKey(coilEvent.Id)) {
144+
foreach (var destConfig in _coilAssignments[coilEvent.Id]) {
145+
if (destConfig.HasDynamicWire) {
147146
// goes back through the wire mapping, which will decide whether it has already sent the event or not
148147
_wirePlayer!.HandleCoilEvent(coilEvent.Id, coilEvent.IsEnabled);
149148
continue;

VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,8 @@ CancellationToken ct
140140
/// Sometimes we want to extend an existing GLE with other components. This
141141
/// API allows other components to be able to drive the GLE.
142142
/// </summary>
143-
public interface IGamelogicBridge
144-
{
143+
public interface IGamelogicBridge
144+
{
145145
void SetCoil(string id, bool isEnabled);
146146

147147
void SetLamp(string id, float value, bool isCoil = false, LampSource source = LampSource.Lamp);
@@ -152,8 +152,23 @@ public interface IGamelogicBridge
152152

153153
public event EventHandler<SwitchEventArgs2> OnSwitchChanged;
154154

155-
// todo displays
156-
}
155+
// todo displays
156+
}
157+
158+
public enum GamelogicInputDispatchMode
159+
{
160+
MainThread = 0,
161+
SimulationThread = 1,
162+
}
163+
164+
/// <summary>
165+
/// Optional capability interface for game logic engines that can safely
166+
/// receive switch updates from the simulation thread.
167+
/// </summary>
168+
public interface IGamelogicInputThreading
169+
{
170+
GamelogicInputDispatchMode SwitchDispatchMode { get; }
171+
}
157172

158173
public class RequestedDisplays
159174
{

VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,11 @@
2222
using Unity.Mathematics;
2323
using UnityEngine;
2424
using UnityEngine.InputSystem;
25-
using UnityEngine.Serialization;
26-
using VisualPinball.Engine.Common;
27-
using VisualPinball.Engine.Game;
28-
using VisualPinball.Engine.Game.Engines;
25+
using UnityEngine.Serialization;
26+
using VisualPinball.Engine.Common;
27+
using VisualPinball.Engine.Game;
28+
using VisualPinball.Engine.Game.Engines;
29+
using VisualPinball.Unity.Simulation;
2930
using Color = VisualPinball.Engine.Math.Color;
3031
using Logger = NLog.Logger;
3132

@@ -107,11 +108,12 @@ public void UnpackReferences(byte[] data, Transform root, PackagedRefs refs, Pac
107108
private const float SlowMotionMax = 0.1f;
108109
private const float TimeLapseMax = 2.5f;
109110

110-
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
111+
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
111112
private TableComponent _tableComponent;
112113
private PlayfieldComponent _playfieldComponent;
113-
private PhysicsEngine _physicsEngine;
114-
private CancellationTokenSource _gamelogicEngineInitCts;
114+
private PhysicsEngine _physicsEngine;
115+
private SimulationThreadComponent _simulationThreadComponent;
116+
private CancellationTokenSource _gamelogicEngineInitCts;
115117

116118
private PlayfieldComponent PlayfieldComponent {
117119
get {
@@ -122,22 +124,39 @@ private PlayfieldComponent PlayfieldComponent {
122124
}
123125
}
124126

125-
private PhysicsEngine PhysicsEngine {
127+
private PhysicsEngine PhysicsEngine {
126128
get {
127129
if (_physicsEngine == null) {
128130
_physicsEngine = GetComponentInChildren<PhysicsEngine>();
129131
}
130132
return _physicsEngine;
131133
}
132-
}
134+
}
135+
136+
private SimulationThreadComponent SimulationThreadComponent {
137+
get {
138+
if (_simulationThreadComponent == null) {
139+
_simulationThreadComponent = GetComponent<SimulationThreadComponent>();
140+
}
141+
return _simulationThreadComponent;
142+
}
143+
}
133144

134145
#region Access
135146

136147
internal IApiSwitch Switch(ISwitchDeviceComponent component, string switchItem) => component != null ? _switchPlayer.Switch(component, switchItem) : null;
137148
public IApiCoil Coil(ICoilDeviceComponent component, string coilItem) => component != null ? _coilPlayer.Coil(component, coilItem) : null;
138149
public IApiLamp Lamp(ILampDeviceComponent component) => component != null ? _lampPlayer.Lamp(component) : null;
139150
public IApiWireDeviceDest WireDevice(IWireableComponent c) => _wirePlayer.WireDevice(c);
140-
internal void HandleWireSwitchChange(WireDestConfig wireConfig, bool isEnabled) => _wirePlayer.HandleSwitchChange(wireConfig, isEnabled);
151+
internal void HandleWireSwitchChange(WireDestConfig wireConfig, bool isEnabled) => _wirePlayer.HandleSwitchChange(wireConfig, isEnabled);
152+
153+
internal void DispatchSwitch(string switchId, bool isClosed)
154+
{
155+
if (SimulationThreadComponent != null && SimulationThreadComponent.EnqueueSwitchFromMainThread(switchId, isClosed)) {
156+
return;
157+
}
158+
GamelogicEngine?.Switch(switchId, isClosed);
159+
}
141160

142161
public Dictionary<string, IApiSwitchStatus> SwitchStatuses => _switchPlayer.SwitchStatuses;
143162
public Dictionary<string, bool> CoilStatuses => _coilPlayer.CoilStatuses;
@@ -170,7 +189,7 @@ private void Awake()
170189
GamelogicEngine = engineComponent;
171190
_lampPlayer.Awake(this, _tableComponent, GamelogicEngine);
172191
_coilPlayer.Awake(this, _tableComponent, GamelogicEngine, _lampPlayer, _wirePlayer);
173-
_switchPlayer.Awake(_tableComponent, GamelogicEngine, _inputManager);
192+
_switchPlayer.Awake(this, _tableComponent, GamelogicEngine, _inputManager);
174193
_wirePlayer.Awake(_tableComponent, _inputManager, _switchPlayer, this, PhysicsEngine);
175194
_displayPlayer.Awake(GamelogicEngine);
176195
}
@@ -297,15 +316,15 @@ public void Register<TApi>(TApi api, MonoBehaviour component) where TApi : IApi
297316
}
298317
}
299318

300-
private void RegisterCollider(int itemId, IApiColliderGenerator apiColl)
301-
{
302-
if (!apiColl.IsColliderAvailable) {
303-
return;
304-
}
305-
_colliderGenerators.Add(apiColl);
306-
if (apiColl is IApiHittable apiHittable) {
307-
_hittables[itemId] = apiHittable;
308-
}
319+
private void RegisterCollider(int itemId, IApiColliderGenerator apiColl)
320+
{
321+
if (!apiColl.IsColliderAvailable) {
322+
return;
323+
}
324+
_colliderGenerators.Add(apiColl);
325+
if (apiColl is IApiHittable apiHittable) {
326+
_hittables[itemId] = apiHittable;
327+
}
309328

310329
if (apiColl is IApiCollidable apiCollidable) {
311330
_collidables[itemId] = apiCollidable;
@@ -319,20 +338,19 @@ private void RegisterCollider(int itemId, IApiColliderGenerator apiColl)
319338
public void ScheduleAction(int timeMs, Action action) => PhysicsEngine.ScheduleAction(timeMs, action);
320339
public void ScheduleAction(uint timeMs, Action action) => PhysicsEngine.ScheduleAction(timeMs, action);
321340

322-
public void OnEvent(in EventData eventData)
323-
{
324-
Debug.Log(eventData);
325-
switch (eventData.EventId) {
326-
case EventId.HitEventsHit:
327-
if (!_hittables.ContainsKey(eventData.ItemId)) {
328-
Debug.LogError($"Cannot find {eventData.ItemId} in hittables.");
329-
}
330-
_hittables[eventData.ItemId].OnHit(eventData.BallId);
331-
break;
332-
333-
case EventId.HitEventsUnhit:
334-
_hittables[eventData.ItemId].OnHit(eventData.BallId, true);
335-
break;
341+
public void OnEvent(in EventData eventData)
342+
{
343+
switch (eventData.EventId) {
344+
case EventId.HitEventsHit:
345+
if (!_hittables.ContainsKey(eventData.ItemId)) {
346+
Debug.LogError($"Cannot find {eventData.ItemId} in hittables.");
347+
}
348+
_hittables[eventData.ItemId].OnHit(eventData.BallId);
349+
break;
350+
351+
case EventId.HitEventsUnhit:
352+
_hittables[eventData.ItemId].OnHit(eventData.BallId, true);
353+
break;
336354

337355
case EventId.LimitEventsBos:
338356
_rotatables[eventData.ItemId].OnRotate(eventData.FloatParam, false);

VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,21 +128,22 @@ public bool HasWireDest(IWireableComponent device, string deviceItem)
128128
internal void OnSwitch(bool enabled)
129129
{
130130
// handle switch -> gamelogic engine
131-
if (Engine != null && _switches != null) {
132-
foreach (var switchConfig in _switches) {
133-
134-
// set new status now
135-
_switchStatuses[switchConfig.SwitchId].IsSwitchEnabled = enabled;
136-
Engine.Switch(switchConfig.SwitchId, switchConfig.IsNormallyClosed ? !enabled : enabled);
131+
if (Engine != null && _switches != null) {
132+
foreach (var switchConfig in _switches) {
133+
var isClosed = switchConfig.IsNormallyClosed ? !enabled : enabled;
134+
135+
// set new status now
136+
_switchStatuses[switchConfig.SwitchId].IsSwitchEnabled = enabled;
137+
_player.DispatchSwitch(switchConfig.SwitchId, isClosed);
137138

138139
// if it's pulse, schedule to re-open
139-
if (enabled && switchConfig.IsPulseSwitch) {
140-
_physicsEngine.ScheduleAction(
141-
switchConfig.PulseDelay,
142-
() => {
143-
_switchStatuses[switchConfig.SwitchId].IsSwitchEnabled = false;
144-
Engine.Switch(switchConfig.SwitchId, switchConfig.IsNormallyClosed);
145-
IsEnabled = false;
140+
if (enabled && switchConfig.IsPulseSwitch) {
141+
_physicsEngine.ScheduleAction(
142+
switchConfig.PulseDelay,
143+
() => {
144+
_switchStatuses[switchConfig.SwitchId].IsSwitchEnabled = false;
145+
_player.DispatchSwitch(switchConfig.SwitchId, switchConfig.IsNormallyClosed);
146+
IsEnabled = false;
146147
#if UNITY_EDITOR
147148
RefreshUI();
148149
#endif
@@ -170,11 +171,12 @@ internal void OnSwitch(bool enabled)
170171
internal void ScheduleSwitch(bool enabled, int delay, Action<bool> onSwitched)
171172
{
172173
// handle switch -> gamelogic engine
173-
if (Engine != null && _switches != null) {
174-
foreach (var switchConfig in _switches) {
175-
_physicsEngine.ScheduleAction(delay,
176-
() => Engine.Switch(switchConfig.SwitchId, switchConfig.IsNormallyClosed ? !enabled : enabled));
177-
}
174+
if (Engine != null && _switches != null) {
175+
foreach (var switchConfig in _switches) {
176+
var isClosed = switchConfig.IsNormallyClosed ? !enabled : enabled;
177+
_physicsEngine.ScheduleAction(delay,
178+
() => _player.DispatchSwitch(switchConfig.SwitchId, isClosed));
179+
}
178180
} else {
179181
Logger.Warn("Cannot schedule device switch.");
180182
}

VisualPinball.Unity/VisualPinball.Unity/Game/SwitchPlayer.cs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ public class SwitchPlayer
3838
/// </summary>
3939
private readonly Dictionary<string, List<KeyboardSwitch>> _keySwitchAssignments = new();
4040

41-
private TableComponent _tableComponent;
42-
private IGamelogicEngine _gamelogicEngine;
43-
private InputManager _inputManager;
41+
private TableComponent _tableComponent;
42+
private Player _player;
43+
private IGamelogicEngine _gamelogicEngine;
44+
private InputManager _inputManager;
4445

4546
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
4647

@@ -51,11 +52,12 @@ internal void RegisterSwitchDevice(ISwitchDeviceComponent component, IApiSwitchD
5152
public bool SwitchDeviceExists(ISwitchDeviceComponent component)
5253
=> _switchDevices.ContainsKey(component);
5354

54-
public void Awake(TableComponent tableComponent, IGamelogicEngine gamelogicEngine, InputManager inputManager)
55-
{
56-
_tableComponent = tableComponent;
57-
_gamelogicEngine = gamelogicEngine;
58-
_inputManager = inputManager;
55+
public void Awake(Player player, TableComponent tableComponent, IGamelogicEngine gamelogicEngine, InputManager inputManager)
56+
{
57+
_player = player;
58+
_tableComponent = tableComponent;
59+
_gamelogicEngine = gamelogicEngine;
60+
_inputManager = inputManager;
5961
}
6062

6163
public void OnStart()
@@ -76,19 +78,23 @@ public void OnStart()
7678
break;
7779
}
7880

79-
// check if device exists
80-
if (!_switchDevices.TryGetValue(switchMapping.Device, out var device)) {
81-
Logger.Error($"Unknown switch device \"{switchMapping.Device}\".");
82-
break;
83-
}
81+
// check if device exists
82+
if (!_switchDevices.TryGetValue(switchMapping.Device, out var device)) {
83+
if (switchMapping.Device is IMechHandler) {
84+
Logger.Info($"Switch \"{switchMapping.Id}\" on mech device \"{switchMapping.Device}\" is handled by mech config and does not require runtime switch registration.");
85+
break;
86+
}
87+
Logger.Error($"Unknown switch device \"{switchMapping.Device}\".");
88+
break;
89+
}
8490

8591
var deviceSwitch = device.Switch(switchMapping.DeviceItem);
8692
if (deviceSwitch != null) {
87-
var existingSwitchStatus = SwitchStatuses.ContainsKey(switchMapping.Id) ? SwitchStatuses[switchMapping.Id] : null;
88-
var switchStatus = deviceSwitch.AddSwitchDest(new SwitchConfig(switchMapping), existingSwitchStatus);
89-
SwitchStatuses[switchMapping.Id] = switchStatus;
90-
91-
} else {
93+
var existingSwitchStatus = SwitchStatuses.ContainsKey(switchMapping.Id) ? SwitchStatuses[switchMapping.Id] : null;
94+
var switchStatus = deviceSwitch.AddSwitchDest(new SwitchConfig(switchMapping), existingSwitchStatus);
95+
SwitchStatuses[switchMapping.Id] = switchStatus;
96+
97+
} else {
9298
Logger.Error($"Unknown switch \"{switchMapping.DeviceItem}\" in switch device \"{switchMapping.Device}\".");
9399
}
94100

@@ -126,13 +132,13 @@ private void HandleKeyInput(object obj, InputActionChange change)
126132
case InputActionChange.ActionStarted:
127133
case InputActionChange.ActionCanceled:
128134
var action = (InputAction)obj;
129-
if (_keySwitchAssignments.TryGetValue(action.name, out var assignment)) {
130-
if (_gamelogicEngine != null) {
131-
foreach (var sw in assignment) {
132-
sw.IsSwitchEnabled = change == InputActionChange.ActionStarted;
133-
_gamelogicEngine.Switch(sw.SwitchId, sw.IsSwitchClosed);
134-
}
135-
}
135+
if (_keySwitchAssignments.TryGetValue(action.name, out var assignment)) {
136+
if (_player != null) {
137+
foreach (var sw in assignment) {
138+
sw.IsSwitchEnabled = change == InputActionChange.ActionStarted;
139+
_player.DispatchSwitch(sw.SwitchId, sw.IsSwitchClosed);
140+
}
141+
}
136142
} else {
137143
Logger.Info($"Unmapped input command \"{action.name}\".");
138144
}

0 commit comments

Comments
 (0)