#pragma once #include #include #include #include #include #include #include #include #include #include "secsgem/gem/substrate_state.hpp" namespace secsgem::gem { // One entry in a substrate's per-axis transition log. Holds the // post-transition state on one axis (the other stays at NoState), // the triggering event encoded back into the SubstrateEvent or // SubstrateProcessingEvent enum (held discriminated by `is_processing`), // the location string at the moment, and a steady_clock timestamp. struct SubstrateHistoryEntry { std::chrono::steady_clock::time_point at; SubstrateState location_to{SubstrateState::NoState}; SubstrateProcessingState processing_to{SubstrateProcessingState::NoState}; std::variant event; std::string location_label; // free-form location at the time of the event }; struct Substrate { std::string substid; // SUBSTID — E90 wafer identifier std::string carrierid; // CARRIERID where this wafer originated uint8_t slot = 0; // 1-based slot in the source carrier std::string location; // free-form: equipment-defined module name std::vector history; std::unique_ptr fsm; }; class SubstrateStore { public: using LocationChangeHandler = std::function; using ProcessingChangeHandler = std::function; SubstrateStore() = default; SubstrateStore(const SubstrateStore&) = delete; SubstrateStore& operator=(const SubstrateStore&) = delete; SubstrateStore(SubstrateStore&&) = delete; SubstrateStore& operator=(SubstrateStore&&) = delete; void set_location_handler(LocationChangeHandler h) { on_loc_ = std::move(h); } void set_processing_handler(ProcessingChangeHandler h) { on_proc_ = std::move(h); } enum class CreateResult { Created, Denied_AlreadyExists }; // Cap per-substrate history retention to keep memory bounded. 0 == // unlimited. Default cap is 256 entries which comfortably covers a // typical wafer's lifecycle. void set_history_limit(std::size_t n) { history_limit_ = n; } std::size_t history_limit() const { return history_limit_; } CreateResult create(std::string substid, std::string carrierid = "", uint8_t slot = 0) { if (subs_.count(substid)) return CreateResult::Denied_AlreadyExists; auto fsm = std::make_unique(); const std::string id = substid; fsm->set_location_handler( [this, id](SubstrateState f, SubstrateState t, SubstrateEvent e) { record_history(id, t, SubstrateProcessingState::NoState, e); if (on_loc_) on_loc_(id, f, t, e); }); fsm->set_processing_handler( [this, id](SubstrateProcessingState f, SubstrateProcessingState t, SubstrateProcessingEvent e) { record_history(id, SubstrateState::NoState, t, e); if (on_proc_) on_proc_(id, f, t, e); }); subs_.emplace(substid, Substrate{substid, std::move(carrierid), slot, "", {}, std::move(fsm)}); return CreateResult::Created; } bool has(const std::string& id) const { return subs_.count(id) > 0; } const Substrate* get(const std::string& id) const { auto it = subs_.find(id); return it == subs_.end() ? nullptr : &it->second; } Substrate* get(const std::string& id) { auto it = subs_.find(id); return it == subs_.end() ? nullptr : &it->second; } bool fire_location_event(const std::string& id, SubstrateEvent e, std::string new_location = "") { auto* s = get(id); if (!s) return false; if (!new_location.empty()) s->location = std::move(new_location); return s->fsm->on_location_event(e); } bool fire_processing_event(const std::string& id, SubstrateProcessingEvent e) { auto* s = get(id); return s && s->fsm->on_processing_event(e); } bool remove(const std::string& id) { return subs_.erase(id) > 0; } std::size_t size() const { return subs_.size(); } std::vector ids() const { std::vector out; out.reserve(subs_.size()); for (const auto& kv : subs_) out.push_back(kv.first); return out; } const std::vector* history(const std::string& id) const { const auto* s = get(id); return s ? &s->history : nullptr; } private: template void record_history(const std::string& id, SubstrateState loc_to, SubstrateProcessingState proc_to, Event ev) { auto* s = get(id); if (!s) return; SubstrateHistoryEntry entry; entry.at = std::chrono::steady_clock::now(); entry.location_to = loc_to; entry.processing_to = proc_to; entry.event = ev; entry.location_label = s->location; s->history.push_back(std::move(entry)); if (history_limit_ != 0 && s->history.size() > history_limit_) { // Drop the oldest entry; vector shift is fine for the small caps // we expect (default 256, typically a few dozen per substrate). s->history.erase(s->history.begin()); } } std::map subs_; LocationChangeHandler on_loc_; ProcessingChangeHandler on_proc_; std::size_t history_limit_ = 256; }; } // namespace secsgem::gem