Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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<SerializedGamelogicEngineLamp> GetLights(this MachineD
// todo color
return md.Lights.Select(light => new SerializedGamelogicEngineLamp(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;
}
}
}
97 changes: 55 additions & 42 deletions VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,16 @@ public string MachineFolder
[SerializeField] private SerializedGamelogicEngineLamp[] requiredLamps = Array.Empty<SerializedGamelogicEngineLamp>();
[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 Down Expand Up @@ -128,8 +131,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 @@ -151,9 +154,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 Down Expand Up @@ -181,6 +185,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 @@ -211,35 +218,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.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);
}
}

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

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.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);
}
Expand All @@ -249,8 +257,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 @@ -262,32 +271,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.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);
return;
}
if (!_coilNames.ContainsKey(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]}.");
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.