How it works
The parts of Empo, how the SwiftUI app and the C++ engine share a process, and where to read more about each piece.
Empo is an app and a game engine in one process. The app is SwiftUI and UIKit. The app owns the window and the user interface. The engine owns the game.
Empo has two engines, and each one is a game core. MkxpCore is a C++ fork of mkxp-z that runs
RPG Maker games in Ruby 1.8, 1.9, or 3.1. PsdkCore is LiteRGSS2 and LiteCGSS, and it runs
PSDK games in Ruby 3.0. Read
cores.md for the rules
that keep the two cores apart.
The repository
- mkxp-z-apple-mobile/ The engine fork. A git submodule, pure C++.
- ios/
- Empo/ The app. SwiftUI, with UIKit for the touch controls.
- Dependencies/ Cross-compiled static libraries: SDL, SFML, four Ruby versions, OpenAL, and the LiteRGSS2 fork.
- GameProbe/ A Swift package that reads game metadata. It builds on Linux, so CI can test it.
- docs/ This documentation.
Three Ruby interpreters in the RPG Maker core
Ruby 1.8, 1.9, and 3.1 compile separately. Each version merges with its binding code into one relocatable object file, and each object exports exactly one symbol. At import, Empo reads the scripts of the game and decides which interpreter it needs. At launch, the app sends a session config, and the engine dispatches to the correct one.
Ruby 3.1 also carries syntax-transform patches, so Pokemon Essentials forks that mix old grammar with a modern runtime still parse. The app turns the mode on for each game that needs it.
Read multi-ruby.md for the detection rules and the dispatch mechanism.
One session for each process
The app creates SDL, the GL context, OpenAL, and the Ruby interpreter one time, and it keeps
them for the lifetime of the process. iOS does not let an app relaunch itself between games, and
ruby_init() runs one time for each process.
This is why Empo plays one game for each session. Read multi-session.md for the
quit paths and the state that stays behind.
Touch controls are keyboard events
The overlay does not talk to the engine through a control API. It calls SDL_PushEvent with
synthetic key events, so the engine reads them as it reads a hardware keyboard. A new button or
a new layout needs no change in the engine.
Games can ship their own layout. Read Ship custom controls.
Windows APIs in Ruby
RPG Maker games call Win32 functions through Win32API. Two preload scripts,
win32_wrap.rb and platform_compat.rb, stub out the calls that games expect. They also
neutralize system, fork, and spawn, so a game cannot start a process.
Import writes containers, not database rows
Each game is a folder under Documents/Games/<title>/. There is no database. The import
pipeline extracts the source one time, validates each game root that it finds, and commits one
container for each game that you select.
Read import-pipeline.md for the stages and the invariants.
Pause is a frozen frame
The SDL window and the SwiftUI view hierarchy cannot animate together. Empo takes a snapshot of the last frame, shows the snapshot, and animates that instead.
Read pause-resume.md for the two pause modes.
Read next
design-system.mdin the repository: the visual rules the app follows, sheets included.sdl-ruby-workarounds.mdin the engine repository: why SDL, the GL context, OpenAL, and the Ruby VM are persistent.patches-format.mdin the engine repository: thepatches.jsonscript-patching system.- CONTRIBUTING.md: the build requirements and the steps to build the app.