diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs index 43110387..0939e613 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs @@ -31,8 +31,9 @@ public static IEnumerable GetSwitches(this Mach if (Regex.Match(sw.Name, "l(eft)?_?flipper|flipper_?l(eft)?", RegexOptions.IgnoreCase).Success) { gleSw.Description = "Left Flipper Button"; gleSw.InputActionHint = InputConstants.ActionLeftFlipper; + } - } if (Regex.Match(sw.Name, "r(ight)?_?flipper|flipper_?r(ight)?", RegexOptions.IgnoreCase).Success) { + if (Regex.Match(sw.Name, "r(ight)?_?flipper|flipper_?r(ight)?", RegexOptions.IgnoreCase).Success) { gleSw.Description = "Right Flipper Button"; gleSw.InputActionHint = InputConstants.ActionRightFlipper; @@ -105,5 +106,41 @@ public static IEnumerable GetLights(this MachineD // todo color return md.Lights.Select(light => new SerializedGamelogicEngineLamp(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; + } } -} +} \ No newline at end of file diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 7d6b8f49..6147daba 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -41,7 +41,7 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine public GamelogicEngineWire[] AvailableWires => availableWires; #pragma warning disable CS0067 - public event EventHandler OnStarted; + public event EventHandler OnStarted; public event EventHandler OnLampChanged; public event EventHandler OnLampsChanged; public event EventHandler OnCoilChanged; @@ -51,7 +51,7 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine public event EventHandler OnSwitchChanged; #pragma warning restore CS0067 - [NonSerialized] + [NonSerialized] private MpfApi _api; public string MachineFolder @@ -67,10 +67,10 @@ public string MachineFolder set { #if UNITY_EDITOR - Undo.RecordObject(this, "Set machine folder"); - PrefabUtility.RecordPrefabInstancePropertyModifications(this); + Undo.RecordObject(this, "Set machine folder"); + PrefabUtility.RecordPrefabInstancePropertyModifications(this); #endif - if (value.Contains("StreamingAssets/")) + if (value.Contains("StreamingAssets/")) { _machineFolder = "./StreamingAssets/" + value.Split("StreamingAssets/")[1]; } @@ -87,11 +87,14 @@ public string MachineFolder [SerializeField] private SerializedGamelogicEngineLamp[] requiredLamps = Array.Empty(); [SerializeField] private GamelogicEngineWire[] availableWires = Array.Empty(); + // 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 Player _player; - private Dictionary _switchIds = new Dictionary(); - private Dictionary _switchNames = new Dictionary(); - private Dictionary _coilNames = new Dictionary(); - private Dictionary _lampNames = new Dictionary(); private bool _displaysAnnounced; @@ -102,18 +105,6 @@ public string MachineFolder 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(ConsoleOptions); @@ -128,8 +119,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}\"."); } @@ -151,9 +142,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); } @@ -181,6 +173,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()); } } @@ -211,9 +206,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.ScheduleAction(1, () => 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); } @@ -221,9 +217,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.ScheduleAction(1, () => 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); } @@ -231,15 +228,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]; + if (_mpfCoilNumbers.ContainsNumber(e.CoilNumber)) { + var coilName = _mpfCoilNumbers.GetNameByNumber(e.CoilNumber); _player.ScheduleAction(e.PulseMs * 10, () => { - Logger.Info($"<-- coil {coilId} ({e.CoilNumber}): false (pulse)"); - OnCoilChanged?.Invoke(this, new CoilEventArgs(coilId, false)); + 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.ScheduleAction(1, () => 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); } @@ -249,8 +245,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); } @@ -262,32 +259,37 @@ 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.ScheduleAction(1, () => _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); + 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.ScheduleAction(1, () => _player.RemoveHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); - Logger.Info($"<-- remove hardware rule: {_switchNames[e.SwitchNumber]} -> {_coilNames[e.CoilNumber]}."); + 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) @@ -333,4 +335,4 @@ private void OnDestroy() } } -} +} \ No newline at end of file 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: