Online Services
Self-hosted live services for MotaEngine games: a standalone
MotawareOnlineServer process plus the engine's HttpOnlineProvider,
the networked implementation of the engine's IOnlineSubsystem
interfaces. Everything the offline LocalStub provider simulates in-process
round-trips to a server you run — leaderboards, parties, presence,
sessions, cloud saves and the rest are shared across every connected
machine.
game / engine code
│ OnlineSubsystemRegistry::Get()
▼
IOnlineSubsystem ──────────────┬──────────────────────────────┐
│ │
LocalStubProvider HttpOnlineProvider
(offline, per-machine, REST/JSON over HTTP
Saved/Online on disk) │
▼
MotawareOnlineServer
(tools/online-server;
state.json + cloud blobs)
A future Steam/EOS port implements the same interfaces against the vendor SDK and swaps in at the same seam; nothing above the registry changes.
Using it from the engine
MotaEngine --online-url=http://my-server:27080 [--online-api-key=SECRET]
MotaServer --online-url=http://my-server:27080 ...
Without --online-url the engine registers the LocalStub exactly as
before. With it, the HttpOnlineProvider is registered even when the
server is unreachable at boot (matching vendor-SDK behaviour); its
IsAvailable() health check re-runs on demand (cached 5 s), and the
online status console command shows the live verdict.
Semantics follow the LocalStub contracts, with the documented
adaptations (see http_online_provider.h): auth is uid+displayName for
a bearer token (no passwords — LocalStub's trust model, centralised);
transport failures degrade to the interface's failure returns; the
matchmaking callback fires for this user's matches drained from a
server-side inbox; achievement definitions and the IAP catalog stay
client-side; voice remains a local state machine.
The server
MotawareOnlineServer [--port=27080] [--bind=0.0.0.0]
[--data-dir=online-data] [--api-key=SECRET]
- State: one atomically-rewritten
state.jsonunder--data-dir(saved after every mutation), plus cloud payloads ascloud/<uid>/<slot>.bin. Matchmaking tickets and bearer tokens are deliberately in-memory: a restart empties the queue and signs everyone out, and clients re-sign-in transparently on the first 401. - Auth: optional shared API key via the
X-Mota-Api-Keyheader gates every route except/v1/health. This is engine-grade auth for a server you control, not internet-grade identity. - TLS: the server speaks plain HTTP. For anything beyond a LAN, front it with a reverse proxy (nginx/caddy) that terminates TLS — note the engine's cpp-httplib is built without SSL, so the proxy must also accept HTTP from the game or you terminate TLS at a trusted network boundary.
Endpoints (/v1, JSON unless noted)
| Area | Method + path | Notes |
|---|---|---|
| Health | GET /health | no auth |
| Auth | POST /auth/signin {uid, displayName} → {token}; POST /auth/signout | |
| Identity | GET /identity/me; GET/POST /identity/friends; DELETE /identity/friends/<uid> | |
| Achievements | `POST /achievements/unlock | progress {id, maxProgress[, progress]}; GET /achievements/state` |
| Leaderboards | POST /leaderboards/define; GET /leaderboards; POST /leaderboards/<id>/scores; GET .../top?n=, .../around?count=, .../rank | keep-best per user; ties: earlier submit wins |
| Cloud saves | GET /cloud; `PUT | GET |
| Presence | POST /presence {state}; `GET /presence/me | |
| Recent players | `POST | GET |
| Parties | POST /parties; GET /parties/current; POST /parties/leave; `POST | GET /parties/invites; POST /parties/invites/; POST |
| Matchmaking | `POST | GET /matchmaking/tickets; DELETE /matchmaking/tickets/; POST /matchmaking/process` |
| Sessions | `POST | GET /sessions; GET |
| IAP | POST /iap/purchase {productId, type}; GET /iap/entitlements; POST /iap/consume | product types enforced client-side (catalog is game content) |
Everything except /health and /auth/signin requires
Authorization: Bearer <token>.
Building the server
With the engine (default when configuring the repo top-level;
-DMOTA_BUILD_ONLINE_SERVER=OFF to skip): the exe lands next to the
other tools at build/tools/online-server/<config>/MotawareOnlineServer.exe,
and MotaOnlineServerCore is linked into MotaTests so the whole
protocol runs in-process in the test suite (tests/test_online_http.cpp,
tag [m6][online-http]).
Standalone on a deployment host (typically Linux — the server is plain C++20 + std::filesystem with no engine dependency):
sudo apt install -y build-essential cmake git # Ubuntu 22.04+
git clone <repo> motaware && cd motaware
cmake -S tools/online-server -B build-online -DCMAKE_BUILD_TYPE=Release
cmake --build build-online -j
./build-online/MotawareOnlineServer --port=27080 --data-dir=/var/lib/motaware-online
find_package is tried for nlohmann-json and cpp-httplib first
(sudo apt install nlohmann-json3-dev to use the distro package);
whatever is missing is fetched at configure time via FetchContent, so a
bare host with a compiler, CMake ≥ 3.21 and network access builds
without any package setup.
systemd unit (Ubuntu)
# /etc/systemd/system/motaware-online.service
[Unit]
Description=Motaware online services
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/opt/motaware/MotawareOnlineServer --port=27080 \
--data-dir=/var/lib/motaware-online --api-key=CHANGE_ME
Restart=on-failure
User=motaware
DynamicUser=yes
StateDirectory=motaware-online
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload && sudo systemctl enable --now motaware-online
curl http://localhost:27080/v1/health
Testing
MotaTests "[m6][online-http]"— the end-to-end suite: real server core behind a real cpp-httplib listener on an ephemeral loopback port, driven through the real provider. Covers every service, two-client cross-visibility (leaderboards, invites, presence, matchmaking, sessions), binary cloud-save round-trips with hash verification, and a server-restart test proving state.json persistence plus the transparent 401 re-sign-in.- Handler-level tests hit
OnlineServerCore::Dispatchdirectly with no port bound — the same split as the streaming module's signaling server.
Known limits
- No passwords / real identity — the server trusts the uid a client presents (plus the shared API key). Fine for a server you run for your own game's players during development; a public launch wants real auth in front.
- IAP has no payment processor — purchases always succeed server-side; the entitlement store is real, the storefront is not.
- Matchmaking
processis client-driven (any client's poll advances the queue) rather than a server-side background matcher. - Voice is a local state machine (the vendor-SDK seam) — no relay.
