Files
secs-gem/include/secsgem/gem/store/substrates.hpp
T
raphael a28a8b5982 K1: SubstrateHistory ring buffer per substrate
Each Substrate now retains an append-only history of state transitions
(both location and processing axes), the triggering event captured as
a std::variant<SubstrateEvent, SubstrateProcessingEvent>, the location
label at the time, and a steady_clock timestamp.

E90 §6.6 requires the equipment to be able to report a wafer's
processing history — typically queried via S6F11 batched reports or
SVID reads.  This commit lays the runtime substrate; wire query
plumbing is the natural follow-up.

set_history_limit(n) caps per-substrate retention (default 256, 0 =
unbounded).  Oldest entries are dropped when the cap is reached;
vector-erase is fine at this scale (typical wafer lifecycle is a few
dozen transitions).

Two new test cases cover the recording invariants (every fire results
in one history entry on the right axis) and history_limit eviction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 08:43:24 +02:00

152 lines
5.6 KiB
C++

#pragma once
#include <chrono>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#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<SubstrateEvent, SubstrateProcessingEvent> 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<SubstrateHistoryEntry> history;
std::unique_ptr<SubstrateStateMachine> fsm;
};
class SubstrateStore {
public:
using LocationChangeHandler =
std::function<void(const std::string& substid, SubstrateState from,
SubstrateState to, SubstrateEvent)>;
using ProcessingChangeHandler =
std::function<void(const std::string& substid,
SubstrateProcessingState from,
SubstrateProcessingState to,
SubstrateProcessingEvent)>;
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<SubstrateStateMachine>();
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<std::string> ids() const {
std::vector<std::string> out;
out.reserve(subs_.size());
for (const auto& kv : subs_) out.push_back(kv.first);
return out;
}
const std::vector<SubstrateHistoryEntry>* history(const std::string& id) const {
const auto* s = get(id);
return s ? &s->history : nullptr;
}
private:
template <typename Event>
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<std::string, Substrate> subs_;
LocationChangeHandler on_loc_;
ProcessingChangeHandler on_proc_;
std::size_t history_limit_ = 256;
};
} // namespace secsgem::gem