missionpinball / mpf

Mission Pinball Framework: Open source software to run a real pinball machine.
http://missionpinball.org
MIT License
216 stars 143 forks source link

Need advice for a new pinball simulator #1528

Open freezy opened 4 years ago

freezy commented 4 years ago

Hi,

We've started a few months ago porting Visual Pinball to Unity. There are a few reasons for that, besides using a current-gen render pipeline and cross-platform support, we also want to make it easier for authors to create new tables by extending Unity's editor.

And, you guessed it, we want a modern scripting engine that facilitates programming game logic for original table creations.

Over the past months I've spent a few hours reading MPF's excellent documentation, and I really like the declarative approach of MPF's config files. Ideally, you would load an MPF config file into our sim, and given the playfield elements are named correctly, it would just work™.

Now, the most obvious question to me is whether to integrate completely with MPF, i.e. somehow interface C# with Python, or re-implement a "light" version of MPF that doesn't handle all the issues you hardware guys need to deal with.

I'm probably vastly underestimating the effort of the latter, so I would like to have your opinion on that. For the former, becoming a rather sophisticated sim, we have a time budget per frame, so performance could be an issue.

Our project is called "VPE" for Visual Pinball Engine. It can read and write the .vpx format, and the goal is to keep a level of backwards-compatibility. Visual Pinball's physics engine is highly regarded in the virtual pinball community, so we've ported it to C# using Unity's new DOTS technology. The renderer uses Unity's new HDRP. You can see an 1:1 import of the Tom & Jerry VPX file into VPE here. There are more screenshots here, and if you're interested in what the virtual pinball community has to say about it, check this thread.

Looking forward to hearing from you!

jabdoa2 commented 4 years ago

Hey freezy,

that sounds like a quite cool project.

As you probably have seen we already can interface with VPX via COM (see: mpf-vpcom-bridge). We also interface with unity for an alternative backbox implementation (instead of kivy based backbox). MPF consists of two parts the game engine (game logic and controlling the hardware) and the media controller (sound, videos and graphics).

The game engine is usually very light on CPU (1-4% on i5; 10-20% on a RPi4). Integrating the game engine via some network connection, pipe or serial should be relatively easy. For our media controller CPU load obviously depends on what is shown in the backbox. I guess you would want to render the backbox LCD or DMD inside unity which might be tricky with kivy (probably requires grabbing pixels from an OpenGL viewport and sending them over which is slow). In your case it might be better to implement something like the unity backbox. That currently is less ergonomic than MPF-MC but we could add some more stuff together.

If running python for the game is fine for you we can totally make this happen with relatively little effort. The VPX-MPF-bridge only contains a few methods to make this happen. Almost none of the calls have to be synchronous. MPF would send command (i.e. activate a coil) and VPE would send switch changes.

Reimplementing the media controller in unity would be some effort but it would also be worthwhile for us independently. Basically MPF would send events (i.e. play sound, show slide x etc). MPF-MC shows certain slide formats and a few audio features. Audio should be super easy to reimplement in unity. Slides might be a bit more work. Alternatively, we could run the media controller independently in the beginning but it might not look as nice or be a bit laggy.

What do you think?

Jan

freezy commented 4 years ago

Hey Jan,

Thanks for your prompt reply. It's interesting, now that you mention it, I remember about the media controller. However, most vpin folks use DirectB2S, or, more recently, PinUP System (check this guy out). I haven't looked into what the media controller can do, but I'm sure it's awesome and would be a neat alternative.

But my question was mainly about the game engine. As you describe the use case, it seems like MPF would be driving VPE. So authors would basically use MPF to code up the game logic, test it with MPF monitor, and then use VPE as a new hardware platform within MPF.

