본문으로 건너뛰기

IVariable

지속성, 변경 추적 및 범위 관리 기능을 갖춘 타입이 지정된 값을 저장하는 액터 변수를 위한 인터페이스입니다. 변수는 액터 상태, 구성 매개변수 및 런타임 데이터를 저장하는 주요 메커니즘입니다.

네임스페이스: ControlBee.Interfaces

상속: IActorItem, INotifyValueChanged

개요

ControlBee의 변수는 다음과 같은 여러 주요 기능을 갖춘 강력한 타입의 범위 인식 상태 관리를 제공합니다:

  • 타입 안전성 - 제네릭 Variable<T>는 컴파일 타임 타입 검사를 보장합니다
  • 범위 지정된 지속성 - Global, Local 및 Temporary 범위가 저장/로드 동작을 제어합니다
  • 변경 추적 - 값이 변경될 때 자동 알림
  • 감사 추적 - 누가 언제 값을 수정했는지 추적
  • 레시피 관리 - 다양한 제품에 대한 변수 세트 저장/로드
  • UI 통합 - 구성 인터페이스에 자동으로 표시

변수는 일반적으로 액터 클래스의 public 필드로 선언됩니다:

public class StageActor : Actor
{
// Global - 모든 레시피에서 공유, 자동 저장
public Variable<double> HomeSpeed = new(VariableScope.Global, 10.0);
public Variable<double> SafetyMargin = new(VariableScope.Global, 5.0);

// Local - 레시피별로 다른 값, 자동 저장
public Variable<double> Speed = new(VariableScope.Local, 100.0);
public Variable<Position1D> LoadPos = new(VariableScope.Local);

// Temporary - 런타임 상태, 자동 저장 안 됨
public Variable<bool> Busy = new(VariableScope.Temporary);
public Variable<int> CurrentIndex = new(VariableScope.Temporary);
}

속성

Id

int? Id { get; }

변수가 저장될 때 할당되는 이 변수의 데이터베이스 ID입니다. 아직 저장되지 않았거나 Local/Temporary 범위 변수의 경우 null입니다.

사용:

  • 변수 관리자의 내부 사용
  • 지속성 상태 추적
  • 데이터베이스 작업

ValueObject

object? ValueObject { get; set; }

타입이 지정되지 않은 객체로서의 변수의 현재 값입니다. 타입 안전 액세스를 위해서는 대신 제네릭 Variable<T>.Value 속성을 사용하세요.

사용 예시:

// 타입 안전 액세스 (권장)
Variable<double> speed = new(VariableScope.Local, 100.0);
double currentSpeed = speed.Value;
speed.Value = 150.0;

// 인터페이스를 통한 타입이 지정되지 않은 액세스 (제네릭 코드용)
IVariable variable = speed;
object? value = variable.ValueObject; // 150.0을 object로 반환
variable.ValueObject = 200.0; // 캐스팅 필요

사용 사례:

  • 제네릭 변수 처리 코드
  • 레시피 로드/저장
  • UI 데이터 바인딩
  • 리플렉션 기반 작업

OldValueObject

object? OldValueObject { get; }

마지막 변경 전의 이전 값입니다. 변경 감지, 유효성 검사 및 실행 취소 작업에 사용됩니다.

사용 예시:

public class StageActor : Actor
{
public Variable<double> Speed = new(VariableScope.Local, 100.0);

public StageActor(ActorConfig config) : base(config)
{
Speed.ValueChanged += (s, e) =>
{
double oldSpeed = (double)e.OldValue!;
double newSpeed = (double)e.NewValue!;

Console.WriteLine($"속도가 {oldSpeed}에서 {newSpeed}로 변경되었습니다");

if (newSpeed > 200)
{
// 이전 값으로 되돌리기
Speed.Value = oldSpeed;
Console.WriteLine("속도 제한 초과, 되돌림");
}
};
}
}

사용 사례:

  • 변경 유효성 검사
  • 실행 취소 기능
  • 감사 로깅
  • 변경 비교

Scope

VariableScope Scope { get; }

변수의 지속성 및 수명 주기 동작을 정의합니다:

  • VariableScope.Local - 레시피 구분 없이 데이터베이스에 저장됩니다 (모든 레시피에서 동일한 값). 제품 간에 변경되지 않는 시스템 전체 설정에 사용됩니다.
  • VariableScope.Local - 레시피별로 데이터베이스에 저장됩니다 (레시피마다 다른 값). 제품별 구성에 사용됩니다.
  • VariableScope.Temporary - 런타임 상태로 자동 저장되지 않습니다. SaveTemporaryVariables()로 수동 저장할 수 있으며, 전역적으로 저장됩니다 (레시피 구분 없음).

올바른 범위 선택:

// Global - 모든 레시피에서 공유되는 시스템 설정
public Variable<double> HomeSpeed = new(VariableScope.Local, 10.0);
public Variable<double> SafetyOffset = new(VariableScope.Local, 5.0);
public Variable<int> DebounceMs = new(VariableScope.Local, 50);

