Room Handlers
vix::realtime::RoomHandler contains the application logic for a room.
A handler receives commands, inspects the current room state, and returns a CommandResult.
RoomCommand
|
v
RoomHandler
|
v
CommandResultThe handler decides what should happen. The room runtime remains responsible for persistence, state application, and event delivery.
Create a handler
A simple handler can look like this:
class CounterHandler final
: public vix::realtime::RoomHandler
{
public:
vix::realtime::CommandResult handle_command(
const vix::realtime::RoomCommand &command,
const vix::realtime::RoomState &,
const vix::realtime::RoomContext &) override
{
if (command.type() != "counter.increment")
{
return vix::realtime::CommandResult::ignored();
}
vix::realtime::RoomEvent event{
command.room_id(),
"counter.incremented"};
return vix::realtime::CommandResult::accepted(
std::move(event));
}
};This handler accepts one command type and converts it into one event.
Handle a command
The main entry point is:
handle_command(
const RoomCommand &command,
const RoomState &state,
const RoomContext &context)The handler receives:
command
current state
room contextand returns:
Accepted
Rejected
IgnoredInspect the command
The handler can inspect the command type:
if (command.type() == "counter.increment")
{
// Handle the command.
}It can also read the payload:
const auto &payload = command.payload();and identifiers such as:
command.room_id();
command.session_id();
command.request_id();
command.correlation_id();Inspect the current state
The current room state is passed as a const reference.
For example:
const auto &counter =
dynamic_cast<const CounterState &>(state);The handler can use the current state to make decisions:
if (counter.value() >= 100)
{
return vix::realtime::CommandResult::rejected(
vix::realtime::ErrorCode::CommandRejected,
"counter limit reached");
}The handler should not modify the state directly.
Produce events
To change authoritative state, the handler returns events.
vix::realtime::RoomEvent event{
command.room_id(),
"counter.incremented"};
return vix::realtime::CommandResult::accepted(
std::move(event));The runtime then:
persists event
|
v
applies event to RoomState
|
v
delivers eventThe handler does not perform those steps itself.
Reject a command
Use a rejected result when an application rule prevents the command:
return vix::realtime::CommandResult::rejected(
vix::realtime::ErrorCode::CommandRejected,
"operation not allowed");A rejected command does not change the authoritative room state.
Ignore a command
Use ignored() when no action is required:
return vix::realtime::CommandResult::ignored();Ignored commands do not produce events.
Use the room context
RoomContext provides information about the current room operation.
For example:
const auto &roomId = context.room_id();The context is provided by the runtime for the current handler call.
Application logic should use it only for information that belongs to the current room operation.
Join lifecycle
A handler can receive a callback when a session joins the room.
vix::realtime::CommandResult on_join(
const vix::realtime::SessionId &sessionId,
const vix::realtime::RoomState &,
const vix::realtime::RoomContext &) override
{
return vix::realtime::CommandResult::accepted();
}The join operation can:
Accepted
Rejected
IgnoredIf the handler rejects the join, the session is not added to the room.
Leave lifecycle
A handler can also receive a callback before a session leaves:
vix::realtime::CommandResult on_leave(
const vix::realtime::SessionId &sessionId,
const vix::realtime::RoomState &,
const vix::realtime::RoomContext &) override
{
return vix::realtime::CommandResult::accepted();
}If the leave operation is rejected, the current membership is preserved.
Open lifecycle
A handler can participate when the room opens:
vix::realtime::CommandResult on_open(
const vix::realtime::RoomState &,
const vix::realtime::RoomContext &) override
{
return vix::realtime::CommandResult::accepted();
}The room reaches Open only after the open lifecycle completes successfully.
Close lifecycle
A handler can participate when the room closes:
vix::realtime::CommandResult on_close(
const vix::realtime::RoomState &,
const vix::realtime::RoomContext &) override
{
return vix::realtime::CommandResult::accepted();
}If the handler rejects the close operation, the room returns to Open.
Lifecycle events
Lifecycle callbacks can also return events.
For example, a join callback can produce an event:
vix::realtime::RoomEvent event{
context.room_id(),
"member.joined"};
return vix::realtime::CommandResult::accepted(
std::move(event));Accepted lifecycle events follow the same authoritative path as normal command events:
handler
|
v
event
|
v
persistence
|
v
RoomState::apply()Keep handlers focused
A handler should normally be responsible for:
validating commands
reading current state
applying application rules
producing eventsIt should not directly manage:
event persistence
snapshot storage
WebSocket connections
session transport
event broadcasting
room version assignment
event ID assignmentThose responsibilities belong to the Realtime runtime.
Simple example
A small counter handler can be written as:
class CounterHandler final
: public vix::realtime::RoomHandler
{
public:
vix::realtime::CommandResult handle_command(
const vix::realtime::RoomCommand &command,
const vix::realtime::RoomState &,
const vix::realtime::RoomContext &) override
{
if (command.type() != "counter.increment")
{
return vix::realtime::CommandResult::ignored();
}
vix::realtime::RoomEvent event{
command.room_id(),
"counter.incremented"};
return vix::realtime::CommandResult::accepted(
std::move(event));
}
};The important separation is:
RoomHandler
decides what should happen
Room
makes it authoritativeContinue with Room Manager for how rooms, sessions, membership, and runtime dependencies are coordinated.