I was thinking more like the other way around. Model the table and add game logic within the Unity editor, preferably with a nice UI (I saw that a graphical interface for config files is still on your TODO). So in terms of setup, MPF would be driven by VPE (of course, during gameplay, it's more appropriate to call MPF the "driver").

But independently whether we have a UI or not, during development, VPE would manage the config file(s) and somehow pass them to MPF (my first suggestion), or (my second suggestion), deal with them itself by re-implementing MPF's event system, and probably a lot more (but ignoring all the hardware-related features of MPF).

So for plan A, VPE would somehow spawn MPF and interact with it through a socket or pipe (as you said). But it has to be transparent to the user, i.e. they wouldn't have to install MPF separately (although they should be able to, if they want to). This would come with a lot of wiring to do, but would allow us to run a fully-fledged MPF in our engine.

For plan B, I'm still wondering what it would take to roll our own MPF implementation. Remember, we don't need to deal with coil overheats and whatnot, so in my naive mind it's a matter of modeling your game elements and the event system. But again, I'm new to what it takes to create a pinball game logic-wise, so I'm maybe vastly underestimating the effort.

My concerns with plan A are mainly how to ship this. The ultimate goal is to build a cross-platform VPE player that runs on mobile as well. If we start spawning Python threads on Android I'm worried about performance.

Anyway, it would great to get a more precise idea of the effort it would take to implement a MPF-compatible game engine for a virtual-only simulation.

jabdoa2 commented 4 years ago

I get the idea. It is certainly a valid point to have all inside VPE. Cloning MPF certainly is possible. It depends which features you copy. There are certainly a lot of different elements around pinball machines.

My perspective is more the view of a game builder. The process usually looks like the following:

My point here is that software and layout are usually two separate steps in the development process.

For running MPF in unity we could try IronPython. That should give you decent performance (similar to pypi) and run on any .NET platform as unity.

freezy commented 4 years ago

Great info, thanks a lot! I've heard about IronPython, but I haven't tested it. As far as I know, it's Microsoft maintaining it, so it might be worth a shot. Will try to fiddle with it when I have time.

About testing, I think already being in Unity and spinning up MPF would be quite fast. So maybe we're onto a more attractive testing workflow for MPF users as well? Given we get the interaction right.

Anyway, cheers for your input. And I'm deeply impressed by the work you guys have done. I'll get back to you once I actually start working on this. Or if you want to take VPE for a spin, the link's in the first post. ;)

freezy commented 4 years ago

I'm not used to Python (yet!), but IronPython supports Python 2.7, while MPF is Python 3, right? As far as I know, 2.7 doesn't support async, which is used by MPF. Does that disqualify IronPython, or are there workarounds?

EDIT: There is IronPython 3, but the first line says NOT ready for use yet. Do you have experience with IronPython 3? You think it's "good enough" for MPF?

jabdoa2 commented 4 years ago

Not sure. I do not have any experience with that. We can give it a try if you like. I might have done time over the weekend.

freezy commented 4 years ago

That'd be awesome!

I've spend some time reading through the MPF code (wow that's nice, readable and documented code!), and as far as I can figure, what we would need to port is core and most of devices. Most if not all platforms we could skip. But it's still a significant part that would take months to port manually. So if there's a way to run this natively via Iron, that'd be the ideal solution. At least that's my current conclusion.

freezy commented 4 years ago

So we've been discussing this internally, and concluded that the best way to go about this is to not include MPF in VPE's core but to ship it separately as a plugin.

Two questions:

  1. Is there anything to add to MPF to make its API available through a socket?
  2. We would like to include the machine files in the .vpx file. They act like a virtual file system, so I was wondering if there was a way to abstract MPF's way of loading the config files and get them through a custom API (ideally through the socket as well)?

By the way, we've published a very first documentation site. It's quite basic but should give you a good overview what VPE does.

jabdoa2 commented 4 years ago

I can create something like that. Currently we got it for VPX via COM on Windows or for various platforms via network or serial sockets. Would you like a serial protocol or a binary one? Protobuf? Json? Async or sync?

Config loading is already abstracted in MPF via a config loader. It would be possible to implement a loader which uses configs from some launcher. Asset loading might still be a challenge because that happens later and expects full paths to images, sounds or videos. But I'm sure that we can find a solution for that as well.

I guess overall we would have a new launcher which returns a command socket. Then VPE would send the config, MPF starts up, requests/receices switch states and starts to send coil/light updates to VPE. Makes sense in general?

