E1: SubstrateStateMachine + SubstrateStore (E90 §6)
Per-substrate dual FSM with two orthogonal axes:
Location (STS):
AtSource -> AtWork (Acquire) -> AtDestination (Release)
AtWork -> AtSource (Return; processing aborted before completion)
Processing:
NeedsProcessing -> InProcess (Start) -> Processed (End)
InProcess -> {Aborted, Stopped, Rejected, Lost} terminal
NeedsProcessing -> {Skipped, Lost} terminal
Wire-byte values pinned via static_assert to E90-0716 §10.3.
SubstrateStore mirrors the CarrierStore pattern: non-movable, per-row
SubstrateStateMachine heap-allocated with handlers dispatching through
the store's location/processing callbacks; fire_location_event accepts
an optional new_location string so the application can carry
equipment-specific module names alongside the FSM state.
Joins EquipmentDataModel alongside carriers / load_ports. 9 test
cases cover initial state, full location lifecycle, all five
processing exits, and store-level dual-axis observer firing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "secsgem/gem/substrate_state.hpp"
|
||||
|
||||
namespace secsgem::gem {
|
||||
|
||||
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::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 };
|
||||
|
||||
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) {
|
||||
if (on_loc_) on_loc_(id, f, t, e);
|
||||
});
|
||||
fsm->set_processing_handler(
|
||||
[this, id](SubstrateProcessingState f, SubstrateProcessingState t,
|
||||
SubstrateProcessingEvent 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;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<std::string, Substrate> subs_;
|
||||
LocationChangeHandler on_loc_;
|
||||
ProcessingChangeHandler on_proc_;
|
||||
};
|
||||
|
||||
} // namespace secsgem::gem
|
||||
Reference in New Issue
Block a user