-
Notifications
You must be signed in to change notification settings - Fork 0
Dispatch
CefDispatch is the WebUserInterface plugin's format-agnostic message routing module. It maps a uint32 message type to a factory, converts raw payload bytes into a typed C++ value, and optionally invokes a typed handler for that value.
Source: Source/CefDispatch
CefDispatch is intentionally independent of WebSockets, protobuf, JSON, and CEF. A transport such as CefWebSocketServer supplies the message type and bytes; CefDispatch decides how those bytes become an application value and where that value is handled.
Use CefDispatch when a message stream contains multiple payload types and the application should not need a large switch statement or transport-specific decoding code at every call site.
It provides:
- one registry for
MessageType -> factoryrouting; - typed value wrappers for protobuf messages, structs, strings, or byte containers;
- optional
MessageType -> handlerrouting after decoding; - replace/unregister support for editor tools, tests, and controlled runtime reconfiguration;
- thread-safe registry access using read/write locks;
- no protobuf dependency in the core module.
Use it with CefWebSocketServer when browser messages need typed routing. Use it without WebSockets when another transport already provides a message type and payload bytes.
CefDispatch is a good fit when:
- message types are stable numeric IDs;
- each type has a distinct decoder or application value;
- multiple systems need to consume the same decoded message;
- transport and business logic should remain separate;
- the project uses a mixture of protobuf and custom binary/text payloads.
It may be unnecessary for a very small protocol with only one or two messages. In that case, decoding directly can be simpler. Do not treat the registry as a security boundary: validate message sizes, source permissions, schema fields, and authorization before invoking gameplay behavior.
message type + raw bytes
|
v
FCefDispatchRegistry::Decode
|
v
FCefDispatchFactory -> TUniquePtr<ICefDispatchValue>
|
+--> typed value read by the caller
|
+--> FCefDispatchHandlerRegistry::Handle / Dispatch
|
v
typed application handler
The factory owns decoding. The returned ICefDispatchValue owns the decoded value until the caller releases its TUniquePtr.
Message types are uint32 values. Define one authoritative mapping for the project and keep it shared by the sender and receiver. Avoid reusing an ID for a different schema without a protocol-version change.
| Message type | Meaning | Decoded value |
|---|---|---|
1001 |
UI text command | FString |
1002 |
Settings update | project struct |
2001 |
protobuf event | generated protobuf message |
The module does not assign IDs or persist a schema. That responsibility belongs to the application protocol.
Get the module registry and register a factory that returns a typed dispatch value:
#include "CefDispatch.h"
TSharedPtr<FCefDispatchRegistry> registry =
FCefDispatchModule::Get().GetRegistry();
registry->RegisterFactory(
1001,
[](uint32 messageType, const TArray<uint8>& payload, FString& error)
-> TUniquePtr<ICefDispatchValue>
{
if (payload.Num() > 4096)
{
error = TEXT("Text message is too large");
return nullptr;
}
FUTF8ToTCHAR converter(
reinterpret_cast<const ANSICHAR*>(payload.GetData()),
payload.Num());
FString text(converter.Length(), converter.Get());
return MakeCefDispatchValue(MoveTemp(text));
});Factories can return any value type that is copyable or movable into TCefDispatchValue<T>\), including FString, a project struct, a protobuf-generated message, a raw byte wrapper, or another application-owned C++ type. Return nullptrand setOutError` when validation or decoding fails.
Call Decode when the caller needs to inspect or pass on the decoded value:
TUniquePtr<ICefDispatchValue> value;
FString error;
const ECefDispatchFactoryResult result =
registry->Decode(messageType, bytes, value, error);
if (result == ECefDispatchFactoryResult::Ok && value.IsValid())
{
if (const TCefDispatchValue<FString>* textValue =
CefDispatchTryGetValue<FString>(*value))
{
UE_LOG(LogTemp, Log, TEXT("Text: %s"), *textValue->GetValue());
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Dispatch failed: %s"), *error);
}CefDispatchTryGetValue<T> checks the runtime type token before the cast. It returns nullptr for a type mismatch, so callers should not assume that a route's value is a particular type unless the registration contract guarantees it.
FCefDispatchRegistry::Decode returns:
| Result | Meaning |
|---|---|
Ok |
A factory ran and returned a valid typed value. |
RouteNotFound |
No factory is registered for the message type. |
FactoryFailed |
The factory returned an invalid/null value. |
InvalidFactory |
A registered factory is not callable. |
Factories should provide a useful error string for malformed or unsupported payloads.
The handler registry can decode a payload and invoke a handler in one call:
TSharedPtr<FCefDispatchHandlerRegistry> handlers =
FCefDispatchModule::Get().GetHandlerRegistry();
handlers->RegisterTypedHandler<FString>(
1001,
[](const FString& text)
{
UE_LOG(LogTemp, Log, TEXT("Handled text: %s"), *text);
});
FString error;
const ECefDispatchHandlerResult result =
handlers->Dispatch(1001, bytes, error);Typed handlers may use these callable forms:
void(const T&)
bool(const T&)
void(uint32, const T&)
bool(uint32, const T&)
void(const T&, FString&)
bool(const T&, FString&)
void(uint32, const T&, FString&)
bool(uint32, const T&, FString&)
Returning bool lets the handler report failure. When an error string is accepted, write a useful message before returning false. Use Handle instead of Dispatch when the value has already been decoded.
FCefDispatchHandlerRegistry::Dispatch and Handle return:
| Result | Meaning |
|---|---|
Ok |
Decode and handler invocation succeeded. |
DecodeRegistryUnavailable |
The handler registry has no decode registry. |
DecodeRouteNotFound |
No factory exists for the message type. |
DecodeFailed |
Factory decoding failed or returned no value. |
HandlerNotFound |
No handler exists for the message type. |
HandlerTypeMismatch |
The decoded value type differs from the registered handler type. |
HandlerFailed |
The handler returned false or otherwise failed. |
InvalidHandler |
The registered handler is not callable. |
For route declarations that should register automatically, use the macros from CefDispatchRegistration.h:
#include "CefDispatch.h"
CEF_DISPATCH_REGISTER_FACTORY(
2001,
[](uint32, const TArray<uint8>& payload, FString& error)
-> TUniquePtr<ICefDispatchValue>
{
FMyPayload decoded;
if (!DecodeMyPayload(payload, decoded, error))
{
return nullptr;
}
return MakeCefDispatchValue(MoveTemp(decoded));
});
CEF_DISPATCH_REGISTER_TYPED_HANDLER(
2001,
FMyPayload,
[](const FMyPayload& value)
{
ConsumeMyPayload(value);
});Available forms include:
-
CEF_DISPATCH_REGISTER_FACTORY; -
CEF_DISPATCH_REGISTER_FACTORY_REPLACE; -
CEF_DISPATCH_REGISTER_HANDLER; -
CEF_DISPATCH_REGISTER_HANDLER_REPLACE; -
CEF_DISPATCH_REGISTER_TYPED_HANDLER; -
CEF_DISPATCH_REGISTER_TYPED_HANDLER_REPLACE.
These macros create static registrar objects. If CefDispatch has not started yet, registration is deferred and applied during StartupModule. This makes static registration usable from modules that load before CefDispatch. Prefer the non-replace macros by default; duplicate message types are rejected unless replacement is explicitly allowed.
CefDispatch does not depend on protobuf. A protobuf-enabled module can register a factory that parses its payload and wraps the generated message:
CEF_DISPATCH_REGISTER_FACTORY(
2001,
[](uint32, const TArray<uint8>& payload, FString& error)
-> TUniquePtr<ICefDispatchValue>
{
MyProto::Message message;
if (!message.ParseFromArray(payload.GetData(), payload.Num()))
{
error = TEXT("Invalid MyProto::Message payload");
return nullptr;
}
return MakeCefDispatchValue(MoveTemp(message));
});The transport can remain binary and generic while the factory owns schema parsing. Pair this with CefProtobuf when using the project's protobuf runtime.
-
FCefDispatchModule::Get()loads the module and returns its singleton module instance. -
FCefDispatchModule::IsAvailable()checks whether the module is already loaded. -
GetRegistry()returns the factory registry. -
GetHandlerRegistry()returns the handler registry sharing the same decode registry. - Registering a duplicate factory or handler fails unless
bAllowReplaceistrue. -
UnregisterFactoryandUnregisterHandlerremove a route and report whether anything was removed. - Registry counts are available through
GetFactoryCountandGetHandlerCount. - The registries are cleared when the module shuts down.
If a plugin can load before CefDispatch, use the registration macros or RegisterDeferredFactory/RegisterDeferredHandler rather than assuming the registry pointer is already initialized.
Registry lookups and modifications are protected internally, so concurrent registration and decode operations are supported at the registry level. Your factories and handlers are still responsible for their own shared state.
Keep decoding and handlers deterministic and lightweight. If a handler needs to modify game-thread-only Unreal state, enqueue that work to the game thread. Do not assume that a dispatch call made from a WebSocket worker is running on the game thread.
- Reusing a message ID for a different payload without a protocol version change.
- Registering a factory but forgetting to register a handler before calling
Dispatch. - Returning
nullptrfrom a factory without settingOutError. - Registering a handler for
FStringwhile the factory returns a project struct. - Using replace registration unintentionally and hiding a duplicate route.
- Passing unvalidated network bytes directly into protobuf or custom decoders.
- Calling gameplay APIs from a worker-thread handler.