freezy commented 4 years ago

Yup, sounds fine!

I don't have a preference for the protocol. Protobuf sounds the most efficient. Regarding sync vs async you're probable the better judge. Given it's all event based, async would be fine I suppose.

I agree about tackling assets at a later point. For me the main goal is to get the game logic running and validate VPE's design to be able to communicate with third party engines.

jabdoa2 commented 4 years ago

I designed some minimal protobufs for this purpose. Those are a bit simpler than LISY because we can skip a lot of the nasty hardware details from real machines:

syntax = "proto3";

// First message from MPF to platform
message GetPlatformDetailsRequest {
  string mpf_version = 1;
}

// Platform returns available hardware and initial switch states
message GetPlatformDetailsResponse {
  map<string, bool> known_switches_with_initial_state = 1;
  repeated string known_lights = 2;
  repeated string known_coils = 3;
}

// Fade multiple light channels in the platform
// This might be too low level for VPE as a RGB light would be three channels and a GI only one
// We might want to separate RGB and single color lights (or handle single color lights as RGB as well)
message FadeLight {
  message ChannelFade {
    string light_number = 1;
    float target_brightness = 2;
  }
  uint32 common_fade_ms = 1;
  repeated FadeLight fades = 2;
}

// Pulse a coil
// If it has been enabled before disable it afterwards
message PulseCoil {
  string coil_number = 1;
  uint32 pulse_ms = 2;
  float pulse_power = 3;
}

// Enable a coil
// Not sure if hold_power is simulated in VPE or if we should remove it
message EnableCoil {
  string coil_number = 1;
  uint32 pulse_ms = 2;
  float pulse_power = 3;
  float hold_power = 4;
}

// Disable a coil
message DisableCoil {
  string coil_number = 1;
}

// Configure a minimal hardware rule to hide latency from pops/slings/flippers
// Dual-wound, EOS, cut-off rules and more are ignored here
// Not sure if hold_power is simulated in VPE or if we should remove it
message ConfigureHardwareRule {
  string coil_number = 1;
  string switch_number = 2;
  uint32 pulse_ms = 3;
  float pulse_power = 4;
  float hold_power = 5;
}

// MPF will poll the platform periodically
message SwitchPoll {
}

// Platform response to SwitchPoll from MPF
message SwitchPollResponse {
  message SwitchChanges {
    string switch_number = 1;
    bool switch_state = 2;
    uint64 change_time = 3;
  }
  uint64 platform_time = 1;
  repeated SwitchChanges switch_changes = 2;
}

I added some comments to the messages. I skiped features such as steppers, motors and servos for now and would add them later. What do you think? Should we implement it this way in MPF?

jabdoa2 commented 4 years ago

GRPC service could look like this:

service HardwarePlatform {
  rpc GetPlatformDetails(GetPlatformDetailsRequest) returns (GetPlatformDetailsResponse);
  rpc GetSwitchChanges(SwitchPoll) returns (stream SwitchPollResponse);
  rpc LightFade(FadeLightRequest) returns (FadeLightResponse);
  rpc CoilPulse(PulseCoilRequest) returns (CoilResponse);
  rpc CoilEnable(EnableCoilRequest) returns (CoilResponse);
  rpc CoilDisable(DisableCoilRequest) returns (CoilResponse);
  rpc ConfigureHardwareRule(ConfigureHardwareRuleRequest) returns (CoilResponse);
}
freezy commented 4 years ago

Awesome stuff!

My first decision would actually be whether it's a uni-directional or bi-directional channel. It looks like you're going for a uni-directional polling approach where VPE polls MPF for every frame (or cycle)? Bi-directional would have the advantage of a) not having to poll and thus b) not having to maintain lists of changed states. You would just publish the new state immediately.

Anyway, here are my comments:

// Fade multiple light channels in the platform
message FadeLight {
  message ChannelFade {
    string light_number = 1;
    float target_brightness = 2;
  }
  uint32 common_fade_ms = 1;
  repeated FadeLight fades = 2;
}

