Expand description
§ygopro-handler
A type-erased, plugin-based message handler framework.
This crate provides the machinery to dispatch messages to registered handlers,
extract request data, and combine handler responses. Every incoming message flows
through a Processor and is handled by a set of handlers keyed by message type.
The extractor/handler pattern mirrors axum: handlers are
plain functions whose parameters are pulled from a Bundle via FromRequest,
and whose return value is converted into a response via IntoResponse. Any type
implementing FromRequest can be a parameter, and any type implementing
IntoResponse can be returned — exactly like axum’s
FromRequest and
IntoResponse.
Unlike axum, however, a message is not handled by a single handler but by a chain
of handlers (all those registered for the message key, plus the globals), and the
chain can be aborted mid-way via StopFlag. This makes it easy to layer
plugins: each plugin registers its own handlers for a message, and any one of them
can stop the chain or replace the message that the rest see.
§Design goals
- Handlers as closures. A handler is any callable that takes extractable
parameters and returns a value convertible into an
extract::Response. Up to 16 parameters are supported; each is pulled from aBundleviaFromRequest. - Message key dispatch. Each message carries a
MessageKey(usually au8flag). TheProcessorroutes a message to the handlers registered for that key, plus any global handlers. - Chained, abortable processing. A message flows through every handler registered
for its key, plus the globals, in priority order. Any handler can abort the chain
via
StopFlagor replace the message that downstream handlers see. Their outputs are combined throughstd::ops::Mulon the response, letting one handler replace the message, another swallow it, and another terminate the room. - Type-erased handlers.
SyncHandler,AsyncHandlerandTowerHandlererase the concrete handler type so a heterogeneous set of handlers can live in a singleProcessor.
§Soundness
This crate is unsafe. You must carefully handle the parameters to prevent
undefined behaviour, especially dual mutable references — a handler that takes
two &mut parameters can alias the same memory. The extraction uses raw-pointer
casts because the Rust compiler cannot properly calculate the required lifetimes,
and due to the architecture the crate cannot offer the same guarantees that axum’s
FromRequest provides.
§Performance
The crate offers three handler wrappers, trading features against speed:
TowerHandler— the most feature-complete, and the slowest. Between the handler and the processor it inserts a towerServicelayer (HandlerService), a boxed future (HandlerServiceFuture), and aBoxCloneService, so every call pays for several layers of boxing and aoneshotdispatch.AsyncHandler— gives up the tower adaptation layer. It holds the handler in anArc<dyn Call>and boxes only the future, so it drops the extraServiceboxing while staying cloneable through a cheapArcclone.SyncHandler— gives up the async nature ofHandler: its future is alwaysReady. In exchange it boxes nothing per call, drops the'staticbound on the request, state, and response, and enables the dual-state trick (handler::sync_handler::WithSubState).
Compared with the C++ original, which dispatches messages with a direct switch or a
virtual call, every handler here pays for at least one heap-allocated future and a
type-erasure indirection (a trait object or a function pointer) per invocation.
Processor::process additionally awaits the whole handler chain per item, moving
the Bundle through it. For the hot path (game messages) this is one boxed future
per handler, which is acceptable at the rate messages are emitted.
§Example
A processor dispatches a message to the handlers registered for its key:
use ygopro_handler::Processor;
use ygopro_handler::TowerHandler;
use ygopro_handler::Bundle;
use ygopro_handler::State;
use ygopro_handler::extract::Request;
use ygopro_handler::extract::Response;
type Req = Request<u8, ()>;
type Res = Response<u8>;
let mut processor = Processor::<u8, Req, State, Res>::new();
processor.register(7, TowerHandler::new(0, "test", "example", |message: &u8| -> Res {
if *message == 7 {
Res::Replace(*message)
} else {
Res::Continue
}
}));
let bundle = Bundle::new(Request { message: 7, extra: () }, State::new(), Res::Continue);
let result = tokio::runtime::Runtime::new().unwrap().block_on(processor.process_bundle(bundle, 7));
assert!(matches!(result.response, Res::Replace(7)));Re-exports§
pub use handler::tower_handler::TowerHandler;pub use handler::async_handler::AsyncHandler;pub use handler::sync_handler::SyncHandler;pub use handler::*;pub use processor::*;
Modules§
- extract
- Practical types used in the ygopro message flow.
- handler
- The handler abstraction: extract parameters, produce a response, and combine them.
- processor
- The message dispatcher: routes messages to handlers by key.
Traits§
- Room
Provider - Abstract of a duel.