20#include <unordered_map>
79 void register_output(
const std::string &name, T *data_ptr,
const std::string &unit =
"",
80 const std::string &description =
"") {
81 register_signal_impl<T>(name, data_ptr, unit, description, SignalLifecycle::Dynamic);
97 const std::string &unit =
"",
const std::string &description =
"") {
98 if (data_ptr ==
nullptr) {
102 register_signal_impl<S>(name +
".x", &((*data_ptr)(0)), unit, description +
" (x)",
103 SignalLifecycle::Dynamic);
104 register_signal_impl<S>(name +
".y", &((*data_ptr)(1)), unit, description +
" (y)",
105 SignalLifecycle::Dynamic);
106 register_signal_impl<S>(name +
".z", &((*data_ptr)(2)), unit, description +
" (z)",
107 SignalLifecycle::Dynamic);
115 template <
typename S>
117 const std::string &unit =
"",
const std::string &description =
"") {
118 if (data_ptr ==
nullptr) {
121 register_signal_impl<S>(name +
".w", &((*data_ptr)(0)), unit, description +
" (w)",
122 SignalLifecycle::Dynamic);
123 register_signal_impl<S>(name +
".x", &((*data_ptr)(1)), unit, description +
" (x)",
124 SignalLifecycle::Dynamic);
125 register_signal_impl<S>(name +
".y", &((*data_ptr)(2)), unit, description +
" (y)",
126 SignalLifecycle::Dynamic);
127 register_signal_impl<S>(name +
".z", &((*data_ptr)(3)), unit, description +
" (z)",
128 SignalLifecycle::Dynamic);
148 template <
typename T>
150 const std::string &unit =
"",
const std::string &description =
"") {
151 if (value ==
nullptr) {
154 if (derivative ==
nullptr) {
158 std::string value_name = name;
159 std::string deriv_name = name +
"_dot";
162 register_signal_impl<T>(value_name, value, unit, description, SignalLifecycle::Dynamic);
163 auto &value_desc = signals_[name_to_index_[value_name]];
164 value_desc.is_integrable =
true;
165 value_desc.derivative_signal = deriv_name;
168 std::string deriv_unit = unit.empty() ?
"" : unit +
"/s";
169 register_signal_impl<T>(deriv_name, derivative, deriv_unit, description +
" derivative",
170 SignalLifecycle::Dynamic);
171 auto &deriv_desc = signals_[name_to_index_[deriv_name]];
172 deriv_desc.integrated_signal = value_name;
175 state_pairs_.push_back({value_name, deriv_name, value, derivative});
183 template <
typename S>
185 const std::string &unit =
"",
const std::string &description =
"") {
186 if (value ==
nullptr) {
189 if (derivative ==
nullptr) {
193 std::string deriv_unit = unit.empty() ?
"" : unit +
"/s";
196 const char *suffixes[] = {
"x",
"y",
"z"};
197 for (
int i = 0; i < 3; ++i) {
198 std::string value_name = name +
"." + suffixes[i];
199 std::string deriv_name = name +
"_dot." + suffixes[i];
202 register_signal_impl<S>(value_name, &((*value)(i)), unit,
203 description +
" (" + suffixes[i] +
")",
204 SignalLifecycle::Dynamic);
205 auto &value_desc = signals_[name_to_index_[value_name]];
206 value_desc.is_integrable =
true;
207 value_desc.derivative_signal = deriv_name;
210 register_signal_impl<S>(deriv_name, &((*derivative)(i)), deriv_unit,
211 description +
" derivative (" + suffixes[i] +
")",
212 SignalLifecycle::Dynamic);
213 auto &deriv_desc = signals_[name_to_index_[deriv_name]];
214 deriv_desc.integrated_signal = value_name;
217 state_pairs_.push_back({value_name, deriv_name, &((*value)(i)), &((*derivative)(i))});
226 template <
typename S>
228 const std::string &unit =
"",
const std::string &description =
"") {
229 if (value ==
nullptr) {
232 if (derivative ==
nullptr) {
237 const char *suffixes[] = {
"w",
"x",
"y",
"z"};
238 for (
int i = 0; i < 4; ++i) {
239 std::string value_name = name +
"." + suffixes[i];
240 std::string deriv_name = name +
"_dot." + suffixes[i];
243 register_signal_impl<S>(value_name, &((*value)(i)), unit,
244 description +
" (" + suffixes[i] +
")",
245 SignalLifecycle::Dynamic);
246 auto &value_desc = signals_[name_to_index_[value_name]];
247 value_desc.is_integrable =
true;
248 value_desc.derivative_signal = deriv_name;
251 register_signal_impl<S>(deriv_name, &((*derivative)(i)),
"",
252 description +
" derivative (" + suffixes[i] +
")",
253 SignalLifecycle::Dynamic);
254 auto &deriv_desc = signals_[name_to_index_[deriv_name]];
255 deriv_desc.integrated_signal = value_name;
258 state_pairs_.push_back({value_name, deriv_name, &((*value)(i)), &((*derivative)(i))});
276 std::vector<const SignalDescriptor *> result;
277 for (
const auto &desc : signals_) {
278 if (desc.is_integrable) {
279 result.push_back(&desc);
299 auto it = name_to_index_.find(name);
300 if (it == name_to_index_.end()) {
309 if (desc.
type != expected_type) {
319 template <
typename T>
321 auto it = name_to_index_.find(name);
322 if (it == name_to_index_.end()) {
331 if (desc.
type != expected_type) {
378 if (name_to_index_.contains(descriptor.
name)) {
379 const auto &existing = signals_[name_to_index_[descriptor.
name]];
381 current_component_.empty() ?
"(unknown)"
382 : current_component_);
386 signals_.push_back(descriptor);
387 signals_.back().owner_component = current_component_;
388 values_.push_back(Scalar{});
391 signals_.back().data_ptr = &values_.back();
393 name_to_index_[descriptor.
name] = index;
405 auto it = name_to_index_.find(name);
406 if (it == name_to_index_.end()) {
415 [[nodiscard]]
bool HasSignal(
const std::string &name)
const {
416 return name_to_index_.contains(name);
423 [[nodiscard]]
const Scalar &
Get(
SignalIndex index)
const {
return values_[index]; }
434 [[nodiscard]]
const Scalar &
GetByName(
const std::string &name)
const {
439 return *
static_cast<const Scalar *
>(desc.
data_ptr);
445 void SetByName(
const std::string &name,
const Scalar &value) {
450 *
static_cast<Scalar *
>(desc.
data_ptr) = value;
460 [[nodiscard]]
const std::deque<SignalDescriptor> &
GetDescriptors()
const {
return signals_; }
465 [[nodiscard]] std::size_t
Size()
const {
return signals_.size(); }
474 [[nodiscard]] std::vector<const SignalDescriptor *>
query(
const std::string &pattern)
const {
475 std::vector<const SignalDescriptor *> results;
478 re = std::regex(pattern);
479 }
catch (
const std::regex_error &e) {
480 throw SignalError(
"Invalid regex pattern '" + pattern +
"': " + e.what());
482 for (
const auto &desc : signals_) {
483 if (std::regex_search(desc.name, re)) {
484 results.push_back(&desc);
494 auto it = name_to_index_.find(name);
495 if (it == name_to_index_.end()) {
498 return &signals_[it->second];
506 template <
typename T>
507 void register_signal_impl(
const std::string &name, T *data_ptr,
const std::string &unit,
509 if (data_ptr ==
nullptr) {
513 if (name_to_index_.contains(name)) {
514 const auto &existing = signals_[name_to_index_[name]];
515 throw DuplicateSignalError(name, existing.owner_component,
516 current_component_.empty() ?
"(unknown)"
517 : current_component_);
520 SignalDescriptor desc;
523 desc.type = TypeTraits<T>::type_id;
524 desc.lifecycle = lifecycle;
525 desc.description = description;
526 desc.owner_component = current_component_;
527 desc.data_ptr = data_ptr;
530 signals_.push_back(std::move(desc));
531 name_to_index_[name] = index;
534 [[nodiscard]]
static const char *signal_type_name(
SignalType type) {
536 case SignalType::Double:
538 case SignalType::Int32:
540 case SignalType::Int64:
551 std::deque<SignalDescriptor> signals_;
552 std::deque<Scalar> values_;
553 std::unordered_map<std::string, SignalIndex> name_to_index_;
554 std::string current_component_;
558 std::string value_name;
559 std::string derivative_name;
560 void *value_ptr =
nullptr;
561 void *derivative_ptr =
nullptr;
563 std::vector<StatePair> state_pairs_;
571 void *handle_ptr =
nullptr;
572 SignalDescriptor info;
577 Scalar *storage =
nullptr;
578 Scalar initial_value{};
579 SignalDescriptor info;
584 void *storage =
nullptr;
585 SignalDescriptor info;
588 std::unordered_map<std::string, InputEntry> inputs_;
589 std::unordered_map<std::string, ParamEntry> params_;
590 std::unordered_map<std::string, ConfigEntry> config_;
608 template <
typename T>
610 const std::string &units =
"",
const std::string &description =
"") {
611 if (handle ==
nullptr) {
615 if (inputs_.contains(name)) {
620 handle->set_name(name);
621 handle->set_full_name(name);
622 handle->set_units(units);
623 handle->set_description(description);
626 entry.handle_ptr = handle;
627 entry.info.name = name;
628 entry.info.unit = units;
629 entry.info.description = description;
631 entry.info.semantic =
typeid(T).name();
632 entry.info.owner_component = current_component_;
634 inputs_[name] = std::move(entry);
639 if (name_to_index_.contains(name)) {
640 const auto &existing = signals_[name_to_index_[name]];
642 current_component_.empty() ?
"(unknown)"
643 : current_component_);
657 signals_.push_back(std::move(desc));
658 name_to_index_[name] = index;
676 void register_param(
const std::string &name, Scalar *storage, Scalar initial_value,
677 const std::string &units =
"",
const std::string &description =
"") {
678 if (storage ==
nullptr) {
682 if (params_.contains(name)) {
686 *storage = initial_value;
689 entry.storage = storage;
690 entry.initial_value = initial_value;
691 entry.info.name = name;
692 entry.info.unit = units;
693 entry.info.description = description;
695 entry.info.semantic =
"Scalar";
696 entry.info.is_optimizable =
true;
697 entry.info.owner_component = current_component_;
699 params_[name] = std::move(entry);
710 const std::string &description =
"") {
711 register_config_impl(name, storage, initial_value,
"int", description);
718 const std::string &description =
"") {
719 register_config_impl(name, storage, initial_value,
"bool", description);
734 template <
typename T>
735 void wire_input(
const std::string &input_name,
const std::string &source_name) {
736 auto it = inputs_.find(input_name);
737 if (it == inputs_.end()) {
738 throw WiringError(
"Input not found: '" + input_name +
"'");
742 const std::string expected_type =
typeid(T).name();
743 if (it->second.info.semantic != expected_type) {
752 throw WiringError(
"Cannot wire input '" + input_name +
"' to source '" + source_name +
753 "': source is an input, not an output");
757 auto *handle =
static_cast<InputHandle<T> *
>(it->second.handle_ptr);
758 handle->wire(source_handle.ptr(), source_name);
759 it->second.info.wired_to = source_name;
770 std::vector<std::string> unwired;
771 for (
const auto &[name, entry] : inputs_) {
772 if (entry.info.wired_to.empty()) {
773 unwired.push_back(entry.info.name);
785 if (!unwired.empty()) {
786 std::string msg =
"Unwired inputs: ";
787 for (
size_t i = 0; i < unwired.size(); ++i) {
806 std::vector<SignalDescriptor> result;
807 for (
const auto &desc : signals_) {
809 result.push_back(desc);
820 [[nodiscard]] std::vector<SignalDescriptor>
822 std::vector<SignalDescriptor> result;
823 for (
const auto &desc : signals_) {
825 result.push_back(desc);
834 [[nodiscard]] std::vector<SignalDescriptor>
836 std::vector<SignalDescriptor> result;
837 for (
const auto &[name, entry] : inputs_) {
838 if (entry.info.owner_component == component_name) {
839 result.push_back(entry.info);
848 [[nodiscard]] std::vector<SignalDescriptor>
850 std::vector<SignalDescriptor> result;
851 for (
const auto &[name, entry] : params_) {
852 if (entry.info.owner_component == component_name) {
853 result.push_back(entry.info);
862 [[nodiscard]] std::vector<SignalDescriptor>
864 std::vector<SignalDescriptor> result;
865 for (
const auto &[name, entry] : config_) {
866 if (entry.info.owner_component == component_name) {
867 result.push_back(entry.info);
876 [[nodiscard]] std::vector<SignalDescriptor>
get_inputs()
const {
877 std::vector<SignalDescriptor> result;
878 result.reserve(inputs_.size());
879 for (
const auto &[name, entry] : inputs_) {
880 result.push_back(entry.info);
888 [[nodiscard]] std::vector<SignalDescriptor>
get_params()
const {
889 std::vector<SignalDescriptor> result;
890 result.reserve(params_.size());
891 for (
const auto &[name, entry] : params_) {
892 result.push_back(entry.info);
900 [[nodiscard]] std::vector<SignalDescriptor>
get_config()
const {
901 std::vector<SignalDescriptor> result;
902 result.reserve(config_.size());
903 for (
const auto &[name, entry] : config_) {
904 result.push_back(entry.info);
912 [[nodiscard]]
bool has_input(
const std::string &name)
const {
return inputs_.contains(name); }
917 [[nodiscard]]
bool has_param(
const std::string &name)
const {
return params_.contains(name); }
922 [[nodiscard]]
bool has_config(
const std::string &name)
const {
return config_.contains(name); }
934 std::vector<std::string> names;
935 for (
const auto &desc : signals_) {
937 names.push_back(desc.name);
947 std::vector<std::string> names;
948 names.reserve(inputs_.size());
949 for (
const auto &[name, entry] : inputs_) {
950 names.push_back(entry.info.name);
1001 template <
typename T>
1003 double gain = 1.0) {
1004 auto it = inputs_.find(input_name);
1005 if (it == inputs_.end()) {
1006 throw WiringError(
"Input not found: '" + input_name +
"'");
1010 const std::string expected_type =
typeid(T).name();
1011 if (it->second.info.semantic != expected_type) {
1016 auto source_handle =
resolve<T>(source_name);
1020 throw WiringError(
"Cannot wire input '" + input_name +
"' to source '" + source_name +
1021 "': source is an input, not an output");
1025 auto *handle =
static_cast<InputHandle<T> *
>(it->second.handle_ptr);
1026 handle->wire_with_gain(source_handle.ptr(), source_name, gain);
1027 it->second.info.wired_to = source_name;
1039 double gain = 1.0) {
1040 auto it = inputs_.find(input_name);
1041 if (it == inputs_.end()) {
1042 throw WiringError(
"Input not found: '" + input_name +
"'");
1046 const std::string &semantic_type = it->second.info.semantic;
1047 if (semantic_type !=
typeid(Scalar).name()) {
1049 "wire_input_with_gain only supports Scalar type, got: " + semantic_type +
1050 " (input: '" + input_name +
"'). Use wire_input for vector types.");
1057 template <
typename T>
1058 void register_config_impl(
const std::string &name, T *storage, T initial_value,
1059 const std::string &type_name,
const std::string &description) {
1060 if (storage ==
nullptr) {
1064 if (config_.contains(name)) {
1065 throw DuplicateSignalError(name, config_[name].info.name, current_component_);
1068 *storage = initial_value;
1071 entry.storage = storage;
1072 entry.info.name = name;
1073 entry.info.description = description;
1075 entry.info.semantic = type_name;
1076 entry.info.is_optimizable =
false;
1077 entry.info.owner_component = current_component_;
1079 config_[name] = std::move(entry);
Core type definitions, concepts, and configuration for Icarus.
Consolidated error handling for Icarus.
Type-safe SignalHandle for zero-overhead hot path access.
Signal types and descriptors for the Icarus Signal Backplane.
Vector and matrix handles for structured signal access.
Signal-related errors (registration, resolution, wiring).
Definition Error.hpp:94
static SignalError NullPointer(const std::string &name, const std::string &context="")
Definition Error.hpp:126
static SignalError TypeMismatch(const std::string &name, const std::string &expected, const std::string &actual)
Definition Error.hpp:119
Type-safe handle for accessing a signal value.
Definition Handle.hpp:41
Central registry for all simulation signals.
Definition Registry.hpp:37
const Scalar & Get(SignalIndex index) const
Get signal value by index (hot path, legacy).
Definition Registry.hpp:423
QuatHandle< S > resolve_quat(const std::string &name)
Resolve a quaternion signal as a QuatHandle.
Definition Registry.hpp:353
std::vector< SignalDescriptor > get_config_for_component(const std::string &component_name) const
Get config for a specific component.
Definition Registry.hpp:863
std::vector< SignalDescriptor > get_outputs_for_component(const std::string &component_name) const
Get output signals for a specific component.
Definition Registry.hpp:821
void register_output_vec3(const std::string &name, Vec3< S > *data_ptr, const std::string &unit="", const std::string &description="")
Register a Vec3 output signal as three scalar components.
Definition Registry.hpp:96
std::vector< std::string > get_all_signal_names() const
Get all output signal names.
Definition Registry.hpp:933
std::vector< SignalDescriptor > get_params() const
Get all parameter info.
Definition Registry.hpp:888
SignalHandle< const T > resolve_const(const std::string &name) const
Resolve a signal as const (for read-only access).
Definition Registry.hpp:320
void register_state_vec3(const std::string &name, Vec3< S > *value, Vec3< S > *derivative, const std::string &unit="", const std::string &description="")
Register a Vec3 state with its derivative.
Definition Registry.hpp:184
void wire_input_with_gain(const std::string &input_name, const std::string &source_name, double gain=1.0)
Wire an input to a source signal with gain factor.
Definition Registry.hpp:1002
const auto & get_state_pairs() const
Get all integrable state pairs.
Definition Registry.hpp:268
const Scalar & GetByName(const std::string &name) const
Get signal value by name (slow path, for debugging).
Definition Registry.hpp:434
void register_input(const std::string &name, InputHandle< T > *handle, const std::string &units="", const std::string &description="")
Register an input port.
Definition Registry.hpp:609
void clear_current_component()
Clear the current component context.
Definition Registry.hpp:58
std::vector< SignalDescriptor > get_inputs() const
Get all input info.
Definition Registry.hpp:876
std::vector< const SignalDescriptor * > query(const std::string &pattern) const
Query signals matching a pattern.
Definition Registry.hpp:474
bool HasSignal(const std::string &name) const
Check if a signal exists.
Definition Registry.hpp:415
bool has_input(const std::string &name) const
Check if an input exists.
Definition Registry.hpp:912
std::vector< std::string > get_all_input_names() const
Get all input signal names.
Definition Registry.hpp:946
void wire_input(const std::string &input_name, const std::string &source_name)
Wire an input to a source signal.
Definition Registry.hpp:735
void register_config(const std::string &name, int *storage, int initial_value, const std::string &description="")
Register an int config value.
Definition Registry.hpp:709
std::vector< std::string > get_all_output_names() const
Get all output signal names.
Definition Registry.hpp:958
std::size_t Size() const
Get number of registered signals.
Definition Registry.hpp:465
std::vector< SignalDescriptor > get_outputs() const
Get all output signal descriptors.
Definition Registry.hpp:805
SignalIndex RegisterSignal(const SignalDescriptor &descriptor)
Register a new signal output (legacy API).
Definition Registry.hpp:377
std::vector< const SignalDescriptor * > get_integrable_signals() const
Get all integrable signal descriptors.
Definition Registry.hpp:275
static constexpr SignalIndex InvalidIndex
Definition Registry.hpp:40
const std::string & current_component() const
Get the current component name.
Definition Registry.hpp:63
std::vector< std::string > GetAllOutputPaths() const
Get all declared output paths (for SignalRouter validation).
Definition Registry.hpp:986
void Set(SignalIndex index, const Scalar &value)
Set signal value by index (hot path, legacy).
Definition Registry.hpp:429
bool HasOutput(const std::string &name) const
Check if an output signal exists (PascalCase alias).
Definition Registry.hpp:969
std::vector< SignalDescriptor > get_config() const
Get all config info.
Definition Registry.hpp:900
bool has_param(const std::string &name) const
Check if a parameter exists.
Definition Registry.hpp:917
void set_current_component(const std::string &name)
Set the current component context for registration.
Definition Registry.hpp:53
bool HasInput(const std::string &name) const
Check if an input port exists (PascalCase alias).
Definition Registry.hpp:974
std::vector< SignalDescriptor > get_params_for_component(const std::string &component_name) const
Get parameters for a specific component.
Definition Registry.hpp:849
bool has_config(const std::string &name) const
Check if a config exists.
Definition Registry.hpp:922
Vec3Handle< S > resolve_vec3(const std::string &name)
Resolve a Vec3 signal as a Vec3Handle.
Definition Registry.hpp:345
void validate_wiring() const
Validate all inputs are wired.
Definition Registry.hpp:783
void SetByName(const std::string &name, const Scalar &value)
Set signal value by name (slow path, for initialization).
Definition Registry.hpp:445
SignalHandle< T > resolve(const std::string &name)
Resolve a signal by name and return a type-safe handle.
Definition Registry.hpp:298
const SignalDescriptor * get_descriptor(const std::string &name) const
Get descriptor by name.
Definition Registry.hpp:493
void register_output(const std::string &name, T *data_ptr, const std::string &unit="", const std::string &description="")
Register an output signal with pointer binding.
Definition Registry.hpp:79
std::vector< SignalDescriptor > get_inputs_for_component(const std::string &component_name) const
Get inputs for a specific component.
Definition Registry.hpp:835
const std::deque< SignalDescriptor > & GetDescriptors() const
Get all signal descriptors.
Definition Registry.hpp:460
std::vector< std::string > GetAllInputPaths() const
Get all declared input paths (for SignalRouter validation).
Definition Registry.hpp:979
void register_output_quat(const std::string &name, Vec4< S > *data_ptr, const std::string &unit="", const std::string &description="")
Register a Vec4/Quaternion output signal as four scalar components.
Definition Registry.hpp:116
void register_state_quat(const std::string &name, Vec4< S > *value, Vec4< S > *derivative, const std::string &unit="", const std::string &description="")
Register a quaternion state with its derivative.
Definition Registry.hpp:227
void register_state(const std::string &name, T *value, T *derivative, const std::string &unit="", const std::string &description="")
Register a scalar state with its derivative.
Definition Registry.hpp:149
void register_config(const std::string &name, bool *storage, bool initial_value, const std::string &description="")
Register a bool config value.
Definition Registry.hpp:717
void wire_input_with_gain(const std::string &input_name, const std::string &source_name, double gain=1.0)
Wire an input with gain (type-erased version).
Definition Registry.hpp:1038
SignalIndex Resolve(const std::string &name) const
Resolve a signal by name (legacy API).
Definition Registry.hpp:404
std::vector< std::string > get_unwired_inputs() const
Get list of unwired input names.
Definition Registry.hpp:769
void register_param(const std::string &name, Scalar *storage, Scalar initial_value, const std::string &units="", const std::string &description="")
Register a parameter.
Definition Registry.hpp:676
std::size_t SignalIndex
Definition Registry.hpp:39
Wiring and signal routing errors.
Definition Error.hpp:274
Definition AggregationTypes.hpp:13
vulcan::io::SignalType SignalType
Signal data type (re-exported from Vulcan for consistency).
Definition Signal.hpp:27
vulcan::io::SignalLifecycle SignalLifecycle
Signal lifecycle (re-exported from Vulcan for consistency).
Definition Signal.hpp:30
@ Input
Input port (wired to an output).
Definition Signal.hpp:78
@ Parameter
Scalar-typed parameter (optimizable).
Definition Signal.hpp:79
@ Config
Discrete config (int/bool/enum, not optimizable).
Definition Signal.hpp:80
Handle for accessing a quaternion signal as four scalar components.
Definition VecHandle.hpp:74
Descriptor for a signal on the backplane.
Definition Signal.hpp:136
std::string semantic
Optional semantic hint: "boolean", "enum".
Definition Signal.hpp:142
std::string description
Human-readable description.
Definition Signal.hpp:145
SignalKind kind
Signal classification.
Definition Signal.hpp:150
SignalType type
Data type (Double, Int32, Int64).
Definition Signal.hpp:139
std::string unit
Physical unit: "N", "m/s", "rad".
Definition Signal.hpp:141
std::string owner_component
Component that registered this signal.
Definition Signal.hpp:146
void * data_ptr
Pointer to storage (for pointer-bind pattern).
Definition Signal.hpp:147
std::string name
Full path: "Falcon9.Propulsion.Thrust".
Definition Signal.hpp:138
Primary template for TypeTraits (generates compile error if not specialized).
Definition Signal.hpp:40
Handle for accessing a Vec3 signal as three scalar components.
Definition VecHandle.hpp:39