EasyRPG / Editor

Game editor similar to RPG Maker
https://easyrpg.org/editor/
GNU General Public License v3.0
334 stars 59 forks source link

RFC: Editor-Player-IPC #139

Open Ghabry opened 3 years ago

Ghabry commented 3 years ago

By fmatthew:

There's a few use cases I see really

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.

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.

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 commented 3 years ago

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

fmatthew5876 commented 3 years ago

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.

fmatthew5876 commented 3 years ago

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 commented 3 years ago

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

fmatthew5876 commented 3 years ago

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 commented 3 years ago

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?)

fmatthew5876 commented 3 years ago

Initial idea

Commands

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.