Skip to content
Merged
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
41 changes: 39 additions & 2 deletions VisualPinball.Engine.Mpf.Unity/Runtime/MpfExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ public static IEnumerable<SerializedGamelogicEngineSwitch> 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;

Expand Down Expand Up @@ -105,5 +106,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;
}
}
}
}
104 changes: 53 additions & 51 deletions VisualPinball.Engine.Mpf.Unity/Runtime/MpfGamelogicEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine
public GamelogicEngineWire[] AvailableWires => availableWires;

#pragma warning disable CS0067
public event EventHandler<EventArgs> OnStarted;
public event EventHandler<EventArgs> OnStarted;
public event EventHandler<LampEventArgs> OnLampChanged;
public event EventHandler<LampsEventArgs> OnLampsChanged;
public event EventHandler<CoilEventArgs> OnCoilChanged;
Expand All @@ -51,7 +51,7 @@ public class MpfGamelogicEngine : MonoBehaviour, IGamelogicEngine
public event EventHandler<SwitchEventArgs2> OnSwitchChanged;
#pragma warning restore CS0067

[NonSerialized]
[NonSerialized]
private MpfApi _api;

public string MachineFolder
Expand All @@ -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];
}
Expand All @@ -87,11 +87,14 @@ public string MachineFolder
[SerializeField] private SerializedGamelogicEngineLamp[] requiredLamps = Array.Empty<SerializedGamelogicEngineLamp>();
[SerializeField] private GamelogicEngineWire[] availableWires = Array.Empty<GamelogicEngineWire>();

// 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<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>();

private bool _displaysAnnounced;

Expand All @@ -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);

Expand All @@ -128,8 +119,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 +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);
}
Expand Down Expand Up @@ -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());
}
}

Expand Down Expand Up @@ -211,35 +206,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];
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);
}
Expand All @@ -249,8 +245,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 +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)
Expand Down Expand Up @@ -333,4 +335,4 @@ private void OnDestroy()
}

}
}
}
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.

Loading