Skip to content

Commit 40862fd

Browse files
committed
feat: Document message handlers subsystem
- Added a new example demonstrating the creation, registration, and dispatch of message handlers in the irsol framework. - Refactored handler context to use `std::shared_ptr` for better memory management. - Updated various handler implementations to accommodate the new context structure.
1 parent 166fb6e commit 40862fd

38 files changed

Lines changed: 250 additions & 65 deletions

src/examples/00-logging-and-asserting/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ Different sinks are configured to have a different _runtime_ log level by defaul
7676
7777
| Compilation mode | Debug | Release |
7878
| ---------------- | ------- | ------- |
79-
| Console logger | `debug` | `info` |
79+
| Console logger | `trace` | `info` |
8080
| File logger | `trace` | `info` |
8181
8282
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
cmake_minimum_required(VERSION 3.13.0)
2+
3+
project(04-message-handlers)
4+
5+
add_executable(
6+
${PROJECT_NAME}
7+
main.cpp
8+
)
9+
target_compile_definitions(${PROJECT_NAME} PRIVATE PROGRAM_NAME="${PROJECT_NAME}")
10+
11+
target_link_libraries(${PROJECT_NAME}
12+
irsol::core
13+
)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# 04-message-handlers {#message_handlers}
2+
@see examples/04-message-handlers/main.cpp
3+
4+
Welcome to the `irsol::server::handlers` subsystem!
5+
This guide introduces you to the core concepts and practical usage of **handlers** and the **message-handler** system in the irsol framework.
6+
7+
## What are Handlers?
8+
9+
Handlers are the building blocks that connect protocol messages (such as commands, assignments, and inquiries) to your application logic. Each handler is responsible for processing a specific type of protocol message and producing an appropriate response.
10+
11+
Handlers are typically implemented as C++ classes (deriving from a base handler template @ref irsol::server::handlers::internal::HandlerBase), but can also be defined as lambda functions for rapid prototyping or simple use cases.
12+
13+
## What is a MessageHandler?
14+
15+
The @ref irsol::server::handlers::MessageHandler is a central registry and dispatcher. It:
16+
17+
- Receives parsed protocol messages and forwards them to the correct handler.
18+
- Allows you to register, replace, or remove handlers at runtime.
19+
20+
## Typical Workflow
21+
22+
1. **Implement a Handler**
23+
Create a handler class (or lambda) that processes a specific protocol message.
24+
25+
2. **Register the Handler**
26+
Register your handler with a `MessageHandler` instance, associating it with a message identifier.
27+
28+
3. **Dispatch Messages**
29+
When a protocol message is received, the `MessageHandler` looks up the handler for its identifier and invokes it, passing the message and client context.
30+
31+
## Example
32+
@see examples/04-message-handlers/main.cpp
33+
See the `main` file in this folder for a complete example showing:
34+
35+
- How to define a custom handler.
36+
- How to register it with a `MessageHandler`.
37+
- How to dispatch messages and verify handler invocation.
38+
39+
## When to Use `Lambda` Handlers
40+
41+
For simple or one-off logic, you can use a lambda handler instead of a full class. This is especially useful for:
42+
43+
- Prototyping new features.
44+
- Writing test or mock handlers.
45+
- Reducing boilerplate for trivial message processing.
46+
47+
48+
For more details, see the documentation in @ref irsol/server/handlers/base.hpp and @ref irsol/server/handlers/lambda_handler.hpp, and the API reference.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/// @file examples/04-message-handlers/main.cpp
2+
/// @brief Demonstrates creation, registration, and dispatch of handlers using
3+
/// @ref irsol::server::handlers::MessageHandler.
4+
5+
#include "irsol/irsol.hpp"
6+
7+
#include <memory>
8+
#include <string>
9+
#include <vector>
10+
11+
/// @brief Returns the program name, typically used for logging.
12+
/// If `PROGRAM_NAME` is not defined at compile time, returns `"message-handlers-demo"`.
13+
const std::string
14+
getProgramName()
15+
{
16+
#ifndef PROGRAM_NAME
17+
#define PROGRAM_NAME "message-handlers-demo"
18+
#endif
19+
return PROGRAM_NAME;
20+
}
21+
22+
/// @brief Example custom handler for Assignment messages.
23+
class MyAssignmentHandler : public irsol::server::handlers::AssignmentHandler
24+
{
25+
public:
26+
MyAssignmentHandler(std::shared_ptr<irsol::server::handlers::Context> ctx)
27+
: irsol::server::handlers::AssignmentHandler(ctx)
28+
{}
29+
30+
/// Overrides the base operator() as we skip the collection of the client's Session
31+
/// in this demo, which is the behavior of the base class.
32+
std::vector<irsol::server::handlers::out_message_t> operator()(
33+
IRSOL_MAYBE_UNUSED const irsol::types::client_id_t& clientId,
34+
irsol::protocol::Assignment&& message) override
35+
{
36+
return process(nullptr, std::move(message));
37+
}
38+
39+
protected:
40+
std::vector<irsol::server::handlers::out_message_t> process(
41+
IRSOL_MAYBE_UNUSED std::shared_ptr<irsol::server::ClientSession> client,
42+
irsol::protocol::Assignment&& message) override
43+
{
44+
IRSOL_LOG_INFO("[MyAssignmentHandler] Called with {}.", message.toString());
45+
// Respond with a Success message
46+
// Due to move-only semantics of `Success`, we must create the response vector
47+
// in this way. Using brace-initialization like `return
48+
// {irsol::protocol::Success::from(message)};` would not work here.
49+
std::vector<irsol::server::handlers::out_message_t> result;
50+
result.emplace_back(irsol::protocol::Success::from(message));
51+
return result;
52+
}
53+
};
54+
55+
/// @brief Demonstrates handler creation, registration, and dispatch.
56+
void
57+
demoHandlers()
58+
{
59+
// Don't pass a Context, as we don't need it in this demo
60+
std::shared_ptr<irsol::server::handlers::Context> ctx = nullptr;
61+
62+
// Create a MessageHandler instance
63+
irsol::server::handlers::MessageHandler msgHandler;
64+
65+
// Create and register a custom Assignment handler for "foo" identifier.
66+
msgHandler.registerHandler<irsol::protocol::Assignment>(
67+
"foo", irsol::server::handlers::makeHandler<MyAssignmentHandler>(ctx));
68+
69+
// Simulate a session and client id
70+
irsol::types::client_id_t clientId = irsol::utils::uuid();
71+
72+
// Dispatch messages to the MessageHandler
73+
74+
// Create test Assignment messages
75+
irsol::protocol::Assignment fooMsg("foo", 42);
76+
IRSOL_LOG_INFO("Dispatching {}...", fooMsg.toString());
77+
auto fooResult = msgHandler.handle(clientId, std::move(fooMsg));
78+
IRSOL_LOG_INFO("fooResult size: {}", fooResult.size());
79+
for(const auto& msg : fooResult) {
80+
IRSOL_LOG_INFO("Response: {}", irsol::protocol::toString(msg));
81+
}
82+
83+
// Try dispatching an unregistered identifier
84+
irsol::protocol::Assignment unknownMsg("baz", 0);
85+
IRSOL_LOG_INFO("Dispatching {} (should not find handler)...", unknownMsg.toString());
86+
auto unknownResult = msgHandler.handle(clientId, std::move(unknownMsg));
87+
IRSOL_LOG_INFO("unknownResult size: {}", unknownResult.size());
88+
for(const auto& msg : unknownResult) {
89+
IRSOL_LOG_INFO("Response: {}", irsol::protocol::toString(msg));
90+
}
91+
}
92+
93+
/// @brief Main entry point for the handler demo application.
94+
int
95+
main()
96+
{
97+
// Construct log file path based on program name
98+
std::string logPath = "logs/" + getProgramName() + ".log";
99+
irsol::initLogging(logPath.c_str());
100+
101+
// Enable custom assertion handler
102+
irsol::initAssertHandler();
103+
104+
demoHandlers();
105+
return 0;
106+
}

