Skip to content

Commit

Permalink
Fix mpf numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurkehrwald committed Jul 16, 2024
1 parent 371c19d commit 07dc8d9
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 55 deletions.
38 changes: 37 additions & 1 deletion VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,41 @@ public static IEnumerable<GamelogicEngineLamp> GetLights(this MachineDescription
// todo color
return md.Lights.Select(light => new GamelogicEngineLamp(light.Name));
}
}

public static Dictionary<string, string> GetSwitchNumbersByNameDict(this MachineDescription md)
{
Dictionary<string, string> ret = new();

foreach (SwitchDescription sw in md.Switches)
{
ret[sw.Name] = sw.HardwareNumber;
}

return ret;
}

public static Dictionary<string, string> GetCoilNumbersByNameDict(this MachineDescription md)
{
Dictionary<string, string> ret = new();

foreach (CoilDescription coil in md.Coils)
{
ret[coil.Name] = coil.HardwareNumber;
}

return ret;
}

public static Dictionary<string, string> GetLampNumbersByNameDict(this MachineDescription md)
{
Dictionary<string, string> ret = new();

foreach (LightDescription light in md.Lights)
{
ret[light.Name] = light.HardwareChannelColor;
}

return ret;
}
}
}
109 changes: 55 additions & 54 deletions VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine
[SerializeField] private GamelogicEngineLamp[] requiredLamps = Array.Empty<GamelogicEngineLamp>();
[SerializeField] private GamelogicEngineWire[] availableWires = Array.Empty<GamelogicEngineWire>();

private Player _player;
private Dictionary<string, int> _switchIds = new Dictionary<string, int>();
private Dictionary<string, string> _switchNames = new Dictionary<string, string>();
private Dictionary<string, string> _coilNames = new Dictionary<string, string>();
private Dictionary<string, string> _lampNames = new Dictionary<string, string>();
// 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<Action> _dispatchQueue = new Queue<Action>();

Expand All @@ -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,
Expand All @@ -98,8 +89,8 @@ public void OnInit(Player player, TableApi tableApi, BallManager ballManager)
// map initial switches
var mappedSwitchStatuses = new Dictionary<string, bool>();
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}\".");
}
Expand All @@ -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);
}
Expand All @@ -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());
}
}

Expand Down Expand Up @@ -177,35 +172,36 @@ 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);
}
}

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);
}
}

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);
}
Expand All @@ -215,8 +211,9 @@ private void OnFadeLight(object sender, FadeLightRequest e)
{
var args = new List<LampEventArgs>();
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);
}
Expand All @@ -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)
Expand Down
102 changes: 102 additions & 0 deletions VisualPinball.Engine.Mpf.Unity/Runtime/MpfNameNumberDictionary.cs
Original file line number Diff line number Diff line change
@@ -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<string> _names = new();
[SerializeField] private List<string> _numbers = new();

// Unity doesn't know how to serialize a Dictionary
private Dictionary<string, string> _namesByNumber = new();
private Dictionary<string, string> _numbersByName = new();

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

public MpfNameNumberDictionary() { }

public MpfNameNumberDictionary(Dictionary<string, string> namesByNumber)
{
Init(namesByNumber);
}

public void Init(Dictionary<string, string> 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);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 07dc8d9

Please sign in to comment.