I'm not sure to understand this message. So ChannelFade defines which light to fade to which brightness, but why does FadeLight contain a reference to itself? Shouldn't it be ChannelFade instead? And that would be for a single-color light, right?

Enable a coil Not sure if hold_power is simulated in VPE or if we should remove it

I think it kinda is, but the physics engine only exposes one coil per flipper. So all our flippers are single-wound. However I don't think it should be a problem to "simulate" dual-wound (i.e. flip up with the main coil and flip down with the hold coil), so users don't need to change the config for dual-wound machines.

TBH I'm not even sure if PulseCoil is necessary, with EnableCoil and DisableCoil we should be fine, no?

Configure a minimal hardware rule to hide latency from pops/slings/flippers Dual-wound, EOS, cut-off rules and more are ignored here Not sure if hold_power is simulated in VPE or if we should remove it

Not sure what you mean by "hide latency"?

If I understand this correctly, this would be to configure for example the flipper strength. We do have these settings, but as far as I understood, these are very much linked to condition of the actual hardware. So I would assume that the author tweaks these settings in VPE independently to get the same behavior as in the real world.

Platform response to SwitchPoll from MPF

What's change_time and platform_time?

jabdoa2 commented 4 years ago

Awesome stuff!

My first decision would actually be whether it's a uni-directional or bi-directional channel. It looks like you're going for a uni-directional polling approach where VPE polls MPF for every frame (or cycle)? Bi-directional would have the advantage of a) not having to poll and thus b) not having to maintain lists of changed states. You would just publish the new state immediately.

It is kind of both. If you look at the grpc service you can see that GetSwitchChanges returns a stream of switch changes (which are incorrectly named poll result; will fix that). Communication is initiated by MPF but stream stays open and VPE can continously send changes when they happen.

Anyway, here are my comments:

// Fade multiple light channels in the platform
message FadeLight {
  message ChannelFade {
    string light_number = 1;
    float target_brightness = 2;
  }
  uint32 common_fade_ms = 1;
  repeated FadeLight fades = 2;
}

I'm not sure to understand this message. So ChannelFade defines which light to fade to which brightness, but why does FadeLight contain a reference to itself? Shouldn't it be ChannelFade instead? And that would be for a single-color light, right?

Correct. My fault. Will fix that. A fade could fade multi channels at once for a RGB light.

Enable a coil Not sure if hold_power is simulated in VPE or if we should remove it

I think it kinda is, but the physics engine only exposes one coil per flipper. So all our flippers are single-wound. However I don't think it should be a problem to "simulate" dual-wound (i.e. flip up with the main coil and flip down with the hold coil), so users don't need to change the config for dual-wound machines.

This rule is only for single-wound and we can totally only send this rule for VPE.

TBH I'm not even sure if PulseCoil is necessary, with EnableCoil and DisableCoil we should be fine, no?

Pulse would give us more accurate timings in VPE. I would expect that the physics engine converts pulse_ms into the strength of an auto plunger/sling/flipper for example.

Configure a minimal hardware rule to hide latency from pops/slings/flippers Dual-wound, EOS, cut-off rules and more are ignored here Not sure if hold_power is simulated in VPE or if we should remove it

Not sure what you mean by "hide latency"?

Most game engines (Spike, MPF, Skeletongame etc) run at around 50-100Hz only. This would translate to huge latency and jitter for flippers. To prevent that MPF tells the hardware to pulse/enable the flipper coil once a certain switch becomes active. This is what the P3-Roc or Stern Spike do as well.

If I understand this correctly, this would be to configure for example the flipper strength. We do have these settings, but as far as I understood, these are very much linked to condition of the actual hardware. So I would assume that the author tweaks these settings in VPE independently to get the same behavior as in the real world.

Platform response to SwitchPoll from MPF

What's change_time and platform_time?

Change time is when the switch changes and platform time the time when the message has been sent. This is used in MPF to calculate accurate switch timings.

jabdoa2 commented 4 years ago

Small update:

syntax = "proto3";