// Local - 레시피별로 변경되는 제품별 설정
public Variable<double> ProcessSpeed = new(VariableScope.Local, 100.0);
public Variable<Position1D> LoadPos = new(VariableScope.Local);
public Variable<int> GridRows = new(VariableScope.Local, 5);

// Temporary - 자주 변경되는 런타임 상태
public Variable<bool> Busy = new(VariableScope.Temporary);
public Variable<int> CurrentIndex = new(VariableScope.Temporary);
public Variable<GridContainer> WorkTray = new(VariableScope.Temporary);

참고:

ActorName

string ActorName { get; }

이 변수를 소유한 액터의 이름입니다. 다음에 사용됩니다:

  • 데이터베이스 저장 - 액터별로 변수 구성
  • 변경 기록 - 어떤 액터의 변수가 변경되었는지 추적
  • UI 구성 - 액터별로 변수 그룹화
  • 진단 - 변수 소유권 식별

예시:

var variable = actor.GetItem("Speed") as IVariable;
Console.WriteLine($"변수 소유자: {variable.ActorName}");
// 출력: 변수 소유자: Stage0

Dirty

bool Dirty { get; set; }

변수에 저장되지 않은 변경 사항이 있는지 여부를 나타냅니다. 값이 변경되면 true로 설정되고, 데이터베이스에 저장되면 지워집니다.

사용 예시:

// 저장되지 않은 변경 사항이 있는 변수 확인
foreach (var actor in actorRegistry.GetAll())
{
var items = actor.GetItems();
foreach (var (path, type) in items)
{
if (actor.GetItem(path) is IVariable variable && variable.Dirty)
{
Console.WriteLine($"{actor.Name}.{path}에 저장되지 않은 변경 사항이 있습니다");
}
}
}

// 모든 더티 변수 저장
variableManager.Save(); // 저장된 모든 변수의 Dirty 플래그 지우기

자동 추적:

  • GlobalLocal 범위 변수의 경우 값이 변경되면 Dirty가 자동으로 true로 설정됩니다
  • Temporary 범위 변수의 경우 Dirty는 자동 설정되지 않습니다 (수동 저장용)

사용 사례:

  • 저장 시기 결정
  • 저장되지 않은 변경 사항에 대한 경고
  • UI의 변경 표시기
  • 감사 추적 트리거

UserInfo

IUserInfo? UserInfo { get; set; }

이 변수를 마지막으로 수정한 사용자에 대한 정보입니다. 감사 추적 및 변경 추적에 사용됩니다.

속성:

  • UserName - 사용자의 로그인 이름
  • AuthorityLevel - 사용자의 권한 레벨
  • Timestamp - 변경이 이루어진 시간

사용 예시:

public class StageActor : Actor
{
public Variable<Position1D> LoadPos = new(VariableScope.Local);

public void TeachLoadPosition()
{
LoadPos.Value.TeachCurrent();

// UserInfo는 시스템에 의해 자동으로 설정됩니다
if (LoadPos.UserInfo != null)
{
Console.WriteLine($"위치를 티칭한 사용자: {LoadPos.UserInfo.UserName}");
Console.WriteLine($"시간: {LoadPos.UserInfo.Timestamp}");
}
}
}

// 변경 기록 조회
var changes = variableManager.ReadVariableChanges(new QueryOptions
{
Filter = "ActorName = 'Stage0' AND ItemPath = 'LoadPos'",
OrderBy = "Timestamp DESC",
Limit = 10
});

foreach (var change in changes)
{
Console.WriteLine($"{change.Timestamp}: {change.UserName}이(가) " +
$"{change.OldValue}에서 {change.NewValue}로 변경");
}

사용 사례:

  • 감사 로깅
  • 변경 책임성
  • 규정 준수 요구 사항
  • 디버깅(누가 무엇을 변경했는지)

메서드

ToJson

저장 또는 전송을 위해 변수 값을 JSON 문자열로 직렬화합니다.

string ToJson();

반환값: 현재 값의 JSON 표현입니다.

사용 예시:

// 단순 타입
Variable<double> speed = new(VariableScope.Local, 100.0);
string json = speed.ToJson();
Console.WriteLine(json); // 출력: "100.0"

// 복잡한 타입
Variable<Position1D> pos = new(VariableScope.Local);
pos.Value.Set(100, 50, 200, 100); // position, speed, accel, decel
string json = pos.ToJson();
// 출력: {"Position":100,"Speed":50,"Accel":200,"Decel":100}

// 배열
Variable<Array2D<bool>> grid = new(VariableScope.Local, new Array2D<bool>(3, 3));
grid.Value[1, 1] = true;
string json = grid.ToJson();
// 출력: [[false,false,false],[false,true,false],[false,false,false]]

사용 사례:

  • 레시피 저장 - 변수를 데이터베이스에 저장
  • 내보내기/가져오기 - 시스템 간 구성 전송
  • 백업/복원 - 변수 상태 저장
  • 네트워크 전송 - 원격 시스템으로 값 전송

