Icarus
Vehicle Simulation as a Transformable Computational Graph, built on Vulcan and Janus
Loading...
Searching...
No Matches
Registry.hpp
Go to the documentation of this file.
1#pragma once
2
10
11#include <deque>
13#include <icarus/core/Error.hpp>
18#include <regex>
19#include <string>
20#include <unordered_map>
21#include <vector>
22
23namespace icarus {
24
37template <typename Scalar> class SignalRegistry {
38 public:
39 using SignalIndex = std::size_t;
40 static constexpr SignalIndex InvalidIndex = static_cast<SignalIndex>(-1);
41
42 // =========================================================================
43 // Context Management
44 // =========================================================================
45
53 void set_current_component(const std::string &name) { current_component_ = name; }
54
58 void clear_current_component() { current_component_.clear(); }
59
63 [[nodiscard]] const std::string &current_component() const { return current_component_; }
64
65 // =========================================================================
66 // Pointer-Based Registration (Phase 1.3)
67 // =========================================================================
68
78 template <typename T>
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);
82 }
83
95 template <typename S>
96 void register_output_vec3(const std::string &name, Vec3<S> *data_ptr,
97 const std::string &unit = "", const std::string &description = "") {
98 if (data_ptr == nullptr) {
99 throw SignalError::NullPointer(name, "Vec3 output");
100 }
101 // Register three scalar signals pointing to Vec3 elements
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);
108 }
109
115 template <typename S>
116 void register_output_quat(const std::string &name, Vec4<S> *data_ptr,
117 const std::string &unit = "", const std::string &description = "") {
118 if (data_ptr == nullptr) {
119 throw SignalError::NullPointer(name, "Quat output");
120 }
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);
129 }
130
131 // =========================================================================
132 // Phase 6: State Registration (Unified Signal Model)
133 // =========================================================================
134
148 template <typename T>
149 void register_state(const std::string &name, T *value, T *derivative,
150 const std::string &unit = "", const std::string &description = "") {
151 if (value == nullptr) {
152 throw SignalError::NullPointer(name, "state value");
153 }
154 if (derivative == nullptr) {
155 throw SignalError::NullPointer(name + "_dot", "state derivative");
156 }
157
158 std::string value_name = name;
159 std::string deriv_name = name + "_dot";
160
161 // Register value signal (integrable)
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;
166
167 // Register derivative signal
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;
173
174 // Track the state pair
175 state_pairs_.push_back({value_name, deriv_name, value, derivative});
176 }
177
183 template <typename S>
184 void register_state_vec3(const std::string &name, Vec3<S> *value, Vec3<S> *derivative,
185 const std::string &unit = "", const std::string &description = "") {
186 if (value == nullptr) {
187 throw SignalError::NullPointer(name, "Vec3 state value");
188 }
189 if (derivative == nullptr) {
190 throw SignalError::NullPointer(name + "_dot", "Vec3 state derivative");
191 }
192
193 std::string deriv_unit = unit.empty() ? "" : unit + "/s";
194
195 // Register each component as a state pair
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];
200
201 // Register value (integrable)
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;
208
209 // Register derivative
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;
215
216 // Track the pair
217 state_pairs_.push_back({value_name, deriv_name, &((*value)(i)), &((*derivative)(i))});
218 }
219 }
220
226 template <typename S>
227 void register_state_quat(const std::string &name, Vec4<S> *value, Vec4<S> *derivative,
228 const std::string &unit = "", const std::string &description = "") {
229 if (value == nullptr) {
230 throw SignalError::NullPointer(name, "Quat state value");
231 }
232 if (derivative == nullptr) {
233 throw SignalError::NullPointer(name + "_dot", "Quat state derivative");
234 }
235
236 // Quaternion components: w, x, y, z (indices 0, 1, 2, 3)
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];
241
242 // Register value (integrable)
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;
249
250 // Register derivative
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;
256
257 // Track the pair
258 state_pairs_.push_back({value_name, deriv_name, &((*value)(i)), &((*derivative)(i))});
259 }
260 }
261
268 [[nodiscard]] const auto &get_state_pairs() const { return state_pairs_; }
269
275 [[nodiscard]] std::vector<const SignalDescriptor *> get_integrable_signals() const {
276 std::vector<const SignalDescriptor *> result;
277 for (const auto &desc : signals_) {
278 if (desc.is_integrable) {
279 result.push_back(&desc);
280 }
281 }
282 return result;
283 }
284
285 // =========================================================================
286 // Type-Safe Resolution (Phase 1.3)
287 // =========================================================================
288
298 template <typename T> [[nodiscard]] SignalHandle<T> resolve(const std::string &name) {
299 auto it = name_to_index_.find(name);
300 if (it == name_to_index_.end()) {
301 throw SignalNotFoundError(name);
302 }
303
304 SignalIndex index = it->second;
305 const SignalDescriptor &desc = signals_[index];
306
307 // Type check
308 constexpr SignalType expected_type = TypeTraits<T>::type_id;
309 if (desc.type != expected_type) {
310 throw TypeMismatchError(name, TypeTraits<T>::name, signal_type_name(desc.type));
311 }
312
313 return SignalHandle<T>(static_cast<T *>(desc.data_ptr), &desc);
314 }
315
319 template <typename T>
320 [[nodiscard]] SignalHandle<const T> resolve_const(const std::string &name) const {
321 auto it = name_to_index_.find(name);
322 if (it == name_to_index_.end()) {
323 throw SignalNotFoundError(name);
324 }
325
326 SignalIndex index = it->second;
327 const SignalDescriptor &desc = signals_[index];
328
329 // Type check
330 constexpr SignalType expected_type = TypeTraits<T>::type_id;
331 if (desc.type != expected_type) {
332 throw TypeMismatchError(name, TypeTraits<T>::name, signal_type_name(desc.type));
333 }
334
335 return SignalHandle<const T>(static_cast<const T *>(desc.data_ptr), &desc);
336 }
337
345 template <typename S> [[nodiscard]] Vec3Handle<S> resolve_vec3(const std::string &name) {
346 return Vec3Handle<S>{resolve<S>(name + ".x"), resolve<S>(name + ".y"),
347 resolve<S>(name + ".z")};
348 }
349
353 template <typename S> [[nodiscard]] QuatHandle<S> resolve_quat(const std::string &name) {
354 return QuatHandle<S>{resolve<S>(name + ".w"), resolve<S>(name + ".x"),
355 resolve<S>(name + ".y"), resolve<S>(name + ".z")};
356 }
357
358 // =========================================================================
359 // Legacy API (Backward Compatibility)
360 // =========================================================================
361 // @deprecated Use register_output<T>() and resolve<T>() instead.
362 // These methods are retained only for test compatibility.
363 // The legacy API uses registry-managed storage (values_ deque) rather than
364 // component-owned pointers, which is less efficient and type-unsafe.
365 // =========================================================================
366
378 if (name_to_index_.contains(descriptor.name)) {
379 const auto &existing = signals_[name_to_index_[descriptor.name]];
380 throw DuplicateSignalError(descriptor.name, existing.owner_component,
381 current_component_.empty() ? "(unknown)"
382 : current_component_);
383 }
384
385 SignalIndex index = signals_.size();
386 signals_.push_back(descriptor);
387 signals_.back().owner_component = current_component_;
388 values_.push_back(Scalar{});
389
390 // For legacy API, point data_ptr at our internal storage
391 signals_.back().data_ptr = &values_.back();
392
393 name_to_index_[descriptor.name] = index;
394 return index;
395 }
396
404 [[nodiscard]] SignalIndex Resolve(const std::string &name) const {
405 auto it = name_to_index_.find(name);
406 if (it == name_to_index_.end()) {
407 throw SignalNotFoundError(name);
408 }
409 return it->second;
410 }
411
415 [[nodiscard]] bool HasSignal(const std::string &name) const {
416 return name_to_index_.contains(name);
417 }
418
423 [[nodiscard]] const Scalar &Get(SignalIndex index) const { return values_[index]; }
424
429 void Set(SignalIndex index, const Scalar &value) { values_[index] = value; }
430
434 [[nodiscard]] const Scalar &GetByName(const std::string &name) const {
435 const SignalDescriptor &desc = signals_[Resolve(name)];
436 if (desc.data_ptr == nullptr) {
437 throw SignalError::NullPointer(name, "output");
438 }
439 return *static_cast<const Scalar *>(desc.data_ptr);
440 }
441
445 void SetByName(const std::string &name, const Scalar &value) {
446 const SignalDescriptor &desc = signals_[Resolve(name)];
447 if (desc.data_ptr == nullptr) {
448 throw SignalError::NullPointer(name, "output");
449 }
450 *static_cast<Scalar *>(desc.data_ptr) = value;
451 }
452
453 // =========================================================================
454 // Query / Introspection (Cold Path)
455 // =========================================================================
456
460 [[nodiscard]] const std::deque<SignalDescriptor> &GetDescriptors() const { return signals_; }
461
465 [[nodiscard]] std::size_t Size() const { return signals_.size(); }
466
474 [[nodiscard]] std::vector<const SignalDescriptor *> query(const std::string &pattern) const {
475 std::vector<const SignalDescriptor *> results;
476 std::regex re;
477 try {
478 re = std::regex(pattern);
479 } catch (const std::regex_error &e) {
480 throw SignalError("Invalid regex pattern '" + pattern + "': " + e.what());
481 }
482 for (const auto &desc : signals_) {
483 if (std::regex_search(desc.name, re)) {
484 results.push_back(&desc);
485 }
486 }
487 return results;
488 }
489
493 [[nodiscard]] const SignalDescriptor *get_descriptor(const std::string &name) const {
494 auto it = name_to_index_.find(name);
495 if (it == name_to_index_.end()) {
496 return nullptr;
497 }
498 return &signals_[it->second];
499 }
500
501 private:
502 // =========================================================================
503 // Implementation Helpers
504 // =========================================================================
505
506 template <typename T>
507 void register_signal_impl(const std::string &name, T *data_ptr, const std::string &unit,
508 const std::string &description, SignalLifecycle lifecycle) {
509 if (data_ptr == nullptr) {
510 throw SignalError::NullPointer(name, "output");
511 }
512
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_);
518 }
519
520 SignalDescriptor desc;
521 desc.name = name;
522 desc.unit = unit;
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;
528
529 SignalIndex index = signals_.size();
530 signals_.push_back(std::move(desc));
531 name_to_index_[name] = index;
532 }
533
534 [[nodiscard]] static const char *signal_type_name(SignalType type) {
535 switch (type) {
536 case SignalType::Double:
537 return "Double";
538 case SignalType::Int32:
539 return "Int32";
540 case SignalType::Int64:
541 return "Int64";
542 default:
543 return "Unknown";
544 }
545 }
546
547 // =========================================================================
548 // Data Members
549 // =========================================================================
550
551 std::deque<SignalDescriptor> signals_; // Deque for stable descriptor refs
552 std::deque<Scalar> values_; // Deque for stable value refs (legacy API)
553 std::unordered_map<std::string, SignalIndex> name_to_index_;
554 std::string current_component_;
555
556 // Phase 6: State pair tracking for integration
557 struct StatePair {
558 std::string value_name;
559 std::string derivative_name;
560 void *value_ptr = nullptr;
561 void *derivative_ptr = nullptr;
562 };
563 std::vector<StatePair> state_pairs_;
564
565 // =========================================================================
566 // Phase 2.4: Input, Parameter, Config Storage
567 // =========================================================================
568
570 struct InputEntry {
571 void *handle_ptr = nullptr; // Type-erased InputHandle<T>*
572 SignalDescriptor info;
573 };
574
576 struct ParamEntry {
577 Scalar *storage = nullptr;
578 Scalar initial_value{};
579 SignalDescriptor info;
580 };
581
583 struct ConfigEntry {
584 void *storage = nullptr; // Type-erased pointer
585 SignalDescriptor info;
586 };
587
588 std::unordered_map<std::string, InputEntry> inputs_;
589 std::unordered_map<std::string, ParamEntry> params_;
590 std::unordered_map<std::string, ConfigEntry> config_;
591
592 public:
593 // =========================================================================
594 // Phase 2.4: Input Registration
595 // =========================================================================
596
608 template <typename T>
609 void register_input(const std::string &name, InputHandle<T> *handle,
610 const std::string &units = "", const std::string &description = "") {
611 if (handle == nullptr) {
612 throw SignalError::NullPointer(name, "input handle");
613 }
614
615 if (inputs_.contains(name)) {
616 throw DuplicateSignalError(name, inputs_[name].info.name, current_component_);
617 }
618
619 // Set up handle metadata
620 handle->set_name(name);
621 handle->set_full_name(name); // Will be prefixed by Backplane
622 handle->set_units(units);
623 handle->set_description(description);
624
625 InputEntry entry;
626 entry.handle_ptr = handle;
627 entry.info.name = name;
628 entry.info.unit = units;
629 entry.info.description = description;
630 entry.info.kind = SignalKind::Input;
631 entry.info.semantic = typeid(T).name();
632 entry.info.owner_component = current_component_;
633
634 inputs_[name] = std::move(entry);
635
636 // Also register input in signal lookup for poke support
637 // The data_ptr points to the input's default value buffer
638 // First check for collision with existing output/state signals
639 if (name_to_index_.contains(name)) {
640 const auto &existing = signals_[name_to_index_[name]];
641 throw DuplicateSignalError(name, existing.owner_component,
642 current_component_.empty() ? "(unknown)"
643 : current_component_);
644 }
645
646 SignalDescriptor desc;
647 desc.name = name;
648 desc.unit = units;
649 desc.description = description;
650 desc.kind = SignalKind::Input;
652 desc.semantic = typeid(T).name();
653 desc.owner_component = current_component_;
654 desc.data_ptr = static_cast<void *>(handle->data_ptr());
655
656 SignalIndex index = signals_.size();
657 signals_.push_back(std::move(desc));
658 name_to_index_[name] = index;
659 }
660
661 // =========================================================================
662 // Phase 2.4: Parameter Registration (Scalar-typed, optimizable)
663 // =========================================================================
664
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) {
679 throw SignalError::NullPointer(name, "param");
680 }
681
682 if (params_.contains(name)) {
683 throw DuplicateSignalError(name, params_[name].info.name, current_component_);
684 }
685
686 *storage = initial_value;
687
688 ParamEntry entry;
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;
694 entry.info.kind = SignalKind::Parameter;
695 entry.info.semantic = "Scalar";
696 entry.info.is_optimizable = true;
697 entry.info.owner_component = current_component_;
698
699 params_[name] = std::move(entry);
700 }
701
702 // =========================================================================
703 // Phase 2.4: Config Registration (discrete, not optimizable)
704 // =========================================================================
705
709 void register_config(const std::string &name, int *storage, int initial_value,
710 const std::string &description = "") {
711 register_config_impl(name, storage, initial_value, "int", description);
712 }
713
717 void register_config(const std::string &name, bool *storage, bool initial_value,
718 const std::string &description = "") {
719 register_config_impl(name, storage, initial_value, "bool", description);
720 }
721
722 // =========================================================================
723 // Phase 2.4: Input Wiring
724 // =========================================================================
725
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 + "'");
739 }
740
741 // Verify type matches what was registered (prevents undefined behavior)
742 const std::string expected_type = typeid(T).name();
743 if (it->second.info.semantic != expected_type) {
744 throw SignalError::TypeMismatch(input_name, it->second.info.semantic, expected_type);
745 }
746
747 // Resolve the source signal
748 auto source_handle = resolve<T>(source_name);
749
750 // Validate source is not an input (inputs cannot be wiring sources)
751 if (source_handle.descriptor()->kind == SignalKind::Input) {
752 throw WiringError("Cannot wire input '" + input_name + "' to source '" + source_name +
753 "': source is an input, not an output");
754 }
755
756 // Wire the input - safe cast after type verification
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;
760 }
761
762 // =========================================================================
763 // Phase 2.4: Validation
764 // =========================================================================
765
769 [[nodiscard]] std::vector<std::string> get_unwired_inputs() const {
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);
774 }
775 }
776 return unwired;
777 }
778
783 void validate_wiring() const {
784 auto unwired = get_unwired_inputs();
785 if (!unwired.empty()) {
786 std::string msg = "Unwired inputs: ";
787 for (size_t i = 0; i < unwired.size(); ++i) {
788 if (i > 0)
789 msg += ", ";
790 msg += unwired[i];
791 }
792 throw WiringError(msg);
793 }
794 }
795
796 // =========================================================================
797 // Phase 2.4: Introspection
798 // =========================================================================
799
805 [[nodiscard]] std::vector<SignalDescriptor> get_outputs() const {
806 std::vector<SignalDescriptor> result;
807 for (const auto &desc : signals_) {
808 if (desc.kind != SignalKind::Input) {
809 result.push_back(desc);
810 }
811 }
812 return result;
813 }
814
820 [[nodiscard]] std::vector<SignalDescriptor>
821 get_outputs_for_component(const std::string &component_name) const {
822 std::vector<SignalDescriptor> result;
823 for (const auto &desc : signals_) {
824 if (desc.owner_component == component_name && desc.kind != SignalKind::Input) {
825 result.push_back(desc);
826 }
827 }
828 return result;
829 }
830
834 [[nodiscard]] std::vector<SignalDescriptor>
835 get_inputs_for_component(const std::string &component_name) const {
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);
840 }
841 }
842 return result;
843 }
844
848 [[nodiscard]] std::vector<SignalDescriptor>
849 get_params_for_component(const std::string &component_name) const {
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);
854 }
855 }
856 return result;
857 }
858
862 [[nodiscard]] std::vector<SignalDescriptor>
863 get_config_for_component(const std::string &component_name) const {
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);
868 }
869 }
870 return result;
871 }
872
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);
881 }
882 return result;
883 }
884
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);
893 }
894 return result;
895 }
896
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);
905 }
906 return result;
907 }
908
912 [[nodiscard]] bool has_input(const std::string &name) const { return inputs_.contains(name); }
913
917 [[nodiscard]] bool has_param(const std::string &name) const { return params_.contains(name); }
918
922 [[nodiscard]] bool has_config(const std::string &name) const { return config_.contains(name); }
923
924 // =========================================================================
925 // Phase 3: Signal Discovery (for external bindings)
926 // =========================================================================
927
933 [[nodiscard]] std::vector<std::string> get_all_signal_names() const {
934 std::vector<std::string> names;
935 for (const auto &desc : signals_) {
936 if (desc.kind != SignalKind::Input) {
937 names.push_back(desc.name);
938 }
939 }
940 return names;
941 }
942
946 [[nodiscard]] std::vector<std::string> get_all_input_names() const {
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);
951 }
952 return names;
953 }
954
958 [[nodiscard]] std::vector<std::string> get_all_output_names() const {
959 return get_all_signal_names(); // Outputs = signals
960 }
961
962 // =========================================================================
963 // Phase 4.0: API Additions for SignalRouter and Backplane
964 // =========================================================================
965
969 [[nodiscard]] bool HasOutput(const std::string &name) const { return HasSignal(name); }
970
974 [[nodiscard]] bool HasInput(const std::string &name) const { return has_input(name); }
975
979 [[nodiscard]] std::vector<std::string> GetAllInputPaths() const {
980 return get_all_input_names();
981 }
982
986 [[nodiscard]] std::vector<std::string> GetAllOutputPaths() const {
987 return get_all_signal_names();
988 }
989
1001 template <typename T>
1002 void wire_input_with_gain(const std::string &input_name, const std::string &source_name,
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 + "'");
1007 }
1008
1009 // Verify type matches what was registered (prevents undefined behavior)
1010 const std::string expected_type = typeid(T).name();
1011 if (it->second.info.semantic != expected_type) {
1012 throw SignalError::TypeMismatch(input_name, it->second.info.semantic, expected_type);
1013 }
1014
1015 // Resolve the source signal
1016 auto source_handle = resolve<T>(source_name);
1017
1018 // Validate source is not an input (inputs cannot be wiring sources)
1019 if (source_handle.descriptor()->kind == SignalKind::Input) {
1020 throw WiringError("Cannot wire input '" + input_name + "' to source '" + source_name +
1021 "': source is an input, not an output");
1022 }
1023
1024 // Wire the input with gain
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;
1028 }
1029
1038 void wire_input_with_gain(const std::string &input_name, const std::string &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 + "'");
1043 }
1044
1045 // Verify input is Scalar type - gain wiring only applies to scalars
1046 const std::string &semantic_type = it->second.info.semantic;
1047 if (semantic_type != typeid(Scalar).name()) {
1048 throw WiringError(
1049 "wire_input_with_gain only supports Scalar type, got: " + semantic_type +
1050 " (input: '" + input_name + "'). Use wire_input for vector types.");
1051 }
1052
1053 wire_input_with_gain<Scalar>(input_name, source_name, gain);
1054 }
1055
1056 private:
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) {
1061 throw SignalError::NullPointer(name, "config");
1062 }
1063
1064 if (config_.contains(name)) {
1065 throw DuplicateSignalError(name, config_[name].info.name, current_component_);
1066 }
1067
1068 *storage = initial_value;
1069
1070 ConfigEntry entry;
1071 entry.storage = storage;
1072 entry.info.name = name;
1073 entry.info.description = description;
1074 entry.info.kind = SignalKind::Config;
1075 entry.info.semantic = type_name;
1076 entry.info.is_optimizable = false;
1077 entry.info.owner_component = current_component_;
1078
1079 config_[name] = std::move(entry);
1080 }
1081};
1082
1083} // namespace icarus
Core type definitions, concepts, and configuration for Icarus.
Consolidated error handling for Icarus.
Type-safe SignalHandle for zero-overhead hot path access.
Input signal port handle for components.
Signal types and descriptors for the Icarus Signal Backplane.
Vector and matrix handles for structured signal access.
Definition Error.hpp:448
Handle to an input signal port.
Definition InputHandle.hpp:35
T * data_ptr()
Get pointer to the default value buffer (for registry).
Definition InputHandle.hpp:81
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
Definition Error.hpp:456
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
Definition Error.hpp:440
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