From 3e0ceaf4e569ea1f57a14de2f6135d1f1a50d080 Mon Sep 17 00:00:00 2001
From: mrDarker <mr.darker@163.com>
Date: 星期三, 27 八月 2025 13:47:31 +0800
Subject: [PATCH] Merge branch 'clh' into liuyang

---
 SourceCode/Bond/EAPSimulator/ProcessJob.cpp |  252 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 252 insertions(+), 0 deletions(-)

diff --git a/SourceCode/Bond/EAPSimulator/ProcessJob.cpp b/SourceCode/Bond/EAPSimulator/ProcessJob.cpp
new file mode 100644
index 0000000..a4935e6
--- /dev/null
+++ b/SourceCode/Bond/EAPSimulator/ProcessJob.cpp
@@ -0,0 +1,252 @@
+#include "pch.h"
+#include "ProcessJob.h"
+#include <cctype>
+
+namespace SERVO {
+    static inline std::string trimCopy(std::string s) {
+        auto notspace = [](int ch) { return !std::isspace(ch); };
+        s.erase(s.begin(), std::find_if(s.begin(), s.end(), notspace));
+        s.erase(std::find_if(s.rbegin(), s.rend(), notspace).base(), s.end());
+        return s;
+    }
+
+    CProcessJob::CProcessJob(std::string pjId)
+        : m_pjId(trimCopy(pjId))
+    {
+        clampString(m_pjId, MAX_ID_LEN);
+    }
+
+    void CProcessJob::setParentCjId(std::string cjId) {
+        m_parentCjId = trimCopy(cjId);
+        clampString(m_parentCjId, MAX_ID_LEN);
+    }
+
+    void CProcessJob::setRecipe(RecipeMethod method, std::string spec) {
+        m_recipeMethod = method;
+        m_recipeSpec = trimCopy(spec);
+        clampString(m_recipeSpec, MAX_ID_LEN);
+    }
+
+    void CProcessJob::addParam(std::string name, std::string value) {
+        name = trimCopy(name);
+        value = trimCopy(value);
+        clampString(name, MAX_PARAM_K);
+        clampString(value, MAX_PARAM_V);
+        m_params.push_back({ std::move(name), std::move(value) });
+    }
+
+    void CProcessJob::setParams(std::vector<PJParam> params) {
+        m_params.clear();
+        m_params.reserve(params.size());
+        for (auto& p : params) addParam(std::move(p.name), std::move(p.value));
+    }
+
+    void CProcessJob::addPauseEvent(uint32_t ceid) {
+        if (ceid) m_pauseEvents.push_back(ceid);
+        std::sort(m_pauseEvents.begin(), m_pauseEvents.end());
+        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
+    }
+
+    void CProcessJob::setPauseEvents(std::vector<uint32_t> ceids) {
+        m_pauseEvents = std::move(ceids);
+        std::sort(m_pauseEvents.begin(), m_pauseEvents.end());
+        m_pauseEvents.erase(std::unique(m_pauseEvents.begin(), m_pauseEvents.end()), m_pauseEvents.end());
+    }
+
+    std::vector<CProcessJob::ValidationIssue>
+        CProcessJob::validate(const IResourceView& rv) const
+    {
+        std::vector<ValidationIssue> issues;
+
+        // 让 add 同时支持 const char* 和 std::string
+        auto add = [&](uint32_t code, std::string msg) {
+            issues.push_back({ code, std::move(msg) });
+        };
+
+        // —— 基本 / 标识 ——
+        if (m_pjId.empty())            add(1001, "PJID empty");
+        if (!asciiPrintable(m_pjId))   add(1002, "PJID has non-printable chars");
+
+        if (m_parentCjId.empty())      add(1010, "Parent CJID empty");
+
+        // —— 配方(RCPSPEC / PPID)——
+        if (m_recipeSpec.empty())      add(1020, "Recipe spec (PPID) empty");
+        else if (!rv.recipeExists(m_recipeSpec)) {
+            add(1021, "PPID not found: " + m_recipeSpec);
+        }
+
+        // —— 配方方法 vs 参数 —— 1=NoTuning 禁止带参数;2=WithTuning 允许/可选
+        if (m_recipeMethod == RecipeMethod::NoTuning && !m_params.empty()) {
+            add(1022, "Params not allowed when PRRECIPEMETHOD=1 (NoTuning)");
+        }
+
+        // —— 物料选择校验 ——(二选一:Carrier+Slots 或 MIDs;两者都不填则错误)
+        const bool hasCarrierSlots = !m_carriers.empty();
+        if (hasCarrierSlots) {
+            // {L:n { CARRIERID {L:j SLOTID} }}
+            for (const auto& cs : m_carriers) {
+                if (cs.carrierId.empty()) {
+                    add(1030, "CarrierID empty");
+                    continue;
+                }
+                if (!rv.carrierPresent(cs.carrierId)) {
+                    add(1031, "Carrier not present: " + cs.carrierId);
+                }
+                if (cs.slots.empty()) {
+                    add(1032, "No slots specified for carrier: " + cs.carrierId);
+                }
+                for (auto s : cs.slots) {
+                    if (s == 0) {
+                        add(1033, "Slot 0 is invalid for carrier: " + cs.carrierId);
+                        continue;
+                    }
+                    if (!rv.slotUsable(cs.carrierId, s)) {
+                        add(1034, "Slot unusable: carrier=" + cs.carrierId + " slot=" + std::to_string(s));
+                    }
+                }
+            }
+        }
+        else {
+            add(1035, "No material selection provided (neither Carrier/Slots nor MIDs)");
+        }
+
+        // —— 暂停事件(PRPAUSEEVENTID 列表)——
+        for (auto ceid : m_pauseEvents) {
+            if (!rv.ceidDefined(ceid)) {
+                add(1050, "Pause CEID unknown: " + std::to_string(ceid));
+            }
+        }
+
+        return issues;
+    }
+
+    // —— 状态机 ——
+    // 规则可按你们协议微调
+    bool CProcessJob::queue() {
+        if (m_state != PJState::NoState) return false;
+        markQueued();
+        return true;
+    }
+
+    bool CProcessJob::enterSettingUp() {
+        if (m_state != PJState::Queued) return false;
+        m_state = PJState::SettingUp;
+        return true;
+    }
+
+    bool CProcessJob::start() {
+        if (m_state != PJState::Queued && m_state != PJState::SettingUp && m_state != PJState::Paused)
+            return false;
+        if (!m_tStart.has_value()) markStart();
+        m_state = PJState::InProcess;
+        return true;
+    }
+
+    bool CProcessJob::pause() {
+        if (m_state != PJState::InProcess) return false;
+        m_state = PJState::Paused;
+        return true;
+    }
+
+    bool CProcessJob::resume() {
+        if (m_state != PJState::Paused) return false;
+        m_state = PJState::InProcess;
+        return true;
+    }
+
+    bool CProcessJob::complete() {
+        if (m_state != PJState::InProcess && m_state != PJState::Paused) return false;
+        m_state = PJState::Completed;
+        markEnd();
+        return true;
+    }
+
+    bool CProcessJob::abort() {
+        if (m_state == PJState::Completed || m_state == PJState::Aborted || m_state == PJState::Failed)
+            return false;
+        m_state = PJState::Aborted;
+        markEnd();
+        return true;
+    }
+
+    bool CProcessJob::fail(std::string reason) {
+        m_failReason = trimCopy(reason);
+        clampString(m_failReason, 128);
+        m_state = PJState::Failed;
+        markEnd();
+        return true;
+    }
+
+    // —— 时间戳 & 工具 —— 
+    void CProcessJob::markQueued() {
+        m_state = PJState::Queued;
+        m_tQueued = std::chrono::system_clock::now();
+    }
+
+    void CProcessJob::markStart() {
+        m_tStart = std::chrono::system_clock::now();
+    }
+
+    void CProcessJob::markEnd() {
+        m_tEnd = std::chrono::system_clock::now();
+    }
+
+    void CProcessJob::clampString(std::string& s, size_t maxLen) {
+        if (s.size() > maxLen) s.resize(maxLen);
+    }
+
+    bool CProcessJob::asciiPrintable(const std::string& s) {
+        return std::all_of(s.begin(), s.end(), [](unsigned char c) {
+            return c >= 0x20 && c <= 0x7E;
+            });
+    }
+
+    void CProcessJob::setCarriers(std::vector<CarrierSlotInfo> carriers)
+    {
+        // 统一通过 addCarrier 做规范化(去空白、截断、去重、合并同 carrier)
+        m_carriers.clear();
+        m_carriers.reserve(carriers.size());
+        for (auto& cs : carriers) {
+            addCarrier(std::move(cs.carrierId), std::move(cs.slots));
+        }
+    }
+
+    void CProcessJob::addCarrier(std::string carrierId, std::vector<uint8_t> slots)
+    {
+        // 1) 规范化 carrierId:去空白 + 长度限制
+        carrierId = trimCopy(std::move(carrierId));
+        clampString(carrierId, MAX_ID_LEN);
+        if (carrierId.empty()) {
+            // 空 ID 直接忽略(也可以选择抛异常/记录日志,看你项目风格)
+            return;
+        }
+
+        // 2) 规范化 slots:去 0、排序、去重
+        //    注:SLOTID 按 1..N,0 视为非法/占位
+        slots.erase(std::remove(slots.begin(), slots.end(), 0), slots.end());
+        std::sort(slots.begin(), slots.end());
+        slots.erase(std::unique(slots.begin(), slots.end()), slots.end());
+        if (slots.empty()) {
+            // 没有有效卡位就不追加
+            return;
+        }
+
+        // 3) 如果已存在同名载具,则合并 slot 列表
+        auto it = std::find_if(m_carriers.begin(), m_carriers.end(),
+            [&](const CarrierSlotInfo& cs) { return cs.carrierId == carrierId; });
+
+        if (it != m_carriers.end()) {
+            // 合并
+            it->slots.insert(it->slots.end(), slots.begin(), slots.end());
+            std::sort(it->slots.begin(), it->slots.end());
+            it->slots.erase(std::unique(it->slots.begin(), it->slots.end()), it->slots.end());
+        }
+        else {
+            // 新增
+            CarrierSlotInfo cs;
+            cs.carrierId = std::move(carrierId);
+            cs.slots = std::move(slots);
+            m_carriers.emplace_back(std::move(cs));
+        }
+    }
+}

--
Gitblit v1.9.3