434 lines
15 KiB
C#
434 lines
15 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Project.StateMachine
|
|
{
|
|
public partial class StateMachine : IDisposable
|
|
{
|
|
public DateTime UpdateTime;
|
|
public TimeSpan RunSpeed = new TimeSpan(0);
|
|
private Boolean bLoop = true;
|
|
private DateTime StepStartTime;
|
|
private byte _messageOption;
|
|
|
|
private System.Threading.Thread worker;
|
|
private Boolean firstRun = false;
|
|
|
|
public Boolean WaitFirstRun = false;
|
|
|
|
|
|
|
|
public StateMachine()
|
|
{
|
|
WaitFirstRun = false;
|
|
UpdateTime = DateTime.Now;
|
|
_messageOption = 0xFF; //모든메세지가 오도록 한다.
|
|
worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
|
|
{
|
|
IsBackground = false
|
|
};
|
|
StepStartTime = DateTime.Parse("1982-11-23");
|
|
}
|
|
|
|
~StateMachine()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
|
|
#region "Dispose"
|
|
|
|
private bool disposed = false;
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
// Check to see if Dispose has already been called.
|
|
if (!this.disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
// Dispose managed resources.
|
|
}
|
|
|
|
Stop();
|
|
// unmanaged resources here.
|
|
// If disposing is false,
|
|
// only the following code is executed.
|
|
disposed = true;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void Stop()
|
|
{
|
|
if (bLoop != false) bLoop = false;
|
|
//if (worker.IsAlive)
|
|
// if (worker.Join(1000) == false)
|
|
// worker.Abort();
|
|
}
|
|
public void Start()
|
|
{
|
|
if (worker.ThreadState == System.Threading.ThreadState.Stopped)
|
|
{
|
|
worker = new System.Threading.Thread(new System.Threading.ThreadStart(Loop))
|
|
{
|
|
IsBackground = false
|
|
};
|
|
}
|
|
bLoop = true;
|
|
worker.Start();
|
|
}
|
|
|
|
private Boolean _isthreadrun;
|
|
public Boolean IsThreadRun { get { return _isthreadrun; } }
|
|
public int LoopCount { get { return _loopCount; } }
|
|
public DateTime LastLoopTime { get { return _lastLoopTime; } }
|
|
|
|
private int _loopCount = 0;
|
|
private DateTime _lastLoopTime = DateTime.Now;
|
|
|
|
void Loop()
|
|
{
|
|
_isthreadrun = true;
|
|
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", "Start");
|
|
|
|
try
|
|
{
|
|
while (bLoop)
|
|
{
|
|
_loopCount++;
|
|
_lastLoopTime = DateTime.Now;
|
|
|
|
// 루프 동작 확인용 로그 (10초마다)
|
|
if (_loopCount % 10000 == 0)
|
|
{
|
|
RaiseMessage("SM-LOOP", $"Loop alive - Count:{_loopCount}, bLoop:{bLoop}, Step:{Step}");
|
|
}
|
|
|
|
RunSpeed = DateTime.Now - UpdateTime; //이전업데이트에서 현재 시간의 오차 한바퀴 시간이 표시됨
|
|
UpdateTime = DateTime.Now;
|
|
|
|
//항상 작동하는 경우
|
|
try
|
|
{
|
|
var spsHandler = SPS;
|
|
if (spsHandler != null)
|
|
{
|
|
var spsStartTime = DateTime.Now;
|
|
var spsTask = System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
try
|
|
{
|
|
spsHandler(this, new EventArgs());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
|
|
}
|
|
});
|
|
|
|
// 타임아웃 1초
|
|
if (!spsTask.Wait(1000))
|
|
{
|
|
var elapsed = (DateTime.Now - spsStartTime).TotalSeconds;
|
|
RaiseMessage("SM-TIMEOUT", $"SPS 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseMessage("SM-ERROR-SPS", $"SPS Exception: {ex.Message}\n{ex.StackTrace}");
|
|
}
|
|
|
|
//작동스텝이 변경되었다면 그것을 알림 처리한다.
|
|
if (GetNewStep != Step)
|
|
{
|
|
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
|
RaiseMessage("SM-STEP", string.Format("Step Changed {0} >> {1}", Step, GetNewStep));
|
|
|
|
StepApply();
|
|
firstRun = true;
|
|
StepStartTime = DateTime.Now;
|
|
}
|
|
else
|
|
{
|
|
//팝업메세지의 종료를 기다리는 식에서 문제가 생긴다
|
|
firstRun = WaitFirstRun;
|
|
}
|
|
|
|
|
|
//동작중에 발생하는 이벤트
|
|
var runningHandler = Running;
|
|
if (runningHandler != null)
|
|
{
|
|
try
|
|
{
|
|
var runningStartTime = DateTime.Now;
|
|
var runningTask = System.Threading.Tasks.Task.Run(() =>
|
|
{
|
|
|
|
//try
|
|
//{
|
|
runningHandler(this, new RunningEventArgs(Step, firstRun, StepRunTime));
|
|
//}
|
|
//catch (Exception ex)
|
|
//{
|
|
// RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
|
//}
|
|
|
|
|
|
});
|
|
|
|
// 타임아웃 2초
|
|
if (!runningTask.Wait(2000))
|
|
{
|
|
var elapsed = (DateTime.Now - runningStartTime).TotalSeconds;
|
|
RaiseMessage("SM-TIMEOUT", $"Running 이벤트 타임아웃 ({elapsed:F2}초 초과) - Step:{Step}, FirstRun:{firstRun}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseMessage("SM-ERROR-RUNNING", $"Running Exception: {ex.Message}\n{ex.StackTrace}");
|
|
}
|
|
}
|
|
System.Threading.Thread.Sleep(1);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RaiseMessage("SM-FATAL", $"Loop Fatal Exception: {ex.Message}\n{ex.StackTrace}");
|
|
}
|
|
finally
|
|
{
|
|
_isthreadrun = false;
|
|
if (GetMsgOpt(EMsgOpt.NORMAL)) RaiseMessage("SM", $"Stop - LoopCount:{_loopCount}");
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// 상태머신의 작동상태
|
|
/// </summary>
|
|
public eSMStep Step { get { return _step; } }
|
|
|
|
/// <summary>
|
|
/// 상태머신값을 숫자로 반환 합니다. (20이하는 시스템상태이고 그 이상은 사용자 상태)
|
|
/// </summary>
|
|
public byte StepValue
|
|
{
|
|
get
|
|
{
|
|
return (byte)this._step;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 진행 스텝을 강제로 변경합니다.
|
|
/// 일반적인 상태변화는 setNewStep 을 사용하세요.
|
|
/// </summary>
|
|
/// <param name="step"></param>
|
|
public void SetCurrentStep(ERunStep step)
|
|
{
|
|
RaiseMessage("SM-STEP", string.Format("강제 스텝 변경 {0}->{1}", _runstepo, step));
|
|
_runstepo = step;
|
|
_runstepn = step;
|
|
}
|
|
|
|
public void SetNewStep(eSMStep newstep_, bool force = false)
|
|
{
|
|
if (Step != newstep_)
|
|
{
|
|
if (force)
|
|
{
|
|
RaiseMessage("SM-STEP", string.Format("강제스텝전환 {0} -> {1}", Step, _newstep));
|
|
OldStep = _step;
|
|
_step = newstep_;
|
|
_newstep = newstep_;
|
|
|
|
// 비동기로 이벤트 발생 (블로킹 방지)
|
|
var handler = StepChanged;
|
|
if (handler != null)
|
|
{
|
|
var args = new StepChangeEventArgs(OldStep, newstep_);
|
|
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
|
{
|
|
try { handler(this, args); }
|
|
catch { /* 이벤트 핸들러 예외 무시 */ }
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_newstep != newstep_)
|
|
{
|
|
//바뀌도록 예약을 한다.
|
|
_newstep = newstep_;
|
|
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
|
RaiseMessage("SM-STEP", string.Format("Step Reserve {0} -> {1}", Step, _newstep));
|
|
}
|
|
else
|
|
{
|
|
//예약은 되어있는데 아직 바뀐것은 아니다.
|
|
if (GetMsgOpt(EMsgOpt.STEPCHANGE))
|
|
RaiseMessage("SM-STEP", string.Format("Step Already Reserve {0} -> {1}", Step, _newstep));
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#region "runstep"
|
|
private int _runStepSeq = 0;
|
|
public int RunStepSeq { get { return _runStepSeq; } }
|
|
|
|
public Stack<ERunStep> BackupRunStep = new Stack<ERunStep>();
|
|
|
|
private DateTime runStepStartTime = DateTime.Parse("1982-11-23");
|
|
private ERunStep _runstepn = ERunStep.READY;
|
|
private ERunStep _runstepo = ERunStep.READY;
|
|
public ERunStep RunStep { get { return _runstepo; } }
|
|
public ERunStep RunStepNew { get { return _runstepn; } }
|
|
public void SetNewRunStep(ERunStep newStep)
|
|
{
|
|
if (_runstepn != newStep)
|
|
{
|
|
// Pub.log.Add(string.Format("set new run step {0}->{1}", _runstepn, newStep));
|
|
_runstepn = newStep;
|
|
}
|
|
}
|
|
public void ApplyRunStep()
|
|
{
|
|
if (_runstepn != _runstepo)
|
|
{
|
|
//Pub.log.Add(string.Format("apply new run step {0}->{1}", _runstepo, _runstepn));
|
|
_runStepSeq = 0;
|
|
_runstepo = _runstepn;
|
|
UpdateRunStepSeq();// runStepStartTime = DateTime.Now;
|
|
}
|
|
}
|
|
public TimeSpan GetRunSteptime { get { return DateTime.Now - runStepStartTime; } }
|
|
public void SetStepSeq(byte value)
|
|
{
|
|
_runStepSeq = value;
|
|
if (GetMsgOpt(EMsgOpt.NORMAL))
|
|
{
|
|
if (_runStepSeq != value) //변화가잇는겨웅에만 처리 220628
|
|
RaiseMessage("STEPSEQ", string.Format("Step Sequence Jump {0} to {1}", _runStepSeq, value));
|
|
}
|
|
|
|
}
|
|
public void UpdateRunStepStartTime()
|
|
{
|
|
runStepStartTime = DateTime.Now;
|
|
// Pub.log.Add("Update RunStep Start Time");
|
|
}
|
|
public void UpdateRunStepSeq(int incvalue = 1)
|
|
{
|
|
_runStepSeq += incvalue;
|
|
UpdateRunStepStartTime();
|
|
// Pub.log.Add(string.Format("스텝({0}) 시퀀스증가 신규값={1}", runStep, _runStepSeq));
|
|
}
|
|
public void ResetRunStepSeq()
|
|
{
|
|
_runStepSeq = 1;
|
|
UpdateRunStepStartTime();
|
|
}
|
|
|
|
/// <summary>
|
|
/// runstep 시퀀스값을 1로 설정하고 시작시간도 업데이트 합니다
|
|
/// 기본 스텝상태를 READY로 변경 합니다
|
|
/// </summary>
|
|
public void ClearRunStep()
|
|
{
|
|
_runStepSeq = 1;
|
|
runStepStartTime = DateTime.Now;
|
|
_runstepn = ERunStep.READY;
|
|
_runstepo = ERunStep.READY;
|
|
}
|
|
#endregion
|
|
|
|
|
|
public eSMStep GetNewStep
|
|
{
|
|
get
|
|
{
|
|
return _newstep;
|
|
}
|
|
}
|
|
private eSMStep _newstep = eSMStep.NOTSET;
|
|
public eSMStep OldStep = eSMStep.NOTSET; //171214
|
|
private eSMStep _step;
|
|
|
|
/// <summary>
|
|
/// newstep 의 값을 step 에 적용합니다.
|
|
/// </summary>
|
|
private void StepApply()
|
|
{
|
|
var ostep = _step;
|
|
OldStep = _step; _step = _newstep;
|
|
|
|
// 비동기로 이벤트 발생 (블로킹 방지)
|
|
var handler = StepChanged;
|
|
if (handler != null)
|
|
{
|
|
var args = new StepChangeEventArgs(ostep, _step);
|
|
System.Threading.ThreadPool.QueueUserWorkItem(_ =>
|
|
{
|
|
try { handler(this, args); }
|
|
catch { /* 이벤트 핸들러 예외 무시 */ }
|
|
});
|
|
}
|
|
} //171214
|
|
|
|
/// <summary>
|
|
/// 메세지 출력옵션을 변경 합니다.
|
|
/// </summary>
|
|
/// <param name="opt"></param>
|
|
/// <param name="value"></param>
|
|
public void SetMsgOpt(EMsgOpt opt, Boolean value)
|
|
{
|
|
byte pos = (byte)opt;
|
|
if (value)
|
|
_messageOption = (byte)(_messageOption | (1 << pos));
|
|
else
|
|
_messageOption = (byte)(_messageOption & ~(1 << pos));
|
|
}
|
|
public void SetMegOptOn() { _messageOption = 0xFF; }
|
|
public void SetMsgOptOff() { _messageOption = 0; }
|
|
public Boolean GetMsgOpt(EMsgOpt opt)
|
|
{
|
|
byte pos = (byte)opt;
|
|
return (_messageOption & (1 << pos)) > 0;
|
|
}
|
|
public TimeSpan StepRunTime
|
|
{
|
|
get
|
|
{
|
|
if (IsThreadRun == false || bLoop == false || StepStartTime.Year == 1982) return new TimeSpan(0);
|
|
else return DateTime.Now - StepStartTime;
|
|
}
|
|
}
|
|
|
|
public Boolean bPause = false;
|
|
|
|
/// <summary>
|
|
/// 상태머신이 실제 동작중인지 확인합니다.
|
|
/// 검사상태 : Step = Run, IsThreadRun, bPause = false
|
|
/// </summary>
|
|
public Boolean IsRun
|
|
{
|
|
get
|
|
{
|
|
if (Step == eSMStep.RUN && _isthreadrun && bPause == false) return true;
|
|
else return false;
|
|
}
|
|
}
|
|
}
|
|
}
|