Message
The message class used for actor-to-actor communication in ControlBee. Messages are immutable and contain a sender reference, message name, and optional payload.
Namespace: ControlBee.Models
Overview
Messages are the primary communication mechanism between actors. Each message is immutable once created and contains:
- Sender - Reference to the actor sending the message
- Name - String identifier for the message type
- Payload - Optional data carried by the message
- Id - Unique identifier for message tracking
- RequestId - Optional identifier linking this message to a request
Constructors
Basic Constructor
public Message(IActor sender, string name)
public Message(IActor sender, string name, object? payload)
Parameters:
sender— The actor sending this messagename— The message name/type identifierpayload— Optional data payload
Usage Example:
using Dict = System.Collections.Generic.Dictionary<string, object?>;
// Simple message
var msg1 = new Message(this, "Start");
// Message with simple data
var msg2 = new Message(this, "SetSpeed", 100.0);
// Message with Dict payload
var msg3 = new Message(this, "ProcessCell", new Dict
{
["Row"] = 5,
["Col"] = 3
});
Request-Response Constructor
public Message(Guid requestId, IActor sender, string name, object? payload)
public Message(Message requestMessage, IActor sender, string name, object? payload)
Parameters:
requestId— The GUID of the original request messagerequestMessage— The original request message to respond tosender— The actor sending this responsename— The response message namepayload— Optional response data
Usage Example:
// Responding to a request
public override bool ProcessMessage(Message message)
{
if (message.Name == "GetStatus")
{
// Send response linked to request
var response = new Message(message, this, "StatusResponse", currentStatus);
message.Sender.Send(response);
return true;
}
return false;
}
Properties
Sender
public IActor Sender { get; }
The actor that sent this message. Used to identify the source and send responses.
Usage Example:
public override bool ProcessMessage(Message message)
{
if (message.Name == "QueryPosition")
{
// Send response back to sender
message.Sender.Send(new Message(this, "PositionResponse", currentPosition));
return true;
}
return false;
}
Name
public string Name { get; }
The message type identifier. Used in switch statements to handle different message types.
Usage Example:
switch (message.Name)
{
case "Start":
HandleStart();
return true;
case "Stop":
HandleStop();
return true;
default:
return false;
}
Payload
public object? Payload { get; }
The message data payload. Can be any type - simple values, objects, or dictionaries.
Usage Example:
// Simple payload
if (message.Name == "SetSpeed" && message.Payload is double speed)
{
Motor.Speed = speed;
return true;
}
// Object payload
if (message.Name == "SetPosition" && message.Payload is Position3D pos)
{
MoveToPosition(pos);
return true;
}
DictPayload
public Dictionary<string, object?>? DictPayload { get; }
Convenience property that returns Payload as a Dictionary if it is one, otherwise null. Automatically set when payload is a Dictionary<string, object?>.
Usage Example:
if (message.Name == "LoadProject" && message.DictPayload != null)
{
var index = message.DictPayload.GetValueOrDefault("Index") as int?;
var filePath = message.DictPayload.GetValueOrDefault("ProjectFile") as string;
if (filePath != null)
{
LoadProject(index ?? 0, filePath);
}
return true;
}
Id
public Guid Id { get; }
Unique identifier for this message. Automatically generated when the message is created.
Use Cases:
- Message tracking and logging
- Debugging message flow
- Correlating messages across actors
- Performance monitoring
Usage Example:
Logger.Info($"Received message {message.Id}: {message.Name} from {message.ActorName}");
RequestId
public Guid RequestId { get; }
Identifier linking this message to a request message. Guid.Empty if not a response.
Usage Example:
// Check if this is a response to a specific request
if (message.RequestId == pendingRequestId)
{
ProcessResponse(message);
pendingRequestId = Guid.Empty;
return true;
}
ActorName
public string ActorName { get; }
Convenience property that returns Sender.Name. Useful for logging and debugging.
Usage Example:
Logger.Debug($"Processing {message.Name} from {message.ActorName}");
Special Messages
Empty Message
public static readonly Message Empty
A singleton empty message used as a placeholder. Should not be sent to actors.
TimerMessage
A special system message sent periodically to actors that have configured a timer interval.
Message Name: TimerMessage.MessageName
Configuration:
Set the TimerMilliseconds property in the actor constructor to enable periodic timer messages:
public FlipperActor(ActorConfig config) : base(config)
{
TimerMilliseconds = 200; // Receive TimerMessage every 200ms
// ... rest of initialization
}
Usage:
Timer messages are commonly used for scanning operations, periodic status checks, or polling sensors:
public override bool ProcessMessage(Message message)
{
switch (message.Name)
{
case TimerMessage.MessageName:
return Scan(); // Periodic operation
// ... other messages
}
return false;
}
private bool Scan()
{
// Check sensors, update status, etc.
if (SensorChanged())
{
UpdateStatus();
return true;
}
return false;
}
Note: If TimerMilliseconds is not set (or set to 0), no timer messages will be sent.
Message Patterns
1. Simple Command
// Sender
NextStage.Send(new Message(this, "Start"));
2. Command with Data
// Sender
Picker.Send(new Message(this, "SetSpeed", 150.0));
// Receiver
if (message.Name == "SetSpeed" && message.Payload is double speed)
{
Motor.Speed = speed;
return true;
}
3. Complex Data with Dict
// Sender
Head.Send(new Message(this, "Pick", new Dict
{
["X"] = 100.5,
["Y"] = 200.3,
["Z"] = 50.0,
["Speed"] = 100.0
}));
// Receiver
if (message.Name == "Pick" && message.DictPayload != null)
{
var x = message.DictPayload.GetValueOrDefault("X") as double?;
var y = message.DictPayload.GetValueOrDefault("Y") as double?;
var z = message.DictPayload.GetValueOrDefault("Z") as double?;
var speed = message.DictPayload.GetValueOrDefault("Speed") as double? ?? 50.0;
if (x.HasValue && y.HasValue && z.HasValue)
{
PickAt(x.Value, y.Value, z.Value, speed);
}
return true;
}
4. Request-Response Pattern
// Request
var requestId = Guid.NewGuid();
var request = new Message(requestId, this, "GetStatus");
peer.Send(request);
pendingRequestId = requestId;
// Response (in peer actor)
if (message.Name == "GetStatus")
{
var response = new Message(message, this, "StatusResponse", GetCurrentStatus());
message.Sender.Send(response);
return true;
}
// Process response (back in original actor)
if (message.Name == "StatusResponse" && message.RequestId == pendingRequestId)
{
HandleStatusResponse(message.Payload);
pendingRequestId = Guid.Empty;
return true;
}
Best Practices
1. Use Descriptive Names
new Message(this, "StartTransfer"); // ✅ Clear intent
new Message(this, "Msg1"); // ❌ Unclear
2. Use Dict for Complex Data
// ✅ Good - flexible and self-documenting
new Message(this, "Configure", new Dict
{
["Speed"] = 100.0,
["Acceleration"] = 200.0,
["Deceleration"] = 150.0
});
// ❌ Bad - fragile and unclear
new Message(this, "Configure", new object[] { 100.0, 200.0, 150.0 });
3. Check Payload Types
// ✅ Good - safe type checking
if (message.Payload is Position3D pos)
{
MoveToPosition(pos);
}
// ❌ Bad - can throw exception
var pos = (Position3D)message.Payload!;
MoveToPosition(pos);
4. Use Request-Response for Queries
// ✅ Good - clear request-response pattern
var response = new Message(message, this, "Response", data);
message.Sender.Send(response);
// ❌ Bad - separate unlinked messages
message.Sender.Send(new Message(this, "Response", data));
See Also
- Message Passing Guide - Complete guide to message patterns
- IActor Interface - Sending messages
- IState Interface - Processing messages