From 07dc8d9efe15aac295e1631706708776860df713 Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 16 Jul 2024 22:06:01 +0200 Subject: [PATCH] Fix mpf numbers --- .../Runtime/MpfExtensions.cs | 38 +++++- .../Runtime/MpfGamelogicEngine.cs | 109 +++++++++--------- .../Runtime/MpfNameNumberDictionary.cs | 102 ++++++++++++++++ .../Runtime/MpfNameNumberDictionary.cs.meta | 11 ++ 4 files changed, 205 insertions(+), 55 deletions(-) create mode 100644 VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs create mode 100644 VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs.meta diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs index 039cb64c..05e675f4 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs @@ -105,5 +105,41 @@ public static IEnumerable GetLights(this MachineDescription // todo color return md.Lights.Select(light => new GamelogicEngineLamp(light.Name)); } - } + + public static Dictionary GetSwitchNumbersByNameDict(this MachineDescription md) + { + Dictionary ret = new(); + + foreach (SwitchDescription sw in md.Switches) + { + ret[sw.Name] = sw.HardwareNumber; + } + + return ret; + } + + public static Dictionary GetCoilNumbersByNameDict(this MachineDescription md) + { + Dictionary ret = new(); + + foreach (CoilDescription coil in md.Coils) + { + ret[coil.Name] = coil.HardwareNumber; + } + + return ret; + } + + public static Dictionary GetLampNumbersByNameDict(this MachineDescription md) + { + Dictionary ret = new(); + + foreach (LightDescription light in md.Lights) + { + ret[light.Name] = light.HardwareChannelColor; + } + + return ret; + } + } } diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 442e0279..9a9b0dd0 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -53,13 +53,16 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine [SerializeField] private GamelogicEngineLamp[] requiredLamps = Array.Empty(); [SerializeField] private GamelogicEngineWire[] availableWires = Array.Empty(); - private Player _player; - private Dictionary _switchIds = new Dictionary(); - private Dictionary _switchNames = new Dictionary(); - private Dictionary _coilNames = new Dictionary(); - private Dictionary _lampNames = new Dictionary(); + // MPF uses names and numbers (for hardware mapping) to identify switches, coils, and lamps. + // VPE only uses names, which is why the classes in the arrays above do not store the numbers. + // These dictionaries store the numbers externally to make communication with MPF possible. + [SerializeField] private MpfNameNumberDictionary _mpfSwitchNumbers = new(); + [SerializeField] private MpfNameNumberDictionary _mpfCoilNumbers = new(); + [SerializeField] private MpfNameNumberDictionary _mpfLampNumbers = new(); - private bool _displaysAnnounced; + private Player _player; + + private bool _displaysAnnounced; private readonly Queue _dispatchQueue = new Queue(); @@ -68,18 +71,6 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine public void OnInit(Player player, TableApi tableApi, BallManager ballManager) { _player = player; - _switchIds.Clear(); - foreach (var sw in requiredSwitches) { - _switchNames[sw.Id] = sw.Id; - } - _coilNames.Clear(); - foreach (var coil in requiredCoils) { - _coilNames[coil.Id] = coil.Id; - } - _lampNames.Clear(); - foreach (var lamp in requiredLamps) { - _lampNames[lamp.Id] = lamp.Id; - } _api = new MpfApi(machineFolder); _api.Launch(new MpfConsoleOptions { ShowLogInsteadOfConsole = false, @@ -98,8 +89,8 @@ public void OnInit(Player player, TableApi tableApi, BallManager ballManager) // map initial switches var mappedSwitchStatuses = new Dictionary(); foreach (var swName in player.SwitchStatuses.Keys) { - if (_switchIds.ContainsKey(swName)) { - mappedSwitchStatuses[_switchIds[swName].ToString()] = player.SwitchStatuses[swName].IsSwitchClosed; + if (_mpfSwitchNumbers.ContainsName(swName)) { + mappedSwitchStatuses[_mpfSwitchNumbers.GetNumberByName(swName)] = player.SwitchStatuses[swName].IsSwitchClosed; } else { Logger.Warn($"Unknown intial switch name \"{swName}\"."); } @@ -121,9 +112,10 @@ private void Update() public void Switch(string id, bool isClosed) { - if (_switchIds.ContainsKey(id)) { - Logger.Info($"--> switch {id} ({_switchIds[id]}): {isClosed}"); - _api.Switch(_switchIds[id].ToString(), isClosed); + if (_mpfSwitchNumbers.ContainsName(id)) { + var number = _mpfSwitchNumbers.GetNumberByName(id); + Logger.Info($"--> switch {id} ({number}): {isClosed}"); + _api.Switch(number, isClosed); } else { Logger.Error("Unmapped MPF switch " + id); } @@ -147,6 +139,9 @@ public void GetMachineDescription() requiredSwitches = md.GetSwitches().ToArray(); requiredCoils = md.GetCoils().ToArray(); requiredLamps = md.GetLights().ToArray(); + _mpfSwitchNumbers.Init(md.GetSwitchNumbersByNameDict()); + _mpfCoilNumbers.Init(md.GetCoilNumbersByNameDict()); + _mpfLampNumbers.Init(md.GetLampNumbersByNameDict()); } } @@ -177,9 +172,10 @@ public bool GetCoil(string id) private void OnEnableCoil(object sender, EnableCoilRequest e) { - if (_coilNames.ContainsKey(e.CoilNumber)) { - Logger.Info($"<-- coil {e.CoilNumber} ({_coilNames[e.CoilNumber]}): true"); - _player.Queue(() => OnCoilChanged?.Invoke(this, new CoilEventArgs(_coilNames[e.CoilNumber], true))); + if (_mpfCoilNumbers.ContainsNumber(e.CoilNumber)) { + var coilName = _mpfCoilNumbers.GetNameByNumber(e.CoilNumber); + Logger.Info($"<-- coil {e.CoilNumber} ({coilName}): true"); + _player.ScheduleAction(1, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(coilName, true))); } else { Logger.Error("Unmapped MPF coil " + e.CoilNumber); } @@ -187,9 +183,10 @@ private void OnEnableCoil(object sender, EnableCoilRequest e) private void OnDisableCoil(object sender, DisableCoilRequest e) { - if (_coilNames.ContainsKey(e.CoilNumber)) { - Logger.Info($"<-- coil {e.CoilNumber} ({_coilNames[e.CoilNumber]}): false"); - _player.Queue(() => OnCoilChanged?.Invoke(this, new CoilEventArgs(_coilNames[e.CoilNumber], false))); + if (_mpfCoilNumbers.ContainsNumber(e.CoilNumber)) { + var coilName = _mpfCoilNumbers.GetNameByNumber(e.CoilNumber); + Logger.Info($"<-- coil {e.CoilNumber} ({coilName}): false"); + _player.ScheduleAction(1, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(coilName, false))); } else { Logger.Error("Unmapped MPF coil " + e.CoilNumber); } @@ -197,15 +194,14 @@ private void OnDisableCoil(object sender, DisableCoilRequest e) private void OnPulseCoil(object sender, PulseCoilRequest e) { - if (_coilNames.ContainsKey(e.CoilNumber)) { - var coilId = _coilNames[e.CoilNumber]; - _player.ScheduleAction(e.PulseMs * 10, () => { - Logger.Info($"<-- coil {coilId} ({e.CoilNumber}): false (pulse)"); - OnCoilChanged?.Invoke(this, new CoilEventArgs(coilId, false)); + if (_mpfCoilNumbers.ContainsNumber(e.CoilNumber)) { + var coilName = _mpfCoilNumbers.GetNameByNumber(e.CoilNumber); + _player.ScheduleAction(e.PulseMs * 10, () => { + Logger.Info($"<-- coil {coilName} ({e.CoilNumber}): false (pulse)"); + OnCoilChanged?.Invoke(this, new CoilEventArgs(coilName, false)); }); - Logger.Info($"<-- coil {e.CoilNumber} ({coilId}): true (pulse {e.PulseMs}ms)"); - _player.Queue(() => OnCoilChanged?.Invoke(this, new CoilEventArgs(coilId, true))); - + Logger.Info($"<-- coil {e.CoilNumber} ({coilName}): true (pulse {e.PulseMs}ms)"); + _player.ScheduleAction(1, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(coilName, true))); } else { Logger.Error("Unmapped MPF coil " + e.CoilNumber); } @@ -215,8 +211,9 @@ private void OnFadeLight(object sender, FadeLightRequest e) { var args = new List(); foreach (var fade in e.Fades) { - if (_lampNames.ContainsKey(fade.LightNumber)) { - args.Add(new LampEventArgs(_lampNames[fade.LightNumber], fade.TargetBrightness)); + if (_mpfLampNumbers.ContainsNumber(fade.LightNumber)) { + var lampName = _mpfLampNumbers.GetNameByNumber(fade.LightNumber); + args.Add(new LampEventArgs(lampName, fade.TargetBrightness)); } else { Logger.Error("Unmapped MPF lamp " + fade.LightNumber); } @@ -228,32 +225,36 @@ private void OnFadeLight(object sender, FadeLightRequest e) private void OnConfigureHardwareRule(object sender, ConfigureHardwareRuleRequest e) { - if (!_switchNames.ContainsKey(e.SwitchNumber)) { + if (!_mpfSwitchNumbers.ContainsNumber(e.SwitchNumber)) { Logger.Error("Unmapped MPF switch " + e.SwitchNumber); return; } - if (!_coilNames.ContainsKey(e.CoilNumber)) { + if (!_mpfCoilNumbers.ContainsNumber(e.CoilNumber)) { Logger.Error("Unmapped MPF coil " + e.CoilNumber); return; } - _player.Queue(() => _player.AddHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); - Logger.Info($"<-- new hardware rule: {_switchNames[e.SwitchNumber]} -> {_coilNames[e.CoilNumber]}."); + var switchName = _mpfSwitchNumbers.GetNameByNumber(e.SwitchNumber); + var coilName = _mpfCoilNumbers.GetNameByNumber(e.CoilNumber); + _player.ScheduleAction(1, () => _player.AddHardwareRule(switchName, coilName)); + Logger.Info($"<-- new hardware rule: {switchName} -> {coilName}."); } private void OnRemoveHardwareRule(object sender, RemoveHardwareRuleRequest e) { - if (!_switchNames.ContainsKey(e.SwitchNumber)) { - Logger.Error("Unmapped MPF coil " + e.SwitchNumber); - return; - } - if (!_coilNames.ContainsKey(e.CoilNumber)) { - Logger.Error("Unmapped MPF coil " + e.CoilNumber); - return; - } - - _player.Queue(() => _player.RemoveHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); - Logger.Info($"<-- remove hardware rule: {_switchNames[e.SwitchNumber]} -> {_coilNames[e.CoilNumber]}."); + if (!_mpfSwitchNumbers.ContainsNumber(e.SwitchNumber)) { + Logger.Error("Unmapped MPF switch " + e.SwitchNumber); + return; + } + if (!_mpfCoilNumbers.ContainsNumber(e.CoilNumber)) { + Logger.Error("Unmapped MPF coil " + e.CoilNumber); + return; + } + + var switchName = _mpfSwitchNumbers.GetNameByNumber(e.SwitchNumber); + var coilName = _mpfCoilNumbers.GetNameByNumber(e.CoilNumber); + _player.ScheduleAction(1, () => _player.RemoveHardwareRule(switchName, coilName)); + Logger.Info($"<-- remove hardware rule: {switchName} -> {coilName}."); } private void OnDmdFrame(object sender, SetDmdFrameRequest frame) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs new file mode 100644 index 00000000..e38ab15c --- /dev/null +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs @@ -0,0 +1,102 @@ +// Visual Pinball Engine +// Copyright (C) 2021 freezy and VPE Team +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using UnityEngine; +using NLog; +using Logger = NLog.Logger; + +namespace VisualPinball.Engine.Mpf.Unity +{ + [Serializable] + public class MpfNameNumberDictionary : ISerializationCallbackReceiver + { + [SerializeField] private List _names = new(); + [SerializeField] private List _numbers = new(); + + // Unity doesn't know how to serialize a Dictionary + private Dictionary _namesByNumber = new(); + private Dictionary _numbersByName = new(); + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public MpfNameNumberDictionary() { } + + public MpfNameNumberDictionary(Dictionary namesByNumber) + { + Init(namesByNumber); + } + + public void Init(Dictionary numbersByName) + { + _namesByNumber.Clear(); + _numbersByName.Clear(); + + foreach (var kvp in numbersByName) + { + _numbersByName[kvp.Key] = kvp.Value; + _namesByNumber[kvp.Value] = kvp.Key; + } + } + + public void OnBeforeSerialize() + { + _names.Clear(); + _numbers.Clear(); + + foreach (var kvp in _numbersByName) + { + _names.Add(kvp.Key); + _numbers.Add(kvp.Value); + } + } + + public void OnAfterDeserialize() + { + _namesByNumber.Clear(); + _numbersByName.Clear(); + + if (_names.Count != _numbers.Count) + { + Logger.Warn("Mismatch between number of serialized names and numbers of coils, " + + "switches, or lamps in machine description. Update the machine description " + + "by clicking 'Get Machine Description' in the Inspector of the MpfGameLogicEngine component."); + } + + for (int i = 0; i != System.Math.Min(_names.Count, _numbers.Count); i++) + { + _namesByNumber.Add(_numbers[i], _names[i]); + _numbersByName.Add(_names[i], _numbers[i]); + } + } + + public string GetNameByNumber(string number) + { + return _namesByNumber[number]; + } + + public string GetNumberByName(string name) + { + return _numbersByName[name]; + } + + public bool ContainsName(string name) + { + return _numbersByName.ContainsKey(name); + } + + public bool ContainsNumber(string number) + { + return _namesByNumber.ContainsKey(number); + } + } +} \ No newline at end of file diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs.meta b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs.meta new file mode 100644 index 00000000..73c294b8 --- /dev/null +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0c1f3207a98a8f042a69783238c0fdb4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: