Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Editor-Player-IPC #139

Open
3 tasks
Ghabry opened this issue Sep 19, 2020 · 7 comments
Open
3 tasks

RFC: Editor-Player-IPC #139

Ghabry opened this issue Sep 19, 2020 · 7 comments

Comments

@Ghabry
Copy link
Member

Ghabry commented Sep 19, 2020

By fmatthew:

There's a few use cases I see really

  • Reusing player logic for complicated things like tile mapping - Refactor out globals, just call functions or create / destroy isolated class instances directly in editor.

For this, what we can and should do is isolate the interfaces we want to reuse from global state using dependency injection. This also helps with making Player itself unit testable. More refactors like the Algo library for this.

  • Actually running player game engine embedded in Qt and somehow allowing live editing and debugging - Requires engine globals

To be able to start and stop player within editor itself, yes I agree we need a very clean creation and destruction sequence that doesn't leak any global state between runs. We need this anyway for load game and player unit tests.

That being said, I wonder if IPC is also the way to go here. Then you could run a separate player binary and attach the interpreter debugger and other tools to it. We also again don't need to worry about player global state at all. Each player run starts and exits a new process for 100% guaranteed cleanliness.

The IPC approach would require us to add stuff to player to pipe the necessary data back and forth, as well as a protocol for the game strate (enhanced LSD?). That can be a lot of work but it would enforce a clean separation. If we did this though, it also opens the door to additional capabilities for regtesting and other introspection tools.

The biggest amount of work with doing IPC / networking is the serialization code, but we already have liblcf giving us a fast binary protocol.

If we don't go the IPC route, I would want to still see a very clean separation. Let's not pollute Player with random // needed for editor hacks.

Not going to IPC route would allow a much tighter and easier integration with edtior, and allow live editing while playing, which could be a big productivity booster for developers.

  • Running multiple editors / players for different games at the same time - use IPC

To run multiple editor instances for different games at the same time with their own embedded players, I would only do it by spawning multiple processes. Then we don't need to worry about player globals, ui globals, renderer state, audio state, etc.. etc... If needed a parent UI process can manage all the child ui instances. Trying to manage all the global state ourselves, especially for all the backend third party libraries I'm 99% sure will end up in frustration and failure, not to mention massive complexity. This also completely shields us to state cleanup bugs from multiple editor/player instances.

For Video/Audio/Input the Editor could provide an "EditorUi" object (like SDLUi etc).

If we embed a running player directly into editor widget, we'll need this.

Options are:

  1. QtWidget Ui to replace SDL - if player is in process
  2. IPC Ui, which just pipes the framebuffer onto IPC channel, and editor picks this up and renders it into it's widget, with potential debug overlays - if player is out of process
  3. Do nothing - if player is out of process, we can just run it in windowed mode using native SDL and have the editor debugging tools separate widgets.

Though the design for this "Player object" is on you @fmatthew5876 I'm not really motivated to approach this ^^

Regardless of editor, this is in my near term future plans. I want to make sure new game / load game bugs are impossible.

We're very close to being able to stand up and tear down player game instances. Once the character PR goes in I'll be unlocked to refactor the player global state to achieve this.

@Ghabry
Copy link
Member Author

Ghabry commented Sep 19, 2020

I would actually prefer the IPC approach.
This would solve the "Multiple instances" problem and a crashing Player can't tear down the Editor (I see this happening alot when users start to modify stuff in Debugging mode ;)).

Even with a IPC protocol you can still achieve alot (even event debugging/single-stepping will still work)

Question is how the IPC protocol is supposed to look like.
Insane idea (but would be totally state-of-the-art): The Player could launch a "http server" and you interact with it through a REST API (return value are JSON or LCF "octet-stream").
Advantage: Lots of tooling, you could IPC via curl.

For comparison. What CMake uses:
https://cmake.org/cmake/help/latest/manual/cmake-server.7.html?highlight=s

The new CMake mode which looks much more ugly, it works by putting files on the Filesystem and then CMake uses file system watches to detet when a file appeared (so kind file-based REST)

https://cmake.org/cmake/help/v3.18/manual/cmake-file-api.7.html

