Skip to main content

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 message
  • name — The message name/type identifier
  • payload — 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 message
  • requestMessage — The original request message to respond to
  • sender — The actor sending this response
  • name — The response message name
  • payload — 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