English | 한국어
Engine Lobby Sample Scenario¶
This sample shows how the Framework owns STREAM sessions, session-to-Actor binding, and client push for game-engine clients in a shared lobby, leaving the application to own player names and the chat fan-out rule.
1. Purpose and scope¶
Engine Lobby is the smallest real-time lobby flow intended for game engines. The table in Implementation structure decides which engine clients exist. A client connects, checks the round trip, joins with a name, and sends chat. The server creates one Actor per connection, binds it to the session, and pushes each chat to every currently bound Actor in the lobby.
The flow starts with a dedicated Redis Location Store and the .NET server ready. It ends when two clients have joined and both receive the same chat sent by one client. Authentication, lobby selection, stored history, moderation, reconnect recovery, and UI design are outside the scope because they are not needed to prove this flow.
An Actor keeps its participant name only for the lifetime of the server process. The sample does not store chat history or define a policy for finding and rebinding an Actor after disconnect.
2. Requirements¶
2.1 Functional requirements¶
- A
PingReqrequest returns aPingReswith the samesentAtUnixMs. - A
JoinReqrequest creates and binds the Actor for that connection before replying withJoinRes. - After joining, a one-way
ChatMsgcauses the lobby to push the sameChatNotifyto every currently bound Actor, including the sender. ChatNotifycarries the name owned by the sender Actor and the submitted text.
2.2 Operational and quality requirements¶
- Packets and lifecycle callbacks for one session use the Framework's serialized session order.
- Application messages do not expose the Actor owner node, transport routing ID, or Location Store key.
- An HTTP endpoint reports readiness; the runner does not infer readiness with a fixed sleep.
- Client self-checks compare reply and push payloads. A server log line is not success evidence.
- Every run owns a dedicated Redis container and key prefix, uses listener ports from the .NET sample range, and cleans up only the process and container it created.
3. System topology¶
%%{init: {'flowchart': {'nodeSpacing': 32, 'rankSpacing': 40, 'padding': 8, 'wrappingWidth': 180}, 'themeVariables': {'fontSize': '18px'}}}%%
flowchart LR
U1["Engine client A"] -->|"STREAM"| S[".NET Engine Lobby server"]
U2["Engine client B"] -->|"STREAM"| S
S -->|"Location and node descriptors"| R[("Redis Location Store")]
One .NET process hosts the STREAM listener, RouteMesh Object Server, Entry Spot, and participant Actors. This keeps the sample small without merging their responsibilities.
A server that uses session handlers alone does not need Redis. This sample creates and binds
Actors and runs them in an Entry Spot, so it selects the Object Server role. The MeshNode contract
requires a Location Store for Object Client and Server roles. Redis owns only Framework object
locations and node descriptors, not chat or participant names.
4. Roles and responsibilities¶
| Role | Responsibility | Owned state |
|---|---|---|
| Engine client | Connect, pump on the main thread, send PingReq/JoinReq/ChatMsg, update UI |
Connector lifecycle and last notification shown |
| STREAM session | Connection lifecycle, session packet dispatch, Actor creation and binding | Session ID and current bound Actor reference |
| Lobby Entry Spot | Select the currently connected participants on this node for chat push | Set of Actors currently eligible for notification |
| Participant Actor | Keep the participant name and process ChatMsg |
Actor ID and participant name |
| Redis Location Store | Share Object Server descriptors and Actor locations | Framework object-location records |
The Participant Actor is the sole owner of its name. The Entry Spot participant set is a current push-target snapshot, not a second copy of names. A session does not separately store the name or lobby membership.
5. Framework elements and why they are used¶
| Element | Reason |
|---|---|
| STREAM node and typed session handlers | Receive named packets from a game-client connection and reply to requests. |
| RouteMesh Object Server | Register the Actor factory and Entry Spot in the same server. |
| Location Store | Manage the Object Server descriptor and current Actor owner. |
| Entry Spot | Provide the initial Actor membership and collect current lobby push targets. |
| Actor and session binding | Keep chat state outside connection callbacks and let an Actor push to its currently bound client. |
| Default typed JSON codec | Give the server and engine clients the same JSON message contract below. Each connector expresses it through its language's public JSON surface. |
The Actor factory selects DisableRelocation. The sample runs one server and does not demonstrate
Actor movement, so it adds no relocation-state adapter or compensation path.
6. Message contract¶
Every message is a JSON object. Names and fields are case-sensitive. Each string field is
required and does not accept null. Messages carry no transport identity or routing field.
| Message | Direction and mode | Fields | Completion meaning |
|---|---|---|---|
PingReq |
Client → session request | sentAtUnixMs: string |
The server read the request value. |
PingRes |
Session → client reply | sentAtUnixMs: string |
Echoes the PingReq value. |
JoinReq |
Client → session request | name: string |
Starts Actor creation and session binding. |
JoinRes |
Session → client reply | actorId: string, name: string |
The Actor is Ready and bound to this session. |
ChatMsg |
Client → bound Actor one-way send | text: string |
The Actor queue accepted the message and runs lobby fan-out; there is no reply. |
ChatNotify |
Actor → bound client push | actorId: string, name: string, text: string |
Reports one chat's sender and text. |
ParticipantActorCreateReq(name: string) is the server-internal message used by the JoinReq
handler for the Actor manager's Create request. The client packet is not reused as the Actor
creation payload.
sentAtUnixMs is a decimal string to avoid language differences when decoding 64-bit JSON
numbers. actorId is an opaque application identity derived by the server from the session
identity; clients do not parse its format.
Every engine client uses the packet names, fields, and directions in this table verbatim. They define no engine-specific aliases or extra wire fields and connect to the same .NET server endpoint.
7. Business flow¶
7.1 Normal flow¶
%%{init: {'sequence': {'actorFontSize': '18px', 'messageFontSize': '18px', 'noteFontSize': '18px', 'boxMargin': 8, 'width': 140}, 'themeVariables': {'fontSize': '18px'}}}%%
sequenceDiagram
participant A as Engine client A
participant B as Engine client B
participant S as STREAM session
participant L as Lobby Entry Spot
participant P as Participant Actor
A->>S: PingReq request
S-->>A: PingRes reply
A->>S: JoinReq(name) request
S->>P: create and bind
P->>L: initial Entry Spot membership
S-->>A: JoinRes reply
B->>S: JoinReq(name) request
S->>P: create and bind
P->>L: initial Entry Spot membership
S-->>B: JoinRes reply
A->>P: ChatMsg(text) relay
P->>L: fan-out current participants
L-->>A: ChatNotify push
L-->>B: ChatNotify push
JoinRes is returned after Actor creation, Ready publication, and session binding. ChatMsg has no
session handler, so the session relays it to the bound Actor. The Actor handler creates a
ChatNotify from its name and the text, then the Entry Spot sends it through the bound session of
each Actor in the current participant snapshot.
7.2 Failure and recovery flow¶
- A
ChatMsgsent beforeJoinReqhas no bound Actor, so it is not handled and produces noChatNotify. The client reconnects and starts withJoinReq. - If a client disconnects during push, it is removed from the current participant set. The application does not roll back completed pushes to other clients or retry them into duplicates.
- If Redis cannot start or the Object Server cannot register with the Location Store, server startup fails. The runner does not fall back to a host Redis instance.
8. Implementation structure¶
Shared sources live under framework/languages/engines/.
| Path | Content | Build targets | Client flow | Connector | Pump location | Mirror repository |
|---|---|---|---|---|---|---|
Server/ |
.NET host, shared message declarations, session/Actor/Entry Spot implementation, probe, and runner | .NET 8 | — | — | — | zlink-engine-server |
Unity/ |
Unity 6 LTS project with the shared ZLinkClient MonoBehaviour and UI |
Unity Editor, native player, WebGL | join, chat, notification | native: Zlink.Stream.Connector NuGet, WebGL: com.zlink.stream-connector.webgl UPM |
Update() |
zlink-unity-examples |
Unreal/ |
Unreal Engine 5 C++ project with a connector-owning Actor and on-screen status UI | Unreal Editor project build | ping, join, chat, notification | the C++ connector's ZLinkStreamConnector plugin |
Tick() |
zlink-unreal-examples |
Godot/ |
Godot 4 .NET project with a connector-owning Control node and status label | Godot 4 .NET project | ping, join, chat, notification | Zlink.Stream.Connector NuGet |
_Process() |
zlink-godot-examples |
Axmol/ |
Axmol 2.3 C++ project with a connector-owning Scene and status label | Axmol native C++ project | ping, join, chat, notification | the C++ connector's Axmol adapter | scheduler update | zlink-axmol-examples |
CocosCreator/ |
Cocos Creator 3.8 web TypeScript project with a Component and Label | web | ping, join, chat, notification | @zlink-systems/stream-connector (browser WebSocket) |
update() |
zlink-cocos-creator-examples |
The server uses only public Zlink.Framework package surfaces. The Unity project excludes the native
assembly from WebGL so the two connector implementations never compile into one target.
When the guide shows engine client code it excerpts it through --8<-- markers and keeps no copied
code sample in the guide.
9. Client self-check¶
Two self-check clients verify this order directly:
- Client A sends
PingReq("1000")and receivesPingRes("1000"). - Client A's
JoinReq("alice")and client B'sJoinReq("bob")return distinct non-emptyactorIdvalues and the requested names. - Both clients register their
ChatNotifywaits before chat is sent. - Client A sends
ChatMsg("hello"). - Both notifications have the same
actorId,name, andtextas client A'sJoinRes.actorId,alice, andhello. - The probe closes both connectors and exits successfully.
10. Smoke execution¶
The Linux lane runs install, build, run, verify, and stop. The Windows lane checks install and build only; the Linux lane owns Redis-backed run/verify. The server runner owns these operations:
- Build the .NET sample.
- Create a dedicated Docker Redis container and key prefix for the run.
- Allocate RouteMesh, STREAM, and HTTP readiness ports and write a typed server configuration file.
- Start the server and use a bounded wait on its HTTP readiness endpoint.
- Run the C# probe with two connectors.
- On success or failure, clean up only the server PID and Redis container ID it created.
Each engine client's build is verified separately, for the build targets in the §8 table, on runners with that engine's installation and license. The ordinary server smoke proves the same connector contract with a C# probe and needs no engine installation.
11. Completion criteria¶
- Korean and English contracts have the same message names, fields, normal and failure flows, and state owners.
- The .NET server builds from public packages and passes its dedicated runner's client self-check.
- The Linux lane runs install, build, run, verify, and stop. The Windows lane checks install and build only; the Linux lane owns Redis-backed run/verify.
- Each engine client in the implementation structure table compiles for
the table's build targets and, through the connector and pump location in that table, connects, pumps, and performs
its row's client flow for the same packet contract. Unity compiles its
native and WebGL targets from the same
ZLinkClientsource. - The exporter maps each path in the implementation structure table to the root tree of that row's mirror repository.