Vix.cpp v2.8.5 is here Read the blog
Skip to content

Commands and Results

Commands describe what a client wants to do in a room.

A RoomCommand does not modify room state directly. It is passed to the room handler, which returns a CommandResult.

text
RoomCommand
    |
    v
RoomHandler
    |
    v
CommandResult

Create a command

A command requires:

  • a room identifier
  • a session identifier
  • a command type

For example:

cpp
vix::realtime::RoomCommand command{
    vix::realtime::RoomId{"room-1"},
    vix::realtime::SessionId{"session-1"},
    "counter.increment"};

A payload can also be provided:

cpp
vix::realtime::JsonObject payload;
payload.set_i64("amount", 1);

vix::realtime::RoomCommand command{
    vix::realtime::RoomId{"room-1"},
    vix::realtime::SessionId{"session-1"},
    "counter.increment",
    std::move(payload)};

Command types

The command type is application-defined.

Examples:

text
counter.increment
message.send
player.move

A command type:

  • cannot be empty
  • cannot exceed 128 characters
  • can contain letters, digits, ., -, and _
  • cannot start or end with .
  • cannot contain consecutive dots

For example:

text
message.send      valid
message_send      valid
message-send      valid

.message          invalid
message.          invalid
message..send     invalid
message/send      invalid

Invalid commands throw ErrorCode::InvalidCommand during validation.

Read command data

A handler can inspect the command:

cpp
command.room_id();
command.session_id();
command.type();
command.payload();

For example:

cpp
if (command.type() == "counter.increment")
{
    // Handle the command.
}

Request identifier

A command can contain a request identifier:

cpp
vix::realtime::RoomCommand command{
    roomId,
    sessionId,
    "counter.increment",
    {},
    "request-1"};

Read it with:

cpp
command.request_id();

When the handler produces an event without its own request identifier, the room runtime propagates the command request identifier to that event.

Correlation identifier

A correlation identifier can be attached separately:

cpp
command.set_correlation_id(
    "operation-42");

Read it with:

cpp
command.correlation_id();

The runtime also propagates it to produced events when those events do not already define one.

Expected room version

A command can require the room to still be at a specific version:

cpp
command.set_expected_version(
    vix::realtime::RoomVersion{5});

If the room is no longer at version 5, the command is rejected before the handler runs.

The constraint can be removed with:

cpp
command.clear_expected_version();

See Rooms for room version behavior.

Handle a command

Application logic implements RoomHandler::handle_command():

cpp
vix::realtime::CommandResult handle_command(
    const vix::realtime::RoomCommand &command,
    const vix::realtime::RoomState &state,
    const vix::realtime::RoomContext &context) override
{
    if (command.type() == "counter.increment")
    {
        return vix::realtime::CommandResult::accepted();
    }

    return vix::realtime::CommandResult::ignored();
}

The handler can inspect the current state, but it should not modify it directly.

To change authoritative state, return events from an accepted result.

Command result statuses

A CommandResult has one of three statuses:

text
Accepted
Rejected
Ignored

Check them with:

cpp
result.is_accepted();
result.is_rejected();
result.is_ignored();

The status is also available directly:

cpp
auto status = result.status();

Accepted

Use accepted() when the command is valid.

An accepted command can produce no events:

cpp
return vix::realtime::CommandResult::accepted();

or one event:

cpp
vix::realtime::RoomEvent event{
    command.room_id(),
    "counter.incremented"};

return vix::realtime::CommandResult::accepted(
    std::move(event));

It can also produce multiple events.

Only events from an accepted result enter the room commit path.

text
Accepted
   |
   v
events
   |
   v
persist
   |
   v
apply to RoomState

An accepted result is also valid with zero events.

Rejected

Use rejected() when an application rule prevents the command from being accepted.

cpp
return vix::realtime::CommandResult::rejected(
    vix::realtime::ErrorCode::CommandRejected,
    "command is not allowed");

A rejected result:

  • must contain a non-None error code
  • cannot contain events
  • does not modify authoritative room state

Check the rejection information with:

cpp
result.error_code();
result.message();

A normal application rejection does not by itself put the room into the Failed state.

Ignored

Use ignored() when the command requires no action:

cpp
return vix::realtime::CommandResult::ignored();

An optional message can be provided:

cpp
return vix::realtime::CommandResult::ignored(
    "nothing to update");

An ignored result cannot contain events.

It does not modify room state.

Accepted, rejected, and ignored

The three outcomes have different meanings:

StatusEvents allowedState change
AcceptedYesThrough produced events
RejectedNoNo
IgnoredNoNo

An accepted command with no events is still accepted, but it produces no authoritative state transition.

Return an event

A common handler pattern is:

cpp
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 runtime then handles persistence and state application.

The handler does not need to assign the persistent event identifier or authoritative room version.

Multiple events

One command may produce several events:

cpp
std::vector<vix::realtime::RoomEvent> events;

events.emplace_back(
    command.room_id(),
    "message.sent");

events.emplace_back(
    command.room_id(),
    "message.indexed");

return vix::realtime::CommandResult::accepted(
    std::move(events));

The events are committed as one batch by the room.

Their order is preserved.

Result message

Any result can contain a human-readable message.

cpp
auto result =
    vix::realtime::CommandResult::accepted();

result.set_message("updated");

The message is informational. It is not authoritative room state.

Metadata

Commands and results can carry application-defined metadata.

For a command:

cpp
vix::realtime::JsonObject metadata;
metadata.set_string("source", "mobile");

command.set_metadata(
    std::move(metadata));

For a result:

cpp
vix::realtime::JsonObject metadata;
metadata.set_string("kind", "counter");

result.set_metadata(
    std::move(metadata));

Metadata can carry contextual or diagnostic information, but it must not be used as an authoritative room state change.

Runtime checks

When commands are executed through Server or RoomManager, the runtime also verifies conditions outside the handler.

For example, it verifies that:

  • the session exists
  • the session is not closed
  • the room exists
  • the session belongs to the room

These runtime failures are different from a business rule intentionally returned as CommandResult::rejected().

Execution model

The complete command path is:

text
client intent
     |
     v
RoomCommand
     |
     v
runtime checks
     |
     v
RoomHandler
     |
     v
CommandResult
     |
     +---- Rejected
     |
     +---- Ignored
     |
     +---- Accepted
              |
              v
          RoomEvent(s)
              |
              v
          persistence
              |
              v
        RoomState::apply()

Commands express intent. CommandResult expresses the application's decision. Accepted events define the authoritative state transition.

Continue with Events for the event model and delivery audiences.

Released under the MIT License.