using Aurora.Api; using Aurora.Util.HislogPlus; using ILSOS_CCW_H52A.Data; using ILSOS_CCW_H52A.Sequences; using System; using System.Collections.Generic; namespace ILSOS_CCW_H52A.Data { // 물리 지그 번호 (0-based enum, 1-based 변환: (eJigNo)(jigNo - 1)) public enum eJigNo { Jig1, Jig2, Jig3, Jig4, Jig5, Jig6, Jig7, Jig8, Jig9, Jig10, Jig11, Jig12, Jig13, Jig14, Jig15, Jig16, } /// /// Dictionary 상속으로 물리 지그 번호 직접 접근과 /// 파이프라인 단계 접근을 동시에 지원하는 셀 스케줄러. /// /// - 물리 지그 접근: scheduler[eJigNo.Jig5] /// - 파이프라인 접근: scheduler.Prefetch1, scheduler.Prefetch2 /// /// 3단계 파이프라인: Prefetch2 → Prefetch1 → ReportTarget /// Prefetch2 : 셀 정보 수신 완료, 스캐너 리스트 업로드 전 /// Prefetch1 : 업로드 완료, 실행 중 — EncoderMonitor 감시 + 완료 체크 대상 /// ReportTarget: 완료, 보고 대기 /// /// Shift()는 Prefetch2→Prefetch1만 전진. GC 할당 없음. /// Prefetch1 ← Prefetch2 ← this[nextJig] /// public class CellInfoScheduler : Dictionary { private readonly Logger _log = new Logger("CellInfoScheduler"); public JigInfo Prefetch2 { get; private set; } // 전전 셀: 셀 정보 수신, 업로드 전 // volatile: Shift()에서 Prefetch1 = Prefetch2 대입이 release fence 역할. // EncoderMonitor 스레드가 Prefetch1을 읽을 때 acquire fence로 JigNo 등 모든 필드 가시성 보장. private volatile JigInfo _prefetch1; public JigInfo Prefetch1 { get => _prefetch1; private set => _prefetch1 = value; } /// 현재 보고 대상 JigInfo (CheckScannerListComplete에서 지정, 보고 완료 후 null) public JigInfo ReportTarget { get; set; } // 재시작 보존용 독립 슬롯 — Reset()에서 건드리지 않음 // 재시작 시, List를 최초 리스트 2번을 생성하는 로직 때문에.. 필요함 private readonly JigInfo _pendingSlot = new JigInfo(); // 용접 직후 (용접 완, 보고 미완) private readonly JigInfo _pendingPrefetch1 = new JigInfo(); // 정지 직전 Prefetch1 (업로드 완료, 용접 미완) private readonly JigInfo _pendingPrefetch2 = new JigInfo(); // 정지 직전 Prefetch2 (업로드 완료, 용접 미완) // 재시작 전 미완료 보고 슬롯 (유효 시 non-null) public JigInfo PendingComplete => HasPendingComplete ? _pendingSlot : null; // 재시작 보존 보고가 있는지 public bool HasPendingComplete => _pendingSlot.IsCanExist && !_pendingSlot.ReportComplete; // 현재 활성 보고 슬롯 — 재시작 pending 우선 public JigInfo ActiveReportSlot => HasPendingComplete ? _pendingSlot : ReportTarget; // 정지 전 미용접 슬롯이 보존돼 있는지 (재시작 후 복원 가능 여부) public bool HasPendingPrefetch => !string.IsNullOrEmpty(_pendingPrefetch1.CellId) || !string.IsNullOrEmpty(_pendingPrefetch2.CellId); // 보고가 필요한 상태인지 (정상 + 재시작 모두 포함) >> 용접 완, 보고 미완 public bool HasReport => HasPendingComplete || (ReportTarget != null && !ReportTarget.ReportComplete); public CellInfoScheduler() { foreach (eJigNo no in Enum.GetValues(typeof(eJigNo))) Add(no, new JigInfo()); } // 1-based jigNo (_WeldingJigNo 등) → 0-based eJigNo 변환 의의 인덱서 public JigInfo this[int jigNo] { get => base[(eJigNo)(jigNo - 1)]; } // Reset() 전에 호출 — 미완료 보고 및 미용접 슬롯을 보존. public void SavePendingComplete() { // 용접 완료됐으나 보고 전 정지된 경우 (_pendingSlot) if (ReportTarget != null && ReportTarget.IsCanExist && ReportTarget.WeldComplete && !ReportTarget.ReportComplete) _pendingSlot.CopyFrom(ReportTarget); // 업로드 완료됐으나 로터리 정지로 용접 자체가 안 된 경우 (_pendingPrefetch1/2) // (연속셀이 들어왔을때, 중간에 멈춘 경우) if (Prefetch1 != null && Prefetch1.IsCanExist && !string.IsNullOrEmpty(Prefetch1.CellId) && !Prefetch1.WeldComplete) _pendingPrefetch1.CopyFrom(Prefetch1); else _pendingPrefetch1.Clear(); if (Prefetch2 != null && Prefetch2.IsCanExist && !string.IsNullOrEmpty(Prefetch2.CellId) && !Prefetch2.WeldComplete) _pendingPrefetch2.CopyFrom(Prefetch2); else _pendingPrefetch2.Clear(); } /// /// 재시작 시 해당 JigNo의 보존된 셀 정보 복원 시도. /// 성공 시 해당 슬롯을 즉시 클리어하여 한 번만 사용되도록 보장. /// canExist=false 지그가 들어오면 슬롯을 소비(폐기)하여 이후 오염 방지. /// /// 복원 성공 시 true (ReceivedCellInfoQueue TryDequeue 불필요) public bool TryRestoreCell(int jigNo, out string lotId, out string cellId) { if (_pendingPrefetch1.JigNo == jigNo && !string.IsNullOrEmpty(_pendingPrefetch1.CellId)) { lotId = _pendingPrefetch1.LotId; cellId = _pendingPrefetch1.CellId; _pendingPrefetch1.Clear(); return true; } if (_pendingPrefetch2.JigNo == jigNo && !string.IsNullOrEmpty(_pendingPrefetch2.CellId)) { lotId = _pendingPrefetch2.LotId; cellId = _pendingPrefetch2.CellId; _pendingPrefetch2.Clear(); return true; } lotId = null; cellId = null; return false; } // 보고 완료 처리 — ReportTarget 마킹 및 _pendingSlot 클리어. // GetReportCellResult_SeqResult 성공 시 호출. public void CompleteReport() { _log.I($"Report >> CellId({ReportTarget.CellId}), JigNo({ReportTarget.JigNo})"); ReportTarget?.SetReportComplete(true); ReportTarget = null; _pendingSlot.Clear(); } /// /// Prefetch2→Prefetch1 한 칸 전진. GC 할당 없음. /// 새 Prefetch2 슬롯은 Clear 후 반환 — 호출자가 셀 정보를 채워야 한다. /// /// 새 Prefetch2로 지정할 물리 지그 (eJigNo) public void Shift(eJigNo nextJig) { Prefetch1 = Prefetch2; Prefetch2 = base[nextJig]; Prefetch2.Clear(); } // AutoRun에서 사용되는 자료형으로 추가 (편의 인덱서 사용) public void Shift(int nextJig) { Prefetch1 = Prefetch2; Prefetch2 = this[nextJig]; Prefetch2.Clear(); } /// /// 전체 초기화 (AutoRun 시작 시 호출). /// SavePendingComplete() 호출 후 이 메서드를 호출할 것. /// _pendingSlot / _pendingPrefetch1 / _pendingPrefetch2 는 재시작 복원을 위해 건드리지 않음. /// public void Reset() { foreach (var info in Values) info.Clear(); Prefetch2 = null; Prefetch1 = null; ReportTarget = null; // _pendingSlot, _pendingPrefetch1/2: SavePendingComplete()가 복사해 둔 데이터를 유지 } /// /// 스케줄러 자체는 Clear 할 수 없도록 함 /// public new void Clear() { Reset(); } } }