FromJson

JSON 문자열에서 변수 값을 역직렬화합니다.

void FromJson(string data);

매개변수:

  • data — 역직렬화하여 변수에 할당할 JSON 문자열입니다.

사용 예시:

// 저장된 JSON에서 복원
Variable<double> speed = new(VariableScope.Local, 100.0);
speed.FromJson("150.0");
Console.WriteLine(speed.Value); // 출력: 150.0

// 복잡한 타입 로드
Variable<Position1D> pos = new(VariableScope.Local);
pos.FromJson("{\"Position\":100,\"Speed\":50,\"Accel\":200,\"Decel\":100}");
Console.WriteLine($"위치: {pos.Value.Position}"); // 출력: 위치: 100

// 배열 로드
Variable<Array2D<bool>> grid = new(VariableScope.Local, new Array2D<bool>(3, 3));
grid.FromJson("[[false,false,false],[false,true,false],[false,false,false]]");
Console.WriteLine($"중앙 셀: {grid.Value[1, 1]}"); // 출력: 중앙 셀: True

사용 사례:

  • 레시피 로드 - 데이터베이스에서 변수 복원
  • 가져오기 - 파일에서 구성 로드
  • 네트워크 수신 - 원격 시스템에서 값 수신
  • 실행 취소/다시 실행 - 이전 상태 복원

오류 처리:

try
{
variable.FromJson(jsonString);
}
catch (JsonException ex)
{
Console.WriteLine($"역직렬화 실패: {ex.Message}");
// 오류 처리 - 현재 값을 유지하거나 기본값 사용
}

변경 알림

변수는 INotifyValueChanged를 구현하여 구독자가 값 변경 시 알림을 받을 수 있도록 합니다:

public class StageActor : Actor
{
public Variable<int> RowCount = new(VariableScope.Local, 5);
public Variable<int> ColCount = new(VariableScope.Local, 8);
public Variable<GridContainer> Tray = new(VariableScope.Temporary);

public StageActor(ActorConfig config) : base(config)
{
// 변경 사항 구독
RowCount.ValueChanged += OnLayoutChanged;
ColCount.ValueChanged += OnLayoutChanged;
}

private void OnLayoutChanged(object? sender, ValueChangedArgs e)
{
// 치수 변경 시 그리드 재생성
Tray.Value = new GridContainer(RowCount.Value, ColCount.Value);
Console.WriteLine($"그리드 크기가 {RowCount.Value}x{ColCount.Value}로 변경되었습니다");
}
}

참고:

일반적인 패턴

1. 변경 시 유효성 검사

public Variable<double> Speed = new(VariableScope.Local, 100.0);

public MyActor(ActorConfig config) : base(config)
{
Speed.ValueChanged += (s, e) =>
{
double newSpeed = (double)e.NewValue!;

if (newSpeed < 0 || newSpeed > 200)
{
Speed.Value = (double)e.OldValue!; // 되돌리기
ErrorDialog.Show("속도는 0에서 200 사이여야 합니다");
}
};
}

2. 계단식 업데이트

public Variable<bool> UseVacuum = new(VariableScope.Local, true);
public Variable<double> VacuumPressure = new(VariableScope.Local, -80.0);

public MyActor(ActorConfig config) : base(config)
{
UseVacuum.ValueChanged += (s, e) =>
{
// 진공을 사용하지 않을 때 진공 압력 숨기기
((IActorItemModifier)VacuumPressure).Visible = (bool)e.NewValue!;
};
}

3. 파생 상태

public Variable<int> RowCount = new(VariableScope.Local, 5);
public Variable<int> ColCount = new(VariableScope.Local, 8);
public Variable<int> TotalCells = new(VariableScope.Local, 40);

public MyActor(ActorConfig config) : base(config)
{
void UpdateTotal(object? s, ValueChangedArgs e)
{
TotalCells.Value = RowCount.Value * ColCount.Value;
}

RowCount.ValueChanged += UpdateTotal;
ColCount.ValueChanged += UpdateTotal;
}

제네릭 Variable 클래스

실제로는 IVariable을 구현하는 제네릭 Variable<T> 클래스를 사용합니다:

// 스칼라 타입
public Variable<int> Count = new(VariableScope.Local, 0);
public Variable<double> Value = new(VariableScope.Local, 0.0);
public Variable<bool> Flag = new(VariableScope.Local, false);
public Variable<string> Name = new(VariableScope.Local, "");

// 위치 타입
public Variable<Position1D> TargetX = new(VariableScope.Local);
public Variable<Position2D> TargetXY = new(VariableScope.Local);
public Variable<Position3D> TargetXYZ = new(VariableScope.Local);

// 배열 타입
public Variable<Array1D<bool>> Flags = new(VariableScope.Local, new Array1D<bool>(10));
public Variable<Array2D<int>> Grid = new(VariableScope.Local, new Array2D<int>(5, 8));

// 사용자 정의 타입
public Variable<GridContainer> Tray = new(VariableScope.Temporary);

참고