service HardwarePlatform {
  rpc GetPlatformDetails(GetPlatformDetailsRequest) returns (GetPlatformDetailsResponse);
  rpc GetSwitchChanges(SwitchChangesRequest) returns (stream SwitchChanges);
  rpc LightFade(FadeLightRequest) returns (FadeLightResponse);
  rpc CoilPulse(PulseCoilRequest) returns (CoilResponse);
  rpc CoilEnable(EnableCoilRequest) returns (CoilResponse);
  rpc CoilDisable(DisableCoilRequest) returns (CoilResponse);
  rpc ConfigureHardwareRule(ConfigureHardwareRuleRequest) returns (CoilResponse);
}

// Pulse a coil
// If it has been enabled before disable it afterwards
message PulseCoilRequest {
  string coil_number = 1;
  uint32 pulse_ms = 2;
  float pulse_power = 3;
}

// Enable a coil
// Not sure if hold_power is simulated in VPE or if we should remove it
message EnableCoilRequest {
  string coil_number = 1;
  uint32 pulse_ms = 2;
  float pulse_power = 3;
  float hold_power = 4;   // Probably not important for VPE. Can a ball be knocked off from a magnet?
}

// Disable a coil
message DisableCoilRequest {
  string coil_number = 1;
}

// Configure a minimal hardware rule to hide latency from pops/slings/flippers
// Dual-wound, EOS, cut-off rules and more are ignored here
// Not sure if hold_power is simulated in VPE or if we should remove it
message ConfigureHardwareRuleRequest {
  string coil_number = 1;
  string switch_number = 2;
  uint32 pulse_ms = 3;
  float pulse_power = 4;
  float hold_power = 5;   // Probably not important for VPE because knocking down a flipper will probably not happen there
}

message CoilResponse {
}

// Fade multiple light channels in the platform
// This might be too low level for VPE as a RGB light would be three channels and a GI only one
// We might want to separate RGB and single color lights (or handle single color lights as RGB as well)
message FadeLightRequest {
  message ChannelFade {
    string light_number = 1;
    float target_brightness = 2;
  }
  uint32 common_fade_ms = 1;
  repeated ChannelFade fades = 2;
}

// Alternative for RGB
message FadeRGBRequest {
  string light_number = 1;
  // or: string target_hex_color = 2;
  float target_brightness_r = 2;
  float target_brightness_g = 3;
  float target_brightness_b = 4;
  uint32 fade_ms = 5;
}

// Alternative for GI/single color lights
message FadeSingleColorRequest {
  string light_number = 1;
  float target_brightness = 2;
  uint32 fade_ms = 3;
}

message FadeLightResponse {
}

// First message from MPF to platform
message GetPlatformDetailsRequest {
  string mpf_version = 1;
}

// Platform returns available hardware and initial switch states
message GetPlatformDetailsResponse {
  map<string, bool> known_switches_with_initial_state = 1;
  repeated string known_lights = 2;
  repeated string known_coils = 3;
}

// MPF will request changes at startup and then stream the changes
message SwitchChangesRequest {
}

// Platform streams switch changes to MPF
message SwitchChanges {
  string switch_number = 1;
  bool switch_state = 2;
  uint64 change_time = 3;
}
ecurtz commented 4 years ago

I'm curious about the tradeoff between RPC and a simple binary protocol to an open port when it comes to something like PinMAME. How difficult is it going to be to talk to the Unity networking RPC from "random" other processes. Also, hasn't the LISY stuff already been implemented in both MPF and PinMAME? Something along the lines of "reinventing the wheel is popular in pinball"... ;^)

jabdoa2 commented 4 years ago

I'm curious about the tradeoff between RPC and a simple binary protocol to an open port when it comes to something like PinMAME. How difficult is it going to be to talk to the Unity networking RPC from "random" other processes. Also, hasn't the LISY stuff already been implemented in both MPF and PinMAME? Something along the lines of "reinventing the wheel is popular in pinball"... ;^)