src/examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ add_subdirectory(00-logging-and-asserting)
1616
add_subdirectory(01-loading-images)
1717
add_subdirectory(02-interacting-with-camera-features)
1818
add_subdirectory(03-message-protocols)
19+
add_subdirectory(04-message-handlers)
1920
add_subdirectory(simple)
2021
add_subdirectory(camera_socket)
2122

src/examples/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ These examples serve as practical references to help you get started and underst
1818
* @subpage message_protocols
1919
Covers the topic of how messages are parsed, processed and serialized back to the client in the client-server architecture implemented in the `irsol` project.
2020

21+
* @subpage message_handlers
22+
Covers the topic of how to implement and use message handlers in the `irsol` framework, including the @ref irsol::server::handlers::MessageHandler class and lambda handlers for rapid prototyping.
23+

src/irsol/include/irsol/server/app.hpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,17 @@ class App
203203
* registerLambdaHandler<protocol::Command>(
204204
* "custom_cmd",
205205
* ctx,
206-
* [](handlers::Context& ctx, const irsol::types::client_id_t& clientId, protocol::Command&&
207-
* cmd) { return std::vector<protocol::OutMessage>{...};
206+
* [](std::shared_ptr<handlers::Context> ctx, const irsol::types::client_id_t& clientId,
207+
* protocol::Command&& cmd) { return std::vector<protocol::OutMessage>{...};
208208
* }
209209
* );
210210
* ```
211211
*/
212212
template<typename InMessageT, typename LambdaT>
213-
void
214-
registerLambdaHandler(const std::string& identifier, handlers::Context& ctx, LambdaT&& lambda)
213+
void registerLambdaHandler(
214+
const std::string& identifier,
215+
std::shared_ptr<handlers::Context> ctx,
216+
LambdaT&& lambda)
215217
{
216218
auto handler = handlers::makeLambdaHandler<InMessageT>(ctx, std::forward<LambdaT>(lambda));
217219
auto& messageHandler = *m_messageHandler;

src/irsol/include/irsol/server/client/session.hpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,8 @@ class App;
3939
*
4040
* In addition, the session holds a reference to the central @ref irsol::server::App instance,
4141
* which enables it to interact with the global server context (e.g., for broadcasting).
42-
*
43-
* The class derives from `std::enable_shared_from_this` to allow safe
44-
* creation of `std::shared_ptr` instances to itself. This is required for
45-
* asynchronous operations and thread-safe lifetime management.
4642
*/
47-
class ClientSession : public std::enable_shared_from_this<ClientSession>
43+
class ClientSession
4844
{
4945
public:
5046
/**

src/irsol/include/irsol/server/handlers.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@
3838
* @see irsol::server::handlers::MessageHandler
3939
*/
4040

41+
namespace irsol {
42+
namespace server {
43+
4144
/**
4245
* @namespace irsol::server::handlers
4346
* @brief Contains all logic for dispatching and implementing protocol message handlers.
@@ -61,8 +64,6 @@
6164
* @see irsol::server::handlers::MessageHandler
6265
* @see irsol::protocol
6366
*/
64-
namespace irsol {
65-
namespace server {
6667
namespace handlers {}
6768
}
6869
}

src/irsol/include/irsol/server/handlers/assignment_frame_rate.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class AssignmentFrameRateHandler : public AssignmentHandler
2828
* @brief Constructs the AssignmentFrameRateHandler.
2929
* @param ctx Handler context.
3030
*/
31-
AssignmentFrameRateHandler(Context ctx);
31+
AssignmentFrameRateHandler(std::shared_ptr<Context> ctx);
3232

3333
protected:
3434
/**

0 commit comments

Comments
 (0)