From def089b3b233a39df229f58c89deba9c0111d093 Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 9 Jul 2024 15:09:30 +0200 Subject: [PATCH 1/9] Patch for new VPE API Replaces calls to Player.Queue with calls to Player.ScheduleAction --- .../Runtime/MpfGamelogicEngine.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 442e0279..e8677f6e 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -179,7 +179,7 @@ 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))); + _player.ScheduleAction(1, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(_coilNames[e.CoilNumber], true))); } else { Logger.Error("Unmapped MPF coil " + e.CoilNumber); } @@ -189,7 +189,7 @@ 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))); + _player.ScheduleAction(1, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(_coilNames[e.CoilNumber], false))); } else { Logger.Error("Unmapped MPF coil " + e.CoilNumber); } @@ -204,7 +204,7 @@ private void OnPulseCoil(object sender, PulseCoilRequest e) OnCoilChanged?.Invoke(this, new CoilEventArgs(coilId, false)); }); Logger.Info($"<-- coil {e.CoilNumber} ({coilId}): true (pulse {e.PulseMs}ms)"); - _player.Queue(() => OnCoilChanged?.Invoke(this, new CoilEventArgs(coilId, true))); + _player.ScheduleAction(1, () => OnCoilChanged?.Invoke(this, new CoilEventArgs(coilId, true))); } else { Logger.Error("Unmapped MPF coil " + e.CoilNumber); @@ -221,7 +221,7 @@ private void OnFadeLight(object sender, FadeLightRequest e) Logger.Error("Unmapped MPF lamp " + fade.LightNumber); } } - _player.Queue(() => { + _player.ScheduleAction(1, () => { OnLampsChanged?.Invoke(this, new LampsEventArgs(args.ToArray())); }); } @@ -237,7 +237,7 @@ private void OnConfigureHardwareRule(object sender, ConfigureHardwareRuleRequest return; } - _player.Queue(() => _player.AddHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); + _player.ScheduleAction(1, () => _player.AddHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); Logger.Info($"<-- new hardware rule: {_switchNames[e.SwitchNumber]} -> {_coilNames[e.CoilNumber]}."); } @@ -252,7 +252,7 @@ private void OnRemoveHardwareRule(object sender, RemoveHardwareRuleRequest e) return; } - _player.Queue(() => _player.RemoveHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); + _player.ScheduleAction(1, () => _player.RemoveHardwareRule(_switchNames[e.SwitchNumber], _coilNames[e.CoilNumber])); Logger.Info($"<-- remove hardware rule: {_switchNames[e.SwitchNumber]} -> {_coilNames[e.CoilNumber]}."); } From d32ee35decab1513b9631450eb6937b4cc6f64d7 Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Wed, 10 Jul 2024 14:14:00 +0200 Subject: [PATCH 2/9] Search for mpf in user path --- VisualPinball.Engine.Mpf/MpfSpawner.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Engine.Mpf/MpfSpawner.cs b/VisualPinball.Engine.Mpf/MpfSpawner.cs index 7c90c78d..a9e41036 100644 --- a/VisualPinball.Engine.Mpf/MpfSpawner.cs +++ b/VisualPinball.Engine.Mpf/MpfSpawner.cs @@ -105,7 +105,8 @@ private static string GetFullPath(string fileName) } // go through all PATHs - var values = Environment.GetEnvironmentVariable("PATH"); + var values = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine); + values += Path.PathSeparator + Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.User); foreach (var path in values.Split(Path.PathSeparator)) { var fullPath = Path.Combine(path, fileName); if (File.Exists(fullPath)) { From ee5e98bc00302c79baa72202cf487a85fe11ee4b Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:17:35 +0200 Subject: [PATCH 3/9] Properly serialize machine description Register changes to machine description with Unity Editor systems to ensure they are saved with the scene. --- VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 442e0279..eedd1b28 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -15,6 +15,7 @@ using Mpf.Vpe; using NLog; using UnityEngine; +using UnityEditor; using VisualPinball.Engine.Game.Engines; using VisualPinball.Unity; using Logger = NLog.Logger; @@ -144,6 +145,8 @@ public void GetMachineDescription() } if (md != null) { + Undo.RecordObject(this, "Get machine description"); + PrefabUtility.RecordPrefabInstancePropertyModifications(this); requiredSwitches = md.GetSwitches().ToArray(); requiredCoils = md.GetCoils().ToArray(); requiredLamps = md.GetLights().ToArray(); From e3368f003657ce496438a5f4f0c150a15d8209b4 Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Mon, 15 Jul 2024 23:05:45 +0200 Subject: [PATCH 4/9] Use new subclasses --- .../Runtime/MpfExtensions.cs | 12 ++++++------ .../Runtime/MpfGamelogicEngine.cs | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs index 039cb64c..43110387 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs @@ -21,10 +21,10 @@ namespace VisualPinball.Engine.Mpf.Unity { public static class MpfExtensions { - public static IEnumerable GetSwitches(this MachineDescription md) + public static IEnumerable GetSwitches(this MachineDescription md) { return md.Switches.Select(sw => { - var gleSw = new GamelogicEngineSwitch(sw.Name) { + var gleSw = new SerializedGamelogicEngineSwitch(sw.Name) { NormallyClosed = sw.SwitchType.ToLower() == "nc" }; @@ -63,10 +63,10 @@ public static IEnumerable GetSwitches(this MachineDescrip }); } - public static IEnumerable GetCoils(this MachineDescription md) + public static IEnumerable GetCoils(this MachineDescription md) { var coils = md.Coils.Select(coil => { - var gleCoil = new GamelogicEngineCoil(coil.Name); + var gleCoil = new SerializedGamelogicEngineCoil(coil.Name); if (Regex.Match(coil.Name, "(l(eft)?_?flipper|flipper_?l(eft)?_?(main)?)$", RegexOptions.IgnoreCase).Success) { gleCoil.Description = "Left Flipper"; @@ -100,10 +100,10 @@ public static IEnumerable GetCoils(this MachineDescription return coils; } - public static IEnumerable GetLights(this MachineDescription md) + public static IEnumerable GetLights(this MachineDescription md) { // todo color - return md.Lights.Select(light => new GamelogicEngineLamp(light.Name)); + return md.Lights.Select(light => new SerializedGamelogicEngineLamp(light.Name)); } } } diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index eedd1b28..8d2ee832 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -49,9 +49,9 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine public string machineFolder; - [SerializeField] private GamelogicEngineSwitch[] requiredSwitches = Array.Empty(); - [SerializeField] private GamelogicEngineCoil[] requiredCoils = Array.Empty(); - [SerializeField] private GamelogicEngineLamp[] requiredLamps = Array.Empty(); + [SerializeField] private SerializedGamelogicEngineSwitch[] requiredSwitches = Array.Empty(); + [SerializeField] private SerializedGamelogicEngineCoil[] requiredCoils = Array.Empty(); + [SerializeField] private SerializedGamelogicEngineLamp[] requiredLamps = Array.Empty(); [SerializeField] private GamelogicEngineWire[] availableWires = Array.Empty(); private Player _player; 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 5/9] 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: From 1137024bb97b2cca2677dd166837d35ee83bb6da Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:11:37 +0200 Subject: [PATCH 6/9] Fix compile error on build --- VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 8d2ee832..51c0223a 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -15,7 +15,9 @@ using Mpf.Vpe; using NLog; using UnityEngine; +#if UNITY_EDITOR using UnityEditor; +#endif using VisualPinball.Engine.Game.Engines; using VisualPinball.Unity; using Logger = NLog.Logger; @@ -145,8 +147,10 @@ public void GetMachineDescription() } if (md != null) { +#if UNITY_EDITOR Undo.RecordObject(this, "Get machine description"); PrefabUtility.RecordPrefabInstancePropertyModifications(this); +#endif requiredSwitches = md.GetSwitches().ToArray(); requiredCoils = md.GetCoils().ToArray(); requiredLamps = md.GetLights().ToArray(); From 6d5a22b8a07af0b76e4b8433485e1b5aaf1c008d Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:21:00 +0200 Subject: [PATCH 7/9] Store relative path to machine folder if it is in the streaming assets path --- .../Editor/MpfGamelogicEngineInspector.cs | 22 +++++++++----- .../Runtime/MpfGamelogicEngine.cs | 29 +++++++++++++++++-- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/VisualPinball.Engine.Mpf.Unity/Editor/MpfGamelogicEngineInspector.cs b/VisualPinball.Engine.Mpf.Unity/Editor/MpfGamelogicEngineInspector.cs index 32b5c893..2afbe5b2 100644 --- a/VisualPinball.Engine.Mpf.Unity/Editor/MpfGamelogicEngineInspector.cs +++ b/VisualPinball.Engine.Mpf.Unity/Editor/MpfGamelogicEngineInspector.cs @@ -46,21 +46,29 @@ public override void OnInspectorGUI() return; } - var pos = EditorGUILayout.GetControlRect(true, 18f); + if (!string.IsNullOrEmpty(_mpfEngine.MachineFolder) && !_mpfEngine.MachineFolder.Contains("StreamingAssets")) { + EditorGUILayout.HelpBox("The machine folder is not in the 'StreamingAssets' folder. It will not be included in builds.", MessageType.Warning); + } + + var pos = EditorGUILayout.GetControlRect(true, 18f); pos = EditorGUI.PrefixLabel(pos, new GUIContent("Machine Folder")); - if (GUI.Button(pos, _mpfEngine.machineFolder, EditorStyles.objectField)) { - var path = EditorUtility.OpenFolderPanel("Mission Pinball Framework: Choose machine folder", _mpfEngine.machineFolder, ""); + if (GUI.Button(pos, _mpfEngine.MachineFolder, EditorStyles.objectField)) { + if (!Directory.Exists(Application.streamingAssetsPath)) { + Directory.CreateDirectory(Application.streamingAssetsPath); + } + var openFolder = Directory.Exists(_mpfEngine.MachineFolder) ? _mpfEngine.MachineFolder : Application.streamingAssetsPath; + var path = EditorUtility.OpenFolderPanel("Mission Pinball Framework: Choose machine folder", openFolder, ""); if (!string.IsNullOrWhiteSpace(path)) { - _mpfEngine.machineFolder = path; + _mpfEngine.MachineFolder = path; } } if (GUILayout.Button("Get Machine Description")) { - if (!Directory.Exists(_mpfEngine.machineFolder)) { + if (!Directory.Exists(_mpfEngine.MachineFolder)) { EditorUtility.DisplayDialog("Mission Pinball Framework", "Gotta choose a valid machine folder first!", "Okay"); - } else if (!Directory.Exists(Path.Combine(_mpfEngine.machineFolder, "config"))) { - EditorUtility.DisplayDialog("Mission Pinball Framework", $"{_mpfEngine.machineFolder} doesn't seem a valid machine folder. We expect a \"config\" subfolder in there!", "Okay"); + } else if (!Directory.Exists(Path.Combine(_mpfEngine.MachineFolder, "config"))) { + EditorUtility.DisplayDialog("Mission Pinball Framework", $"{_mpfEngine.MachineFolder} doesn't seem a valid machine folder. We expect a \"config\" subfolder in there!", "Okay"); } else { _mpfEngine.GetMachineDescription(); } diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 442e0279..2ba63178 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -18,6 +18,7 @@ using VisualPinball.Engine.Game.Engines; using VisualPinball.Unity; using Logger = NLog.Logger; +using System.IO; namespace VisualPinball.Engine.Mpf.Unity { @@ -46,7 +47,29 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine [NonSerialized] private MpfApi _api; - public string machineFolder; + public string MachineFolder + { + get + { + if (_machineFolder != null && _machineFolder.Contains("StreamingAssets/")) + { + return Path.Combine(Application.streamingAssetsPath, _machineFolder.Split("StreamingAssets/")[1]); + } + return _machineFolder; + } + set + { + if (value.Contains("StreamingAssets/")) + { + _machineFolder = "./StreamingAssets/" + value.Split("StreamingAssets/")[1]; + } + else + { + _machineFolder = value; + } + } + } + [SerializeField] private string _machineFolder; [SerializeField] private GamelogicEngineSwitch[] requiredSwitches = Array.Empty(); [SerializeField] private GamelogicEngineCoil[] requiredCoils = Array.Empty(); @@ -80,7 +103,7 @@ public void OnInit(Player player, TableApi tableApi, BallManager ballManager) foreach (var lamp in requiredLamps) { _lampNames[lamp.Id] = lamp.Id; } - _api = new MpfApi(machineFolder); + _api = new MpfApi(MachineFolder); _api.Launch(new MpfConsoleOptions { ShowLogInsteadOfConsole = false, VerboseLogging = true, @@ -136,7 +159,7 @@ public void GetMachineDescription() MachineDescription md = null; try { - md = MpfApi.GetMachineDescription(machineFolder); + md = MpfApi.GetMachineDescription(MachineFolder); } catch (Exception e) { From 16647d262e666bd693e7efa39d7d6067d74fa28e Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:46:37 +0200 Subject: [PATCH 8/9] Register changes with Unity Editor --- .../Runtime/MpfGamelogicEngine.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index 2ba63178..d40dc799 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -59,7 +59,11 @@ public string MachineFolder } set { - if (value.Contains("StreamingAssets/")) +#if UNITY_EDITOR + Undo.RecordObject(this, "Set machine folder"); + PrefabUtility.RecordPrefabInstancePropertyModifications(this); +#endif + if (value.Contains("StreamingAssets/")) { _machineFolder = "./StreamingAssets/" + value.Split("StreamingAssets/")[1]; } From be6e228c5fb821d66c34170f3f8e13f04b361e34 Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:21:00 +0200 Subject: [PATCH 9/9] Rebase fix-mpf-numbers --- .../Runtime/MpfGamelogicEngine.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs index b373f2c9..cadfd530 100644 --- a/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs +++ b/VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs @@ -101,6 +101,18 @@ 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(new MpfConsoleOptions { ShowLogInsteadOfConsole = false,