Inventing a new "simple" binary protocol is simply more effort than using a battle-proven library such as gRPC with Protobufs which exists for all languages. LISY would work as well but it contains stuff which isn't needed in a simulator (dual-wound, debouncing, hold/PWM in general and so on). Yes reusing LISY would be possible but it would also bring some limitations such as only numeric switch/coil/lamp numbers. I guess that you would want nice names in your simulation or at least a super set of all other hardware platforms so that you can switch to VPE without changing the configuration of your machine.

ecurtz commented 4 years ago

I was referring to the LISY stuff you had already done as the simple binary protocol, not suggesting a separate one. It does look like there is an experimental gRPC release with Unity support, so maybe I'm being overly cautious with respect to worrying about additional dependencies?

freezy commented 4 years ago

@jabdoa2 Cool, looks good. What remains is an adapter for fetching config files from VPE I think?

I have to do some more homework before I can work on a POC. First of all I need to properly handle switches, i.e. get the playfield triggers and whatnot together with keyboard mappings and unify them in some way.

Then you guys were talking about gRPC. That's what you're planning on using? If so, I'll need to read up on that and design an architecture.

PS: GitHub colors protobuf correctly, adding it after the triple backticks would make it more readable :)

jabdoa2 commented 4 years ago

@jabdoa2 Cool, looks good. What remains is an adapter for fetching config files from VPE I think?

Yes exactly. I will think about that next and create some kind of PoC for that.

I have to do some more homework before I can work on a POC. First of all I need to properly handle switches, i.e. get the playfield triggers and whatnot together with keyboard mappings and unify them in some way.

Then you guys were talking about gRPC. That's what you're planning on using? If so, I'll need to read up on that and design an architecture.

We use gRPC at work so that is something I know. But I'm open to everything.

PS: GitHub colors protobuf correctly, adding it after the triple backticks would make it more readable :)

Fixed :-)

freezy commented 4 years ago

Thanks :)

No, I'm mainly doing REST interfaces at work, so gRPC sounds fine!

freezy commented 4 years ago

Here's the issue with the UI we're thinking about to set up the switches: freezy/VisualPinball.Engine#185.

Let me know if that makes sense. The change_time isn't there, but I think that would be constant anyways, no? Or is there any use case where it makes sense setting this per item?

jabdoa2 commented 4 years ago

Here's the issue with the UI we're thinking about to set up the switches: freezy/VisualPinball.Engine#185.

Looks good.

Let me know if that makes sense. The change_time isn't there, but I think that would be constant anyways, no? Or is there any use case where it makes sense setting this per item?

We don't need it. MPF can also take the current time when the event arrives. Some of our platforms got an internal time which let's MPF calculate more accurate timings between two switches. But I get we don't need that here.

freezy commented 4 years ago

image

Starting to get somewhere!

How's the MPF front doing? :)

jabdoa2 commented 4 years ago

Awesome. We are doing good :-). I will push something over the weekend.

Am 2. Oktober 2020 09:24:25 MESZ schrieb freezy notifications@github.com:

image

Starting to get somewhere!

How's the MPF front doing? :)

-- You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/missionpinball/mpf/issues/1528#issuecomment-702572193

freezy commented 4 years ago

Cool! No hurry, that's just the switches, now we need to do the same for the coils to get at least some action.

jabdoa2 commented 4 years ago

I implemented the VPE platform in MPF in 0.54.0-dev.73.

Source of the platform is here (including the protobufs): https://github.com/missionpinball/mpf/tree/dev/mpf/platforms/visual_pinball_evolution.

A simple integration test is here: https://github.com/missionpinball/mpf/blob/dev/mpf/tests/test_VisualPinballEvolution.py. You can run the test using python3 -m unittest mpf.tests.test_VisualPinballEvolution -v.

You can find the config the test uses in this folder: https://github.com/missionpinball/mpf/tree/dev/mpf/tests/machine_files/vpe/config. If you want you can run `mpf game -t -v -V -b" inside that folder to run the test machine with your code. By default it uses localhost:50051 (but that can be configured in the vpe section of the config).

Let me know if that works for you or if you need anything.

freezy commented 4 years ago

Sweet! Will finish up some things and then play around with it!

By the way VPE stands for Visual Pinball Engine, might want to rename that :)