@mateofio
Copy link
Contributor

mateofio commented Sep 19, 2020

I would actually prefer the IPC approach.
This would solve the "Multiple instances" problem and a crashing Player can't tear down the Editor (I see this happening alot when users start to modify stuff in Debugging mode ;)).

Test play crashes (which it will since I'm actively developing new features), kills editor. And I lose all my saved work or end up with corrupted data..

Just for that I'm sold. Lets do IPC :)

Question is how the IPC protocol is supposed to look like.
Insane idea (but would be totally state-of-the-art): The Player could launch a "http server" and you interact with it through a REST API (return value are JSON or LCF "octet-stream").

One major problem with REST is that it's unidirectional. The editor will have to poll the player for state updates. Given the randomness of frame rate timing, this can be lossy.

To have frame accurate debugging we probably want to synchronize per frame updates between player and editor. I think something bi-directional would work better. Editor will need to send commands to player, or query for things. Player will also need to push back state updates every frame.

If you're talking http, then a websocket based API could work.

Ultimately I think the transport mechanism is less important. More important is what is the payload and the communication protocol used to exchange data.

I haven't really thought about this too deeply yet. We could look to remote debugging protocols like used in gdb for inspiration. I don't know how those work yet, haven't looked into it.

@mateofio
Copy link
Contributor

Another option which can simplify. If we limit ourselves to IPC and not general network programming. We could use shared memory and just put a thread safe queue on it.

Lots of ways this can go.. A lot to think about

@Ghabry
Copy link
Member Author

Ghabry commented Sep 19, 2020

Besides the channel used more important is also the API avaliable. Which features do we need?
One could also integrate a scripting language into Player for this, then you could send "script commands" to Player which are executed. The current "hot shit" is Javascript https://bellard.org/quickjs/ or https://duktape.org/ (JS would also attract game devs because everybody is using JS right now ;))

From the Qt side we can basicly use anything: https://doc.qt.io/Qt-5/ipc.html but it should be something that is not painful to build into Player.
One of the easiest ways is likely via sockets because everybody copied the API from BSD, so the function look almost the same everywhere (except for WSAStartup ;)).

Debugging protocols for reference:

GDB Remote Serial protocol: (LLDB uses the same)
https://users.informatik.haw-hamburg.de/~krabat/FH-Labor/gnupro/3_GNUPro_Debugging_Tools/b_Debugging_with_GDB/gdbThe_GDB_remote_serial_protocol.html

Chrome Dev Tools (web-sockets)
https://developers.google.com/web/tools/chrome-devtools

Firefox:
https://docs.firefox-dev.tools/backend/protocol.html

@mateofio
Copy link
Contributor

mateofio commented Sep 19, 2020

Thinking about it more, a unidirectional REST protocol could work.

You could hit an endpoint to pause the player. Then inspect it's state. You can single step or continue. Queries should fail if player is not paused

For live editing, we can have a quicksave reload command. Which saves and reload the game. This quicksave/ load can be done in memory.

https://github.com/ipkn/crow

I used this library in the past, it's really easy to run a server in a few lines of code. Unmaintained since 2017, so if we use this we would probably need to fork and maintain our own copy.

@Ghabry
Copy link
Member Author

Ghabry commented Sep 19, 2020

so always one frame forward and then send commands to Player?
Well also an idea.

At least one use case I'm seeing already is: Audio Playback. To integrate this in the Editor I'm required to copy-paste the entire Audio Decoder. Player IPC could help here by asking the Player to play the audio for me :) (though here a frame-based protocol wouldn't work here I guess because this isn't really related to game state?)

@mateofio
Copy link
Contributor

mateofio commented Sep 19, 2020

Initial idea

Commands

  • Pause - pauses the player after the next frame
  • Get - get a state value, error if not paused
  • Set - set a state value, error if not paused
  • Step - advance by 1 frame
  • Continue - continue running frames
  • Reload - quicksave, reload all game assets, quickload

For get, set format we can use lcf / lcf json. We could add debugging data and structs and codegen them with liblcf. This will integrate very easily with what we have already for save games.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants