본문으로 건너뛰기

Message

ControlBee에서 액터 간 통신에 사용되는 메시지 클래스입니다. 메시지는 불변(immutable)이며 발신자 참조, 메시지 이름 및 선택적 페이로드를 포함합니다.

네임스페이스: ControlBee.Models

개요

메시지는 액터 간의 주요 통신 메커니즘입니다. 각 메시지는 생성되면 불변이며 다음을 포함합니다:

  • Sender - 메시지를 보내는 액터에 대한 참조
  • Name - 메시지 타입의 문자열 식별자
  • Payload - 메시지가 전달하는 선택적 데이터
  • Id - 메시지 추적을 위한 고유 식별자
  • RequestId - 이 메시지를 요청과 연결하는 선택적 식별자

생성자

기본 생성자

public Message(IActor sender, string name)
public Message(IActor sender, string name, object? payload)

매개변수:

  • sender — 이 메시지를 보내는 액터
  • name — 메시지 이름/타입 식별자
  • payload — 선택적 데이터 페이로드

사용 예시:

using Dict = System.Collections.Generic.Dictionary<string, object?>;

// 간단한 메시지
var msg1 = new Message(this, "Start");

// 간단한 데이터를 포함한 메시지
var msg2 = new Message(this, "SetSpeed", 100.0);

// Dict 페이로드를 포함한 메시지
var msg3 = new Message(this, "ProcessCell", new Dict
{
["Row"] = 5,
["Col"] = 3
});

요청-응답 생성자

public Message(Guid requestId, IActor sender, string name, object? payload)
public Message(Message requestMessage, IActor sender, string name, object? payload)

매개변수:

  • requestId — 원래 요청 메시지의 GUID
  • requestMessage — 응답할 원래 요청 메시지
  • sender — 이 응답을 보내는 액터
  • name — 응답 메시지 이름
  • payload — 선택적 응답 데이터

사용 예시:

// 요청에 응답
public override bool ProcessMessage(Message message)
{
if (message.Name == "GetStatus")
{
// 요청에 연결된 응답 전송
var response = new Message(message, this, "StatusResponse", currentStatus);
message.Sender.Send(response);
return true;
}
return false;
}

속성

Sender

public IActor Sender { get; }

이 메시지를 보낸 액터입니다. 발신지를 식별하고 응답을 보내는 데 사용됩니다.

사용 예시:

public override bool ProcessMessage(Message message)
{
if (message.Name == "QueryPosition")
{
// 발신자에게 응답 전송
message.Sender.Send(new Message(this, "PositionResponse", currentPosition));
return true;
}
return false;
}

Name

public string Name { get; }

메시지 타입 식별자입니다. 다양한 메시지 타입을 처리하기 위해 switch 문에서 사용됩니다.

사용 예시:

switch (message.Name)
{
case "Start":
HandleStart();
return true;
case "Stop":
HandleStop();
return true;
default:
return false;
}

Payload

public object? Payload { get; }

메시지 데이터 페이로드입니다. 간단한 값, 객체 또는 딕셔너리 등 모든 타입이 가능합니다.

사용 예시:

// 간단한 페이로드
if (message.Name == "SetSpeed" && message.Payload is double speed)
{
Motor.Speed = speed;
return true;
}

// 객체 페이로드
if (message.Name == "SetPosition" && message.Payload is Position3D pos)
{
MoveToPosition(pos);
return true;
}

DictPayload

public Dictionary<string, object?>? DictPayload { get; }

페이로드가 Dictionary인 경우 Dictionary로 반환하고, 그렇지 않으면 null을 반환하는 편의 속성입니다. 페이로드가 Dictionary<string, object?>일 때 자동으로 설정됩니다.

사용 예시:

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; }

이 메시지의 고유 식별자입니다. 메시지가 생성될 때 자동으로 생성됩니다.

사용 사례:

  • 메시지 추적 및 로깅
  • 메시지 흐름 디버깅
  • 액터 간 메시지 상관관계
  • 성능 모니터링

사용 예시:

Logger.Info($"Received message {message.Id}: {message.Name} from {message.ActorName}");

RequestId

public Guid RequestId { get; }

이 메시지를 요청 메시지와 연결하는 식별자입니다. 응답이 아닌 경우 Guid.Empty입니다.

사용 예시:

// 특정 요청에 대한 응답인지 확인
if (message.RequestId == pendingRequestId)
{
ProcessResponse(message);
pendingRequestId = Guid.Empty;
return true;
}

ActorName

public string ActorName { get; }

Sender.Name을 반환하는 편의 속성입니다. 로깅 및 디버깅에 유용합니다.

사용 예시:

Logger.Debug($"Processing {message.Name} from {message.ActorName}");

특수 메시지

Empty Message

public static readonly Message Empty

자리 표시자로 사용되는 싱글톤 빈 메시지입니다. 액터에게 전송되어서는 안 됩니다.

TimerMessage

타이머 간격이 구성된 액터에게 주기적으로 전송되는 특수 시스템 메시지입니다.

메시지 이름: TimerMessage.MessageName

구성:

액터 생성자에서 TimerMilliseconds 속성을 설정하여 주기적인 타이머 메시지를 활성화합니다:

public FlipperActor(ActorConfig config) : base(config)
{
TimerMilliseconds = 200; // 200ms마다 TimerMessage 수신
// ... 나머지 초기화
}

사용법:

타이머 메시지는 일반적으로 스캔 작업, 주기적인 상태 확인 또는 센서 폴링에 사용됩니다:

public override bool ProcessMessage(Message message)
{
switch (message.Name)
{
case TimerMessage.MessageName:
return Scan(); // 주기적인 작업
// ... 다른 메시지들
}
return false;
}

private bool Scan()
{
// 센서 확인, 상태 업데이트 등
if (SensorChanged())
{
UpdateStatus();
return true;
}
return false;
}

참고: TimerMilliseconds가 설정되지 않았거나 0으로 설정된 경우, 타이머 메시지가 전송되지 않습니다.

메시지 패턴

1. 간단한 명령

// 발신자
NextStage.Send(new Message(this, "Start"));

2. 데이터를 포함한 명령

// 발신자
Picker.Send(new Message(this, "SetSpeed", 150.0));

// 수신자
if (message.Name == "SetSpeed" && message.Payload is double speed)
{
Motor.Speed = speed;
return true;
}

3. Dict를 사용한 복잡한 데이터

// 발신자
Head.Send(new Message(this, "Pick", new Dict
{
["X"] = 100.5,
["Y"] = 200.3,
["Z"] = 50.0,
["Speed"] = 100.0
}));

// 수신자
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. 요청-응답 패턴

// 요청
var requestId = Guid.NewGuid();
var request = new Message(requestId, this, "GetStatus");
peer.Send(request);
pendingRequestId = requestId;

// 응답 (피어 액터에서)
if (message.Name == "GetStatus")
{
var response = new Message(message, this, "StatusResponse", GetCurrentStatus());
message.Sender.Send(response);
return true;
}

// 응답 처리 (원래 액터에서)
if (message.Name == "StatusResponse" && message.RequestId == pendingRequestId)
{
HandleStatusResponse(message.Payload);
pendingRequestId = Guid.Empty;
return true;
}

모범 사례

1. 설명적인 이름 사용

new Message(this, "StartTransfer");  // ✅ 명확한 의도
new Message(this, "Msg1"); // ❌ 불명확

2. 복잡한 데이터는 Dict 사용

// ✅ 좋음 - 유연하고 자체 문서화됨
new Message(this, "Configure", new Dict
{
["Speed"] = 100.0,
["Acceleration"] = 200.0,
["Deceleration"] = 150.0
});

// ❌ 나쁨 - 취약하고 불명확
new Message(this, "Configure", new object[] { 100.0, 200.0, 150.0 });

3. 페이로드 타입 확인

// ✅ 좋음 - 안전한 타입 확인
if (message.Payload is Position3D pos)
{
MoveToPosition(pos);
}

// ❌ 나쁨 - 예외를 발생시킬 수 있음
var pos = (Position3D)message.Payload!;
MoveToPosition(pos);

4. 쿼리에는 요청-응답 사용

// ✅ 좋음 - 명확한 요청-응답 패턴
var response = new Message(message, this, "Response", data);
message.Sender.Send(response);

// ❌ 나쁨 - 연결되지 않은 별도의 메시지
message.Sender.Send(new Message(this, "Response", data));

참고