jabdoa2 commented 4 years ago

Ups. Will fix the name next.

freezy commented 4 years ago

Cool. Just merged the switch manager and there's some doc here and here if you want to have a look.

freezy commented 3 years ago

Coil manager is on its way as well (freezy/VisualPinball.Engine#212).

freezy commented 3 years ago

@jabdoa2 So I've looked at your code and you seem to be connecting to a gRPC server? Shouldn't MPF act as server since it's MPF exposing the interface?

jabdoa2 commented 3 years ago

@jabdoa2 So I've looked at your code and you seem to be connecting to a gRPC server? Shouldn't MPF act as server since it's MPF exposing the interface?

Both works. I implemented this the way we usually do this in MPF. But technically, VPE could also initiate the connection and MPF would stream back commands for coils/lights. The interface would look slightly different but it would work similarly.

freezy commented 3 years ago

So I start mpf and it says connecting and hangs. Then I start my test client and it says cannot connect. Any special parameter to make mpf listen instead of connecting?

On Sat, Oct 17, 2020, 18:19 jabdoa2 notifications@github.com wrote:

@jabdoa2 https://github.com/jabdoa2 So I've looked at your code and you seem to be connecting to a gRPC server? Shouldn't MPF act as server since it's MPF exposing the interface?

Both works. I implemented this the way we usually do this in MPF. But technically, VPE could also initiate the connection and MPF would stream back commands for coils/lights. The interface would look slightly different but it would work similarly.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/missionpinball/mpf/issues/1528#issuecomment-711038394, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAARGGVILNMAT3I3VFZ5A5DSLG7ZLANCNFSM4P3H5LEQ .

jabdoa2 commented 3 years ago

I need to implement that first. Currently, that is not implemented. GRPC either needs one endpoint per side or you need to use streaming. That will require some API changes. I will look into that tomorrow.

jabdoa2 commented 3 years ago

What would you think of the following API from the VPE side? MPF would open a port (or we could use a socket) on startup (when passing some parameter such as --vpe). Then you could pass the config (optional) and MPF would start:

service MpfHardwareService {
  rpc Start(MachineConfiguration) returns (stream Commands);
  rpc SendSwitchChanges(stream SwitchChanges) returns (EmptyResponse);
  rpc Quit(QuitRequest) returns (EmptyResponse);
}

message EmptyResponse {}
message QuitRequest {}

message MachineConfiguration {
  map<string, bool> known_switches_with_initial_state = 1;
  repeated string known_lights = 2;
  repeated string known_coils = 3;
  // TODO: add MPF optional config here
}

// Platform streams switch changes to MPF
message SwitchChanges {
  string switch_number = 1;
  bool switch_state = 2;
  uint64 change_time = 3;
}

message Commands {
  oneof command {
    FadeLightRequest fade_light = 0;
    PulseCoilRequest pulse_coil = 1;
    EnableCoilRequest enable_coil = 2;
    DisableCoilRequest disable_coil = 3;
    ConfigureHardwareRuleRequest configure_hardware_rule = 4;
    RemoveHardwareRuleRequest remove_hardware_rule = 5;
  }
}

message FadeLightRequest {
  // This is up to discussion
  message ChannelFade {
    string light_number = 1;
    float target_brightness = 2;
  }
  uint32 common_fade_ms = 1;
  repeated ChannelFade fades = 2;
}

// Pulse a coil
// If it has been enabled before disable it afterwards
message PulseCoilRequest {
  string coil_number = 1;
  uint32 pulse_ms = 2;
  float pulse_power = 3;
}

// Enable a coil
// Not sure if hold_power is simulated in VPE or if we should remove it
message EnableCoilRequest {
  string coil_number = 1;
  uint32 pulse_ms = 2;
  float pulse_power = 3;
  float hold_power = 4;   // Probably not important for VPE. Can a ball be knocked off from a magnet?
}

// Disable a coil
message DisableCoilRequest {
  string coil_number = 1;
}

// Configure a minimal hardware rule to hide latency from pops/slings/flippers
// Dual-wound, EOS, cut-off rules and more are ignored here
// Not sure if hold_power is simulated in VPE or if we should remove it
message ConfigureHardwareRuleRequest {
  string coil_number = 1;
  string switch_number = 2;
  uint32 pulse_ms = 3;
  float pulse_power = 4;
  float hold_power = 5;   // Probably not important for VPE because knocking down a flipper will probably not happen there
}

// Remove a rule between switch and coil
message RemoveHardwareRuleRequest {
  string coil_number = 1;
  string switch_number = 2;
}
freezy commented 3 years ago

So we previously had a HardwarePlatform service, and now a MpfHardwareService? How do the two relate?

Other comments / questions:

Sorry for the delay BTW, was in a VPE refactoring the entire weekend...

jabdoa2 commented 3 years ago

This would replace the previous service. A generic RPC makes sense. However, this is still somehow specific to VPE. Guess it could become a more generic interface in the future but that might require further design work.

freezy commented 3 years ago

Okay. Going forward I think it would be much more convenient not having to spawn a server and then spawning MPF. We could simply spawn MPF once and then connect to it when necessary (during edit time). This would simplify a lot, apologies if the current API design doesn't allow it, I thought receiving a switches stream would logically mean the client can post to it. But it seems like the other way around.

So Start() returns a stream with commands that VPE will execute (sorry I've missed that). Then VPE posts switch changes through SendSwitchChanges. For now, pulling configuration through a local machine folder is fine, however at some point there should be a way to either provide all the files (or configuration objects) through the API.

Sounds good?

freezy commented 3 years ago

BTW could you slip in a package Mpf; into the .protos to avoid global namespaces in C#?

jabdoa2 commented 3 years ago

Sure. I can add the namespace.

Commands are returned as stream to VPE.

The idea is to send the complete MPF configuration (without assets in the beginning). We need to define the format and implement this. But that would be super cool.

freezy commented 3 years ago

Yup. The first thing I want to get running are the flippers and the trough. On VPE side we have a UI now that connects switches and coils to playfield items and correctly handles them during gameplay. The only thing missing is a trough implementation, which is currently WIP. Once we get the initial wiring right, there are a lot of fun things to add :)

jabdoa2 commented 3 years ago

I implemented the new interface in 0.55.0-dev.2. Please give that a try!

freezy commented 3 years ago

Thanks!

Connection works great now. However I'm missing a way to pull the config. Or I might have misunderstood the MachineConfiguration passed to the server when starting.

From my point of view, there are two ways in which VPE interacts with MPF:

  1. During configuration. MPF is spawned in the background, loads the configuration (primarily from the file system because here the author would likely be updating it). VPE would pull coil- switch- and lamp names from MPF so the author can hook them up to the playfield. Like that (the ID column would be provided by MPF's coil names): No game is started at this point. It would be great if MPF could re-load its config when it changes on the file system so VPE has always the latest data.
  2. During gameplay: MPF is also spawned in the background. The machine config is read from the file system for now, and in the future passed via gRPC. Then VPE sends the initial switch states and waits for commands.

That's my current understanding of how thing should work, so I'm not sure what I'm supposed to pass into MachineConfiguration when starting, since the actual config should already have been loaded by MPF. I've looked at your tests and I can get it to work by passing the lights and coils in the config, but that info is kind of redundant, no?

So what's mainly missing is a way for VPE to pull the coil- switch- and lamp names from MPF during configuration. Like the current implementation, but the other way around:

service MpfHardwareService {
    rpc GetMachineConfiguration(EmptyRequest) returns (MachineConfiguration);
}

message MachineConfiguration {
    repeated string known_switches = 1;
    repeated string known_lights = 2;
    repeated string known_coils = 3;
}

message EmptyRequest{}

Does that make sense?

freezy commented 3 years ago

This is a milestone, so I'm posting it here as well:

https://user-images.githubusercontent.com/70426/110706758-a250aa00-81f8-11eb-93dc-3121d9d9c3d5.mp4

Once we get the game part stable, we'll discuss the media controller, but I'm pretty happy already how it runs.