$darkmode
stack.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
24 #pragma once
25 
26 #include <map> // for map<>
27 #include <memory> // for shared_ptr<>
28 #include <set> // for set<>
29 #include <string> // for string
30 #include <utility> // for move
31 #include <vector> // for vector<>
32 
33 #include <boost/filesystem/path.hpp> // for path
34 #include <boost/optional.hpp> // for optional<>
35 
36 #include <cloe/component.hpp> // for ComponentFactory
37 #include <cloe/controller.hpp> // for ControllerFactory
38 #include <cloe/core.hpp> // for Conf, Confable, Json
39 #include <cloe/simulator.hpp> // for SimulatorFactory
40 #include <cloe/trigger.hpp> // for Source
41 #include <cloe/utility/command.hpp> // for Command
42 #include <fable/schema/custom.hpp> // for CustomDeserializer
43 #include <fable/schema/factory.hpp> // for Factory
44 
45 #include "plugin.hpp" // for Plugin
46 
47 #ifndef CLOE_STACK_VERSION
48 #define CLOE_STACK_VERSION "4.1"
49 #endif
50 
51 #ifndef CLOE_STACK_SUPPORTED_VERSIONS
52 #define CLOE_STACK_SUPPORTED_VERSIONS {"4", "4.0", "4.1"}
53 #endif
54 
55 #ifndef CLOE_XDG_SUFFIX
56 #define CLOE_XDG_SUFFIX "cloe"
57 #endif
58 
59 #ifndef CLOE_CONFIG_HOME
60 #define CLOE_CONFIG_HOME "${XDG_CONFIG_HOME-${HOME}/.config}/" CLOE_XDG_SUFFIX
61 #endif
62 
63 #ifndef CLOE_DATA_HOME
64 #define CLOE_DATA_HOME "${XDG_DATA_HOME-${HOME}/.local/share}/" CLOE_XDG_SUFFIX
65 #endif
66 
67 #define CLOE_SIMULATION_UUID_VAR "CLOE_SIMULATION_UUID"
68 
69 namespace cloe {
70 
77 class PersistentConfable : public Confable {
78  public:
79  const Conf& conf() const { return conf_; }
80 
81  void from_conf(const Conf& c) {
83  conf_ = c;
84  }
85 
86  protected:
87  Conf conf_;
88 };
89 
90 inline auto id_prototype(std::string&& desc = "") {
91  return schema::make_prototype<std::string>(std::move(desc)).c_identifier();
92 }
93 
94 inline auto id_path_prototype(std::string&& desc = "") {
95  return schema::make_prototype<std::string>(std::move(desc))
96  .pattern("^([a-zA-Z_][a-zA-Z0-9_]*/?)+$");
97 }
98 
99 // --------------------------------------------------------------------------------------------- //
100 
105 using IncludeConf = boost::filesystem::path;
106 using IncludeSchema = decltype(schema::make_schema(static_cast<IncludeConf*>(nullptr), ""));
108 
109 // --------------------------------------------------------------------------------------------- //
110 
160 struct LoggingConf : public Confable {
161  std::string name;
162  boost::optional<std::string> pattern;
163  boost::optional<LogLevel> level;
164 
165  public: // Special
166  void apply() const;
167 
168  public: // Confable Overrides
169  CONFABLE_SCHEMA(LoggingConf) {
170  using namespace schema; // NOLINT(build/namespaces)
171  return Schema{
172  {"name", make_schema(&name, "name of the logger to configure").require()},
173  {"pattern", make_schema(&pattern, "pattern of the logger")},
174  {"level", make_schema(&level, "level of the logger")},
175  };
176  }
177 
178  void validate(const Conf& c) const override {
179  const auto& s = this->schema();
180  s.validate(c);
181  if (!c.has("pattern") && !c.has("level")) {
182  throw SchemaError(c, s.json_schema(),
183  "require at least one of 'pattern' or 'level' properties");
184  }
185  }
186 };
187 
188 // --------------------------------------------------------------------------------------------- //
189 
190 struct ServerConf : public Confable {
191  bool listen{true};
192  std::string listen_address{"127.0.0.1"};
193  uint16_t listen_port{8080};
194  uint16_t listen_threads{10};
195  std::string api_prefix{"/api"};
196  std::string static_prefix{""};
197 
198  public: // Confable Overrides
199  CONFABLE_SCHEMA(ServerConf) {
200  using namespace schema; // NOLINT(build/namespaces)
201  return Schema{
202  {"listen", make_schema(&listen, "whether web server is enabled")},
203  {"listen_address", make_schema(&listen_address, "address web server should listen at")},
204  {"listen_port", make_schema(&listen_port, "port web server should listen at")},
205  {"listen_threads", make_schema(&listen_threads, "threads web server should use")},
206  {"static_prefix", make_schema(&static_prefix, "endpoint prefix for static resources")},
207  {"api_prefix", make_schema(&api_prefix, "endpoint prefix for API resources")},
208  };
209  }
210 };
211 
212 // --------------------------------------------------------------------------------------------- //
213 
220  boost::filesystem::path plugin_path{};
221 
223  boost::optional<std::string> plugin_name{};
224 
226  boost::optional<std::string> plugin_prefix{};
227 
229  boost::optional<bool> ignore_missing{};
230 
236  boost::optional<bool> ignore_failure{};
237 
244  boost::optional<bool> allow_clobber{};
245 
246  public: // Constructors
247  PluginConf() = default;
248  explicit PluginConf(const std::string& p) : plugin_path(p) {}
249 
250  public: // Special
258  std::string canonical() const;
259 
260  public: // Confable Overrides
261  CONFABLE_SCHEMA(PluginConf) {
262  // clang-format off
263  using namespace schema; // NOLINT(build/namespaces)
264  auto proto = String(nullptr, "").c_identifier();
265  return Struct{
266  {"path", make_schema(&plugin_path, "absolute or relative path to plugin").require().not_empty().normalize(true)},
267  {"name", make_schema(&plugin_name, proto, "alternative name plugin is available by")},
268  {"prefix", make_schema(&plugin_prefix, proto, "prefix the plugin name with this")},
269  {"ignore_missing", make_schema(&ignore_missing, "ignore not-exist errors")},
270  {"ignore_failure", make_schema(&ignore_failure, "ignore plugin loading errors")},
271  {"allow_clobber", make_schema(&allow_clobber, "replace same-named plugins")},
272  };
273  // clang-format on
274  }
275 };
276 
277 using PluginsSchema = schema_type<std::vector<PluginConf>>::type;
278 
279 // --------------------------------------------------------------------------------------------- //
280 
287 enum class WatchdogMode {
288  Off,
289  Log,
290  Abort,
291  Kill,
292 };
293 
294 // clang-format off
296  {WatchdogMode::Off, "off"},
297  {WatchdogMode::Log, "log"},
298  {WatchdogMode::Abort, "abort"},
299  {WatchdogMode::Kill, "kill"},
300 }))
301 // clang-format on
302 
303 struct EngineConf : public Confable {
304  // Parsing:
305  std::vector<std::string> ignore_sections{};
306 
307  // Security:
308  bool security_enable_hooks{true};
309  bool security_enable_commands{false};
310  bool security_enable_includes{true};
311  size_t security_max_include_depth{64};
312 
313  // Plugins:
314  std::vector<std::string> plugin_path{};
315  bool plugins_ignore_missing{false};
316  bool plugins_ignore_failure{false};
317  bool plugins_allow_clobber{true};
318 
319  // Hooks:
320  std::vector<Command> hooks_pre_connect{};
321  std::vector<Command> hooks_post_disconnect{};
322 
323  // Triggers:
324  bool triggers_ignore_source{false};
325 
326  // Output:
327  boost::optional<boost::filesystem::path> registry_path{CLOE_DATA_HOME "/registry"};
328  boost::optional<boost::filesystem::path> output_path{"${CLOE_SIMULATION_UUID}"};
329  boost::optional<boost::filesystem::path> output_file_config{"config.json"};
330  boost::optional<boost::filesystem::path> output_file_result{"result.json"};
331  boost::optional<boost::filesystem::path> output_file_triggers{"triggers.json"};
332  boost::optional<boost::filesystem::path> output_file_data_stream;
333  bool output_clobber_files{true};
334 
340  std::chrono::milliseconds polling_interval{100};
341 
347  WatchdogMode watchdog_mode{WatchdogMode::Off};
348 
358  std::chrono::milliseconds watchdog_default_timeout{90'000};
359 
387  std::map<std::string, boost::optional<std::chrono::milliseconds>> watchdog_state_timeouts{
388  {"CONNECT", std::chrono::milliseconds{300'000}},
389  {"ABORT", std::chrono::milliseconds{90'000}},
390  {"STOP", std::chrono::milliseconds{300'000}},
391  {"DISCONNECT", std::chrono::milliseconds{600'000}},
392  };
393 
400  bool keep_alive{false};
401 
402  public: // Confable Overrides
403  CONFABLE_SCHEMA(EngineConf) {
404  // clang-format off
405  using namespace schema; // NOLINT(build/namespaces)
406  auto dir_proto = []() { return make_prototype<boost::filesystem::path>().not_file(); };
407  auto file_proto = []() { return make_prototype<boost::filesystem::path>().not_dir().resolve(false); };
408  return Struct{
409  {"ignore", make_schema(&ignore_sections, "JSON pointers to sections that should be ignored").extend(true)},
410  {"security", Struct{
411  {"enable_hooks_section", make_schema(&security_enable_hooks, "whether to enable engine hooks")},
412  {"enable_command_action", make_schema(&security_enable_commands, "whether to enable the command action")},
413  {"enable_include_section", make_schema(&security_enable_includes, "whether to allow config files to include other files")},
414  {"max_include_depth", make_schema(&security_max_include_depth, "how many recursive includes are allowed")},
415  }},
416  {"hooks", Struct{
417  {"pre_connect", make_schema(&hooks_pre_connect, "pre-connect hooks to execute").extend(true)},
418  {"post_disconnect", make_schema(&hooks_post_disconnect, "post-disconnect hooks to execute").extend(true)},
419  }},
420  {"plugin_path", make_schema(&plugin_path, "list of directories to scan for plugins").extend(false)},
421  {"plugins", Struct{
422  {"ignore_missing", make_schema(&plugins_ignore_missing, "ignore not-exist errors")},
423  {"ignore_failure", make_schema(&plugins_ignore_failure, "ignore plugin loading errors")},
424  {"allow_clobber", make_schema(&plugins_allow_clobber, "replace same-named plugins")},
425  }},
426  {"registry_path", make_schema(&registry_path, dir_proto(), "cloe registry directory")},
427  {"output", Struct{
428  {"path", make_schema(&output_path, dir_proto().resolve(false), "directory to dump output files in, relative to registry path")},
429  {"clobber", make_schema(&output_clobber_files, "whether to clobber existing files or not")},
430  {"files", Struct{
431  {"config", make_schema(&output_file_config, file_proto(), "file to store config in")},
432  {"result", make_schema(&output_file_result, file_proto(), "file to store simulation result in")},
433  {"triggers", make_schema(&output_file_triggers, file_proto(), "file to store triggers in")},
434  {"api_recording", make_schema(&output_file_data_stream, file_proto(), "file to store api data stream")},
435  }},
436  }},
437  {"triggers", Struct{
438  {"ignore_source", make_schema(&triggers_ignore_source, "ignore trigger source when reading in triggers")},
439  }},
440  {"polling_interval", make_schema(&polling_interval, "milliseconds to sleep when polling for next state")},
441  {"watchdog", Struct{
442  {"mode", make_schema(&watchdog_mode, "modus operandi of watchdog [one of: off, log, abort, kill]")},
443  {"default_timeout", make_schema(&watchdog_default_timeout, "default timeout if not overridden, 0 for no timeout")},
444  {"state_timeouts", make_schema(&watchdog_state_timeouts, "timeout specific to a given state, 0 for no timeout").unique_properties(false)},
445  }},
446  {"keep_alive", make_schema(&keep_alive, "keep simulation alive after termination")},
447  };
448  // clang-format on
449  }
450 };
451 
452 using EngineSchema = schema_type<EngineConf>::type;
453 
454 // --------------------------------------------------------------------------------------------- //
455 
466 struct DefaultConf : public Confable {
467  boost::optional<std::string> name;
468  boost::optional<std::string> binding;
469  Conf args;
470 
471  public: // Confable Overrides
472  CONFABLE_SCHEMA(DefaultConf) {
473  using namespace schema; // NOLINT(build/namespaces)
474  return Struct{
475  {"binding", make_schema(&binding, "name of binding")},
476  {"name", make_schema(&name, id_prototype(), "globally unique identifier for component")},
477  {"args", make_schema(&args, "defaults to set for binding/name combination").require()},
478  };
479  }
480 };
481 
482 // --------------------------------------------------------------------------------------------- //
483 
484 template <typename C, typename F>
485 class FactoryPlugin : public fable::schema::FactoryPointerless<C> {
486  public:
487  FactoryPlugin() {
488  this->set_factory_key("binding");
489  this->set_args_subset(false);
490  }
491  virtual ~FactoryPlugin() = default;
492 
493  void add_plugin(const std::string& name, std::shared_ptr<Plugin> p) {
494  this->set_factory(name, p->make<F>()->schema(), [p, name](const Conf& c) {
495  C tmp{name, p->make<F>()};
496  tmp.from_conf(c);
497  return tmp;
498  });
499  }
500 };
501 
502 // --------------------------------------------------------------------------------------------- //
503 
507 struct SimulatorConf : public Confable {
508  const std::string binding;
509  boost::optional<std::string> name;
510  std::shared_ptr<SimulatorFactory> factory;
511  Conf args;
512 
513  public: // Constructors
514  SimulatorConf(const std::string& b, std::shared_ptr<SimulatorFactory> f)
515  : binding(b), factory(std::move(f)) {}
516 
517  public: // Confable Overrides
518  CONFABLE_SCHEMA(SimulatorConf) {
519  using namespace schema; // NOLINT(build/namespaces)
520  return Struct{
521  {"binding", make_const_str(binding, "name of simulator binding").require()},
522  {"name", make_schema(&name, id_prototype(), "identifier override for binding")},
523  {"args", make_schema(&args, factory->schema(), "factory-specific arguments")},
524  };
525  }
526 };
527 
528 class SimulatorSchema : public FactoryPlugin<SimulatorConf, SimulatorFactory> {
529  public:
530  SimulatorSchema();
531  virtual ~SimulatorSchema() = default;
532 };
533 
534 // --------------------------------------------------------------------------------------------- //
535 
539 struct ControllerConf : public Confable {
540  const std::string binding;
541  boost::optional<std::string> name;
542  std::string vehicle;
543  std::shared_ptr<ControllerFactory> factory;
544  Conf args;
545 
546  public: // Constructors
547  ControllerConf(const std::string& b, std::shared_ptr<ControllerFactory> f)
548  : binding(b), factory(std::move(f)) {}
549 
550  public: // Confable Overrides
551  CONFABLE_SCHEMA(ControllerConf) {
552  // clang-format off
553  using namespace schema; // NOLINT(build/namespaces)
554  return Struct{
555  {"binding", make_const_str(binding, "name of controller binding").require()},
556  {"name", make_schema(&name, id_prototype(), "identifier override for binding")},
557  {"vehicle", make_schema(&vehicle, "vehicle controller is assigned to").c_identifier().require()},
558  {"args", make_schema(&args, factory->schema(), "factory-specific arguments")},
559  };
560  // clang-format on
561  }
562 };
563 
564 class ControllerSchema : public FactoryPlugin<ControllerConf, ControllerFactory> {
565  public:
566  ControllerSchema();
567  virtual ~ControllerSchema() = default;
568 };
569 
570 // --------------------------------------------------------------------------------------------- //
571 
572 struct FromSimulator : public Confable {
573  std::string simulator;
574  std::string index_str;
575  size_t index_num;
576 
577  public: // Special
578  bool is_by_name() const { return !index_str.empty(); }
579  bool is_by_index() const { return index_str.empty(); }
580  void clear() {
581  simulator.clear();
582  index_str.clear();
583  index_num = 0;
584  }
585 
586  public: // Confable Overrides
587  CONFABLE_SCHEMA(FromSimulator) {
588  // clang-format off
589  using namespace schema; // NOLINT(build/namespaces)
590  return Variant{
591  Struct{
592  {"simulator", make_schema(&simulator, "simulator").not_empty().require()},
593  {"index", make_schema(&index_num, "index of vehicle in simulator").require()},
594  },
595  Struct{
596  {"simulator", make_schema(&simulator, "simulator").not_empty().require()},
597  {"name", make_schema(&index_str, "name of vehicle in simulator").not_empty().require()},
598  },
599  };
600  // clang-format on
601  }
602 
603  void from_conf(const Conf& c) override {
604  clear(); // Avoid inconsistent state
605  Confable::from_conf(c);
606  }
607 
608  void to_json(Json& j) const override {
609  if (is_by_index()) {
610  j = Json{
611  {"simulator", simulator},
612  {"index", index_num},
613  };
614  } else {
615  j = Json{
616  {"simulator", simulator},
617  {"name", index_str},
618  };
619  }
620  }
621 };
622 
623 struct ComponentConf : public Confable {
624  const std::string binding;
625  boost::optional<std::string> name;
626  std::vector<std::string> from;
627  std::shared_ptr<ComponentFactory> factory;
628  Conf args;
629 
630  public: // Constructors
631  ComponentConf(const std::string& b, std::shared_ptr<ComponentFactory> f)
632  : binding(b), factory(std::move(f)) {}
633 
634  public: // Confable Overrides
635  CONFABLE_SCHEMA(ComponentConf) {
636  // clang-format off
637  using namespace schema; // NOLINT(build/namespaces)
638  return Struct{
639  {"binding", make_const_str(binding, "name of binding").require()},
640  {"name", make_schema(&name, id_prototype(), "globally unique identifier for component")},
641  {"from", Variant{
642  make_schema(&from, "component inputs for binding"),
643  CustomDeserializer(
644  make_prototype<std::string>("component input for binding"),
645  [this](CustomDeserializer*, const Conf& c) {
646  this->from.push_back(c.get<std::string>());
647  }
648  ),
649  }},
650  {"args", make_schema(&args, factory->schema(), "factory-specific args")},
651  };
652  // clang-format on
653  }
654 };
655 
656 class ComponentSchema : public FactoryPlugin<ComponentConf, ComponentFactory> {
657  public:
658  ComponentSchema();
659  virtual ~ComponentSchema() = default;
660 };
661 
662 // TODO(ben): Add AliasConf as alternative to ComponentConf.
663 
685 struct VehicleConf : public Confable {
686  std::string name;
687  FromSimulator from_sim;
688  std::string from_veh;
689  std::map<std::string, ComponentConf> components;
690 
691  private: // Schemas
692  ComponentSchema component_schema;
693 
694  public: // Constructors
695  explicit VehicleConf(const ComponentSchema& s) : component_schema(s) {}
696 
697  public: // Special
698  bool is_from_simulator() const { return from_veh.empty(); }
699  bool is_from_vehicle() const { return !from_veh.empty(); }
700  void clear() {
701  name.clear();
702  from_sim.clear();
703  from_veh.clear();
704  }
705 
706  public: // Confable Overrides
707  CONFABLE_SCHEMA(VehicleConf) {
708  // clang-format off
709  using namespace schema; // NOLINT(build/namespaces)
710  return Struct{
711  {"name", make_schema(&name, "globally unique identifier for vehicle").c_identifier().require()},
712  {"from", Variant{
713  make_schema(&from_sim, "simulator source"),
714  make_schema(&from_veh, "vehicle source").c_identifier(),
715  }.require()},
716  {"components", make_schema(&components, component_schema, "component configuration of vehicle")},
717  };
718  // clang-format on
719  }
720 
721  void from_conf(const Conf& c) override {
722  clear(); // Avoid inconsistent state
723  Confable::from_conf(c);
724  }
725 
726  void to_json(Json& j) const override {
727  Json from = is_from_simulator() ? Json(from_sim) : Json(from_veh);
728  j = Json{
729  {"name", name},
730  {"from", from},
731  {"components", components},
732  };
733  }
734 };
735 
736 class VehicleSchema : public fable::schema::Base<VehicleSchema> {
737  public: // Constructors
738  using Type = VehicleConf;
739  using MakeFunc = ComponentSchema::MakeFunc;
740  using TypeFactory = ComponentSchema::TypeFactory;
741 
742  explicit VehicleSchema(std::string&& desc = "") : Base(std::move(desc)) {}
743 
744  public: // Special
745  bool has_factory(const std::string& name) const { return components_.has_factory(name); }
746  void add_plugin(const std::string& name, std::shared_ptr<Plugin> p) {
747  components_.add_plugin(name, p);
748  }
749 
750  public: // Overrides
751  Json json_schema() const override {
752  VehicleConf v{components_};
753  return v.schema().json_schema();
754  }
755 
756  void validate(const Conf& c) const override {
757  VehicleConf v{components_};
758  v.schema().validate(c);
759  }
760 
761  Json serialize(const Type& x) const { return x.to_json(); }
762 
763  Type make(const Conf& c) const { return deserialize(c); }
764 
765  Type deserialize(const Conf& c) const {
766  VehicleConf v{components_};
767  v.from_conf(c);
768  return v;
769  }
770 
771  void from_conf(const Conf&) override {
772  throw std::logic_error("VehicleSchema does not implement from_conf");
773  }
774 
775  void to_json(Json&) const override {
776  throw std::logic_error("VehicleSchema does not implement to_json");
777  }
778 
779  void reset_ptr() override {}
780 
781  private: // State
782  ComponentSchema components_;
783 };
784 
785 // --------------------------------------------------------------------------------------------- //
786 
787 struct TriggerConf : public PersistentConfable {
788  boost::optional<std::string> label{boost::none};
789  Source source{Source::FILESYSTEM};
790  Conf action{};
791  Conf event{};
792  bool conceal{false};
793  bool sticky{false};
794  bool optional{false};
795 
796  public: // Confable Overrides
797  CONFABLE_SCHEMA(TriggerConf) {
798  // clang-format off
799  using namespace schema; // NOLINT(build/namespaces)
800  auto EANDA_SCHEMA = Variant{
801  String{nullptr, "inline format"}.pattern("^[a-zA-Z0-9_/]+(=.*)?$"),
802  Struct{
803  {"name", id_path_prototype().require()}
804  }.additional_properties(true),
805  };
806  return Struct{
807  {"label", make_schema(&label, "description of trigger")},
808  {"source", make_schema(&source, "source from which trigger originates")},
809  {"event", make_schema(&event, EANDA_SCHEMA, "event").require()},
810  {"action", make_schema(&action, EANDA_SCHEMA, "action").require()},
811  {"sticky", make_schema(&sticky, "whether trigger should be sticky")},
812  {"conceal", make_schema(&conceal, "whether trigger should be concealed in history")},
813  {"optional", make_schema(&optional, "whether errors creating event or action should be ignored")},
814  {"at", Ignore("time at which trigger was executed", JsonType::string)},
815  {"since", Ignore("time since which trigger was in queue", JsonType::string)},
816  };
817  // clang-format on
818  }
819 };
820 
821 // --------------------------------------------------------------------------------------------- //
822 
827 struct SimulationConf : public Confable {
831  boost::optional<std::string> name{boost::none};
832 
836  Duration model_step_width = Duration{20'000'000}; // 20 ms
837 
843  int64_t controller_retry_limit{1000};
844 
848  std::chrono::milliseconds controller_retry_sleep{1};
849 
856  bool abort_on_controller_failure{true};
857 
858  public: // Confable Overrides
859  CONFABLE_SCHEMA(SimulationConf) {
860  // clang-format off
861  using namespace schema; // NOLINT(build/namespaces)
862  return Struct{
863  {"namespace", make_schema(&name, id_prototype(), "namespace for simulation events and actions")},
864  {"model_step_width", make_schema(&model_step_width, "default model time step in ns")},
865  {"controller_retry_limit", make_schema(&controller_retry_limit, "times to retry controller processing before aborting")},
866  {"controller_retry_sleep", make_schema(&controller_retry_sleep, "time to sleep before retrying controller process")},
867  {"abort_on_controller_failure", make_schema(&abort_on_controller_failure, "abort simulation on controller failure")},
868  };
869  // clang-format on
870  }
871 };
872 
873 // --------------------------------------------------------------------------------------------- //
874 
875 class StackIncompleteError : public Error {
876  public:
877  explicit StackIncompleteError(std::vector<std::string>&& missing);
878 
879  std::string all_sections_missing(const std::string& sep = ", ") const;
880  const std::vector<std::string>& sections_missing() const { return sections_missing_; }
881 
882  private:
883  std::vector<std::string> sections_missing_;
884 };
885 
886 using ConfReader = std::function<Conf(const std::string&)>;
887 
888 class Stack : public Confable {
889  private: // Constants (1)
890  std::vector<std::string> reserved_ids_;
891  boost::optional<std::string> schema_ref_;
892 
893  public: // Configuration (13)
894  std::string version;
895  EngineConf engine;
896  ServerConf server;
897  std::vector<IncludeConf> include;
898  std::vector<LoggingConf> logging;
899  std::vector<PluginConf> plugins;
900  std::vector<DefaultConf> simulator_defaults;
901  std::vector<SimulatorConf> simulators;
902  std::vector<DefaultConf> controller_defaults;
903  std::vector<ControllerConf> controllers;
904  std::vector<DefaultConf> component_defaults;
905  std::vector<VehicleConf> vehicles;
906  std::vector<TriggerConf> triggers;
907  SimulationConf simulation;
908 
909  private: // Schemas (3) & Prototypes (3)
910  EngineSchema engine_schema;
911  IncludesSchema include_schema;
912  PluginsSchema plugins_schema;
913 
914  SimulatorSchema simulator_prototype;
915  ControllerSchema controller_prototype;
916  VehicleSchema vehicle_prototype;
917 
918  private: // State (3)
919  std::set<std::string> scanned_plugin_paths_;
920  std::map<std::string, std::shared_ptr<Plugin>> all_plugins_;
921  std::vector<Conf> applied_confs_;
922  ConfReader conf_reader_func_;
923 
924  public: // Constructors
925  Stack();
926  Stack(const Stack& other);
927  Stack(Stack&& other);
928  Stack& operator=(Stack other);
929  ~Stack() = default;
930 
931  friend void swap(Stack& left, Stack& right);
932 
933  public: // Special
934  Logger logger() const { return logger::get("cloe"); }
935 
944  void set_conf_reader(ConfReader fn) {
945  assert(fn != nullptr);
946  conf_reader_func_ = fn;
947  }
948 
952  void apply_plugin_conf(const PluginConf& c);
953 
960  void insert_plugin(const PluginConf& c);
961 
968  void insert_plugin(std::shared_ptr<Plugin> p, const PluginConf& c = {});
969 
973  bool has_plugin_with_name(const std::string& key) const;
974 
978  bool has_plugin_with_path(const std::string& path) const;
979 
985  std::shared_ptr<Plugin> get_plugin_with_name(const std::string& key) const;
986 
992  std::shared_ptr<Plugin> get_plugin_with_path(const std::string& key) const;
993 
997  std::shared_ptr<Plugin> get_plugin_or_load(const std::string& key_or_path) const;
998 
1002  const std::map<std::string, std::shared_ptr<Plugin>>& get_all_plugins() const {
1003  return all_plugins_;
1004  }
1005 
1006  std::vector<DefaultConf> get_simulator_defaults(std::string binding, std::string name) const;
1007  std::vector<DefaultConf> get_controller_defaults(std::string binding, std::string name) const;
1008  std::vector<DefaultConf> get_vehicle_defaults(std::string name) const;
1009  std::vector<DefaultConf> get_component_defaults(std::string binding, std::string name) const;
1010 
1018  void validate() const;
1019 
1023  bool is_valid() const;
1024 
1037  void check_consistency() const;
1038 
1042  void check_defaults() const;
1043 
1054  bool is_complete() const;
1055 
1059  void check_completeness() const;
1060 
1065  void initialize() { from_conf(Conf{Json{{"version", CLOE_STACK_VERSION}}}); }
1066 
1070  Json active_config() const;
1071 
1075  Json input_config() const;
1076 
1077  public: // Confable Overrides
1087  void validate(const Conf& c) const override;
1088 
1089  void to_json(Json& j) const override;
1090  void from_conf(const Conf& c) override { from_conf(c, 0); }
1091  void reset_schema() override;
1092 
1093  CONFABLE_SCHEMA(Stack) {
1094  // clang-format off
1095  using namespace schema; // NOLINT(build/namespaces)
1096 
1097  return Struct{
1098  {"$schema", make_schema(&schema_ref_, "valid URI to schema describing this cloe stack version")},
1099  {"version", make_schema(&version, "version of stackfile").require().enum_of(
1100  CLOE_STACK_SUPPORTED_VERSIONS
1101  )},
1102  {"engine", engine_schema},
1103  {"include", include_schema},
1104  {"logging", make_schema(&logging, "logging configuration").extend(true)},
1105  {"plugins", plugins_schema},
1106  {"server", make_schema(&server, "server configuration")},
1107  {"defaults", Struct{
1108  {"simulators", make_schema(&simulator_defaults, "simulator default configurations").extend(true)},
1109  {"controllers", make_schema(&controller_defaults, "controller default configurations").extend(true)},
1110  {"components", make_schema(&component_defaults, "component default configurations").extend(true)},
1111  }},
1112  {"vehicles", make_schema(&vehicles, vehicle_prototype, "vehicle configuration").extend(true)},
1113  {"simulators", make_schema(&simulators, simulator_prototype, "simulator configuration").extend(true)},
1114  {"controllers", make_schema(&controllers, controller_prototype, "controller configuration").extend(true)},
1115  {"triggers", make_schema(&triggers, "triggers").extend(true)},
1116  {"simulation", make_schema(&simulation, "simulation configuration")},
1117  };
1118  // clang-format on
1119  }
1120 
1121  protected:
1126  void from_conf(const Conf& c, size_t depth);
1127 };
1128 
1129 } // namespace cloe
Definition: conf.hpp:74
Definition: confable.hpp:46
Definition: stack.hpp:77
Definition: stack.hpp:160
void validate(const Conf &c) const override
Definition: stack.hpp:178
bool has(const std::string &key) const
Definition: conf.hpp:130
Definition: stack.hpp:218
Definition: error.hpp:120
Definition: coordinator.hpp:36
Definition: schema.hpp:178
Schema & schema()
Definition: confable.cpp:55
#define ENUM_SERIALIZATION(xType, xMap)
Definition: enum.hpp:52
Definition: stack.hpp:190
virtual void from_conf(const Conf &c)
Definition: confable.cpp:66
Definition: schema.hpp:167
boost::filesystem::path IncludeConf
Definition: stack.hpp:105
void from_conf(const Conf &c)
Definition: stack.hpp:81
WatchdogMode
Definition: stack.hpp:287