diff --git a/.bazelrc b/.bazelrc index f93facf0..96296ed3 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,2 @@ build --cxxopt=-std=c++14 +build --python_top=//:venv --incompatible_use_python_toolchains=false diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml deleted file mode 100644 index 71c6629d..00000000 --- a/.github/workflows/bazel.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: PSim Bazel Builds - -on: - pull_request: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: 'recursive' - - - name: Install dependencies - run: | - sudo apt-get install bazel - - - name: Build and Test with Bazel - run: | - bazel build :psim diff --git a/.github/workflows/main.yml b/.github/workflows/gnc.yml similarity index 97% rename from .github/workflows/main.yml rename to .github/workflows/gnc.yml index ba280aef..599e0e91 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/gnc.yml @@ -1,4 +1,4 @@ -name: PSim Regression Tests +name: GNC Regression Tests on: pull_request: @@ -11,6 +11,7 @@ jobs: - uses: actions/checkout@v2 with: submodules: 'recursive' + - uses: actions/setup-python@v1 with: python-version: '3.7' diff --git a/.github/workflows/psim.yml b/.github/workflows/psim.yml new file mode 100644 index 00000000..a39afa38 --- /dev/null +++ b/.github/workflows/psim.yml @@ -0,0 +1,32 @@ +name: PSim Regression Tests + +on: + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: 'recursive' + + - uses: actions/setup-python@v1 + with: + python-version: '3.7' + architecture: 'x64' + + - name: Install dependencies + run: | + sudo apt-get install bazel + python -m venv venv + source venv/bin/activate + pip install --upgrade pip + pip install -r requirements.txt + + - name: Build and Test + run: | + bazel build //:gnc + bazel build //:psim + bazel test //test/psim:ci diff --git a/.gitignore b/.gitignore index c04f12e6..0c67ad45 100644 --- a/.gitignore +++ b/.gitignore @@ -153,3 +153,6 @@ bazel-out bazel-psim bazel-testlogs bazel-genfiles + +# Compiler commands from Bazel +compile_commands.json diff --git a/BUILD b/BUILD index b499d144..404d71dc 100644 --- a/BUILD +++ b/BUILD @@ -1,11 +1,75 @@ +load("@rules_python//python:defs.bzl", "py_runtime", "py_binary") +load("@rules_cc//cc:defs.bzl", "cc_library") + +load("//:tools/psim.bzl", "psim_autocode") + +# Build flight software's gnc code as a library. +cc_library( + name = "gnc", + srcs = glob([ + "src/gnc/**/*.hpp", "src/gnc/*.cpp", "src/gnc/**/*.inl", + "include/gnc/**/*.inl" + ]), + hdrs = glob([ + "include/gnc/**/*.hpp", "include/orb/*.h", "include/orb/*.hpp" + ]), + includes = ['include'], + copts = ['-Isrc'], + linkstatic = True, + visibility = ["//visibility:public"], + deps = ["@lin//:lin"], +) + +# Let bazel know about our virtual environment. +# +# Also see additional options passed with .bazelrc to ensure we're using the +# virtual environment. +# +# TODO : This is a little hacky and could be cleaned up with toolchains. +py_runtime( + name = "venv", + files = glob(["venv/**"], exclude=["venv/**/* *"]), + interpreter = "venv/bin/python", + python_version = "PY3", + visibility = ["//visibility:private"], +) + +# Register the autocoder as an executable. +# +# This is later consumed by the autocoder command. See psim.bzl for more +# information. +py_binary( + name = "autocoder", + srcs = ["tools/autocoder.py"], + srcs_version = "PY3", + visibility = ["//visibility:private"], +) + +# Autocode the model interfaces +psim_autocode( + name = "autocode", + srcs = glob(["include/psim/**/*.yml"]), + includes = ["include"], + tool = "//:autocoder", + visibility = ["//visibility:private"], +) + +# Build all PSim models as a library +# +# This will be linked with executables later on. cc_library( name = "psim", - srcs = glob(["src/**/*.cpp", "src/**/*.inl", "src/**/*.hpp", "include/**/*.inl"], exclude=["src/targets/*.cpp"]), - hdrs = glob(["include/**/*.hpp", "include/**/*.h"]), - copts = ["-Iinclude -Isrc"], + srcs = glob([ + "src/psim/**/*.hpp", "src/psim/**/*.inl", "src/psim/**/*.cpp", + "include/psim/**/*.inl", "include/psim/**/*.yml.hpp", + ]), + hdrs = glob( + ["include/psim/**/*.hpp"], + exclude = ["include/psim/**/*.yml.hpp"], + ), + includes = ["include"], + copts = ["-Isrc"], linkstatic = True, visibility = ["//visibility:public"], - deps = [ - "@lin//:lin", - ] + deps = ["@lin//:lin", "//:gnc", "//:autocode"], ) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index b37a0b66..00000000 --- a/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# Dockerfile for running PlatformIO unit tests in a Linux environment. This is -# specifically to allow Mac developers to troubleshoot potential precision -# issues as seen in the past. -# - -FROM ubuntu:18.04 - -RUN \ - apt-get update && \ - apt-get -y upgrade && \ - apt-get install -y \ - python3 \ - python3-pip - -RUN \ - python3 -m pip install \ - platformio - -# Following two environment variables were required for PIO -ENV LC_ALL=C.UTF-8 -ENV LANG=C.UTF-8 - -RUN \ - platformio update diff --git a/README.md b/README.md index 1852e56d..1ae9f599 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,74 @@ # PSim -This repository will hold all GNC code for the PAN mission. It will largely be -comprised of the two main executables/modules: +This repository holds all GNC code for the PAN mission. It will be comprised of +two main modules: - 1. MATLAB simulation to support GNC algorithm development. - 2. C++ versions of all flight GNC algorithms. + 1. GNC algorithms and utility functions implemented in C++ that will be + used by flight software and our internal simulation - PSim. + 2. PSim models and simulation implementations. -See folder specific READMEs for more information. +MATLAB code listed in `MATLAB/**` is deprecated and will be phased out by the +new C++ implementation of PSim. -## Running CXX Tests Locally +## Getting Setup -Perform the following from the root PSim directory and replace `python` in the following commands with whatver command matches Python 3.7: +For GNC development, it's only necessary to setup the PlatformIO development +environment. This requires setting up a local virtual environment as shown +below: python -m venv venv source venv/bin/activate + pip install --upgrade pip pip install -r requirements.txt pio platform install native + +Officially, we support Python 3.7 (this is what continuous integration runs) but +you'll most likely have success with Python 3.6+. + +You can ensure everything is behaving as expected by building and running tests +for the GNC code with: + + source venv/bin/activate pio test -e native + +**_Note: Naming your virtual environment `venv` is required for PSim +development!_** + +For PSim development, we need to have the virtual environment setup as shown +above _and_ setup Bazel. You should be able to install Bazel through your +system's package manager. + +It's also recommended to install the Bazel build tools along with the VSCode +extensions 'Bazel' by the 'Bazel Build Team'. With this extension plus +configuring C/C++ intellisense according to +`tools/bazel-compilation-database.sh` the Bazel build system should integrate +fairly well with VSCode. + +To ensure everything is working as expected, you should be able to run the +following successfully: + + bazel test //test/psim:all + +## GNC + +The GNC library is a set of flight software algorithms (mainly controllers and +estimators) along with a myriad of utility functions. + +The source code for the GNC library lives in `include/gnc/**` and `src/gnc/**`. +The associated unit tests are located in `test/gnc/**` and can be built with +PlatformIO using the following command: + + source venv/bin/activate + pio test -e native + +## PSim + +The actual infrastructure for PSim is slightly fluid at the moment; however, +this PR have a good overview of the architecture: [#231](https://github.com/pathfinder-for-autonomous-navigation/psim/pull/231). + +The code lives under `include/psim/**` and `src/psim/**` and leverages the GNC +modules as a dependency. Unit test are implemented under `test/psim/**` and can +be run with the following command: + + bazel test //test/psim:all --test_output=all diff --git a/WORKSPACE b/WORKSPACE index a6df0c16..c67ac7fa 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,21 +1,27 @@ -load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") -lin_build = """ -cc_library( - name = "lin", - srcs = glob(["src/**/*.cpp", "include/**/*.inl"]), - hdrs = glob(["include/**/*.hpp"]), - includes = ["include"], - linkstatic = True, - visibility = ["//visibility:public"] +git_repository( + name = "rules_python", + remote = "https://github.com/bazelbuild/rules_python.git", + commit = "3baa2660569a76898d0f520c73b299ea39b6374d", # (9-8-2020) +) + +http_archive( + name = "gtest", + url = "https://github.com/google/googletest/archive/release-1.10.0.zip", + sha256 = "94c634d499558a76fa649edb13721dce6e98fb1e7018dfaeba3cd7a083945e91", + strip_prefix = "googletest-release-1.10.0", ) -""" -new_git_repository( +# http_archive( +# name = "sofa", +# url = "https://github.com/pathfinder-for-autonomous-navigation/sofa/archive/v1.0.zip", +# sha256 = "a5660ff94270934c33b3fdaaf891620c9a454181dad7fb12378e66a5b73c205f", +# strip_prefix = "sofa-1.0", +# ) + +local_repository( name = "lin", - init_submodules = True, - remote = "https://github.com/pathfinder-for-autonomous-navigation/lin", - commit = "f8fafaa5a29190663bc313a0b835201749b876b8", - build_file_content = lin_build + path = "lib/lin", ) diff --git a/estimatortest/pythonwrapper/pwrap.cpp b/estimatortest/pythonwrapper/pwrap.cpp index b3ee284e..7e54bfef 100644 --- a/estimatortest/pythonwrapper/pwrap.cpp +++ b/estimatortest/pythonwrapper/pwrap.cpp @@ -7,16 +7,17 @@ mypath=Path(filepath) psimpath= mypath.parent.parent.parent #flightsoftwarepath= psimpath.parent/'FlightSoftware' # 'dependencies' controls what get checked for changes before recompile -cfg['dependencies'] = [str(p) for p in (psimpath/'src').rglob('*') if p.is_file()] +cfg['dependencies'] = [str(p) for p in (psimpath/'src/gnc').rglob('*') if p.is_file()] cfg['dependencies'] += [str(p) for p in (psimpath/'lib').rglob('*') if p.is_file()] -cfg['dependencies'] += [str(p) for p in (psimpath/'include').rglob('*') if p.is_file()] +cfg['dependencies'] += [str(p) for p in (psimpath/'include/gnc').rglob('*') if p.is_file()] +cfg['dependencies'] += [str(p) for p in (psimpath/'include/orb').rglob('*') if p.is_file()] cfg['dependencies'] += [str(mypath.parent/'lin_ext.cpp')] cfg['dependencies'] += [str(mypath.parent/'orb_ext.cpp')] #cfg['dependencies'] += [str(p) for p in (flightsoftwarepath/'src').rglob('*') if p.is_file()] #cfg['dependencies'] += [str(p) for p in (flightsoftwarepath/'lib').rglob('*') if p.is_file()] cfg['sources'] = ['lin_ext.cpp', 'orb_ext.cpp'] -cfg['sources'] += [str(p) for p in (psimpath/'src').rglob('*.cpp') if (p.is_file() and p.parent.name!='targets')] +cfg['sources'] += [str(p) for p in (psimpath/'src/gnc').rglob('*.cpp') if (p.is_file() and p.parent.name!='targets')] #cfg['sources'] += [str(p) for p in (flightsoftwarepath/'src/fsw').rglob('*.cpp') if (p.is_file() and p.parent.name!='targets')] #cfg['sources'] += [str(p) for p in (flightsoftwarepath/'src/common').rglob('*.cpp') if (p.is_file() and p.parent.name!='targets')] diff --git a/include/psim/core/configuration.hpp b/include/psim/core/configuration.hpp new file mode 100644 index 00000000..3b7ab7f0 --- /dev/null +++ b/include/psim/core/configuration.hpp @@ -0,0 +1,108 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/configuration.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_CONFIGURATION_HPP_ +#define PSIM_CORE_CONFIGURATION_HPP_ + +#include "parameter.hpp" +#include "parameter_base.hpp" +#include "types.hpp" + +#include +#include + +namespace psim { + +/** @brief Represents a set of parameters used to initialize a simulation. + * + * A configuration is a set of parameters parsed from a configuration file. These + * parameters are made accesible to the simulation's models during intialization + * to specify initial conditions and configure constants. + */ +class Configuration { + private: + /** @brief Map containing the parameter pointers. + */ + std::unordered_map _parameters; + + Configuration() = default; + + /** @brief Adds a parameter and associates it with the specified name. + * + * @param[in] name + * @param[in] value + * + * If a parameter has already been associated with the name, a runtime error is + * thrown. + */ + template + void add(std::string const &name, T &&value); + + public: + Configuration(Configuration const &) = delete; + Configuration &operator=(Configuration const &) = delete; + + Configuration(Configuration &&config); + Configuration &operator=(Configuration &&config); + + virtual ~Configuration(); + + /** @brief Retrives a parameter by name. + * + * @param[in] name + * + * @return Pointer to the parameter. + * + * If no parameter is found by the specified name, a null pointer is returned. + */ + ParameterBase const *get(std::string const &name) const; + + /** @brief Retrives a parameter by name. + * + * @param[in] name + * + * @return Pointer to the parameter. + * + * If no parameter is found by the specified name, a runtime error is thrown. + */ + ParameterBase const &operator[](std::string const &name) const; + + /** @brief Parses a configuration file into a set of parameters. + * + * @param[in] file Configuration file. + * + * @return Parameters. + * + * If the configuration file doesn't exist, or a line with invalid formatting + * is encountered, a runtime error will be thrown. + */ + static Configuration make(std::string const &file); +}; +} // namespace psim + +#endif diff --git a/include/psim/core/model.hpp b/include/psim/core/model.hpp new file mode 100644 index 00000000..aeeff627 --- /dev/null +++ b/include/psim/core/model.hpp @@ -0,0 +1,125 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/model.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_MODEL_HPP_ +#define PSIM_CORE_MODEL_HPP_ + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace psim { + +/** @brief The base unit of "logic" in a simulation. + * + * Every simulation relies on a model type to declare state fields, request state + * fields, and contain all the neccesary functionality to step the simulation + * forward in time. + */ +class Model { + protected: + Model() = default; + + /** @brief Retrive a field from the simulation state. + * + * @tparam T Underlying type. + * + * @param[in] state Simulation state. + * @param[in] name Field name. + * + * @return Pointer to the state field. + * + * Note, if the field wasn't found or has an invalid underlying type, a runtime + * error will be thrown. + */ + template + StateField const *get_field(State const &state, std::string const &name) { + auto const *field_ptr = dynamic_cast const *>(state.get(name)); + if (!field_ptr) + throw std::runtime_error("Invalid cast while getting a field: " + name); + + return field_ptr; + } + + /** @brief Retrive a writable field from the simulation state. + * + * @tparam T Underlying type. + * + * @param[in] state Simulation state. + * @param[in] name Field name. + * + * @return Pointer to the writable state field. + * + * Note, if the field wasn't found or has an invalid underlying type, a runtime + * error will be thrown. + */ + template + StateFieldWritable *get_field(State &state, std::string const &name) { + auto *field_ptr = dynamic_cast *>(state.get_writable(name)); + if (!field_ptr) + throw std::runtime_error("Invalid cast while getting a writable field: " + name); + + return field_ptr; + } + + public: + Model(Model const &) = delete; + Model(Model &&) = delete; + Model &operator=(Model const &) = delete; + Model &operator=(Model &&) = delete; + + virtual ~Model() = default; + + /** @brief The model adds it's state fields to the simulation state. + * + * @param[in] state Simulation state. + */ + virtual void add_fields(State &state); + + /** @brief The model requests extra fields it needs from the simulation state. + * + * @param[in] state Simulation state. + */ + virtual void get_fields(State &state); + + /** @brief The model steps forward. + * + * This essentially is the update step that is responsible for updating state + * fields values. + */ + virtual void step(); +}; +} // namespace psim + +#endif diff --git a/include/psim/core/model_list.hpp b/include/psim/core/model_list.hpp new file mode 100644 index 00000000..12094c5e --- /dev/null +++ b/include/psim/core/model_list.hpp @@ -0,0 +1,87 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/model_list.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_MODEL_LIST_HPP_ +#define PSIM_CORE_MODEL_LIST_HPP_ + +#include + +#include +#include +#include + +namespace psim { + +/** @brief A model consisting of multiple models run in series. + */ +class ModelList : public Model { + private: + /** @brief Model list. + */ + std::vector> _models; + + protected: + ModelList() = default; + + /** @brief Adds a new model to the model list. + * + * @tparam C Model type. + * @tparam Ts Model constructor argument types. + * + * @param[in] ts Model constructor arguments. + * + * Note that the models are executed in the order they are added to the model + * list. + */ + template + void add(Ts &&... ts) { + _models.push_back(std::make_unique(std::forward(ts)...)); + } + + public: + virtual ~ModelList() = default; + + /** @brief All models add their state fields to the simulation state. + * + * @param[in] state Simulation state. + */ + virtual void add_fields(State &state) override; + + /** @brief All models request extra fields they need from the simulation state. + * + * @param[in] state Simulation state. + */ + virtual void get_fields(State &state) override; + + /** @brief All models step forward. + */ + virtual void step() override; +}; +} // namespace psim + +#endif diff --git a/include/psim/core/parameter.hpp b/include/psim/core/parameter.hpp new file mode 100644 index 00000000..a3541570 --- /dev/null +++ b/include/psim/core/parameter.hpp @@ -0,0 +1,159 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/parameters.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_PARAMETER_HPP_ +#define PSIM_CORE_PARAMETER_HPP_ + +#include "parameter_base.hpp" + +#include +#include + +namespace psim { + +/** @brief Simulation parameter holding a single value of the underlying type. + * + * @tparam T Underlying type. + */ +template +class Parameter : public ParameterBase { + private: + /** @brief Underlying value. + */ + T _value; + + public: + virtual ~Parameter() = default; + + /** @brief Default constructs the parameter's initial value. + * + * https://stackoverflow.com/questions/2417065/does-the-default-constructor-initialize-built-in-types + */ + Parameter() + : _value() { } + + /** @param[in] value Parameter's initial value. + * + * @{ + */ + Parameter(T const &value) + : _value(value) { } + + Parameter(T &&value) + : _value(std::move(value)) { } + /** @} + */ + + /** @param[in] value Parameter's initial value. + * + * If the passed parameter's underlying type does not match, a runtime error + * will be thrown. + * + * @{ + */ + Parameter(ParameterBase const ¶m) + : _value(param.get()) { } + + Parameter(ParameterBase &¶m) + : _value(std::move(param.get())) { } + /** @} + */ + + /** @return Reference to the underlying type. + * + * @{ + */ + operator T const & () const { + return _value; + } + + operator T & () { + return _value; + } + /** @} + */ + + /** @param[in] value New value held by the parameter. + * + * @return Reference to this parameter. + * + * @{ + */ + Parameter &operator=(T const &value) { + _value = value; + } + + Parameter &operator=(T &&value) { + _value = std::move(value); + } + /** @} + */ + + /** @param[in] param New value held by the parameter. + * + * @return Reference to this parameter. + * + * If the passed parameter's underlying type does not match a runtime error + * will be thrown. + * + * @{ + */ + Parameter &operator=(ParameterBase const ¶m) { + _value = param.get(); + } + + Parameter &operator=(ParameterBase &¶m) { + _value = std::move(param.get()); + } + /** @} + */ + + /** @returns Reference to the underlying value. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + * + * @{ + */ + template + U const &get() const { + static_assert(std::is_same::value, "Invalid parameter type in call to 'get'."); + return _value; + } + + template + U &get() { + static_assert(std::is_same::value, "Invalid parameter type in call to 'get'."); + return _value; + } + /** @} + */ +}; +} // namespace psim + +#endif diff --git a/include/psim/core/parameter_base.hpp b/include/psim/core/parameter_base.hpp new file mode 100644 index 00000000..71754888 --- /dev/null +++ b/include/psim/core/parameter_base.hpp @@ -0,0 +1,84 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/parameter_base.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_PARAMETER_BASE_HPP_ +#define PSIM_CORE_PARAMETER_BASE_HPP_ + +#include + +namespace psim { + +template +class Parameter; + +/** @brief Virtual base class for all parameters. + * + * The main purpose of this class is to allow dynamic casting to be used to check + * parameter types at runtime and provide helpful casting functions. + */ +class ParameterBase { + protected: + ParameterBase() = default; + + public: + virtual ~ParameterBase() = default; + + /** @brief Attempt to get the parameter's underlying value. + * + * @tparam Expected underlying type. + * + * @return Reference to the underlying value. + * + * If the underlying type doesn't match the expected underlying type, a runtime + * error will be thrown. + * + * @{ + */ + template + T const &get() const { + auto const *param_ptr = dynamic_cast const *>(this); + if (!param_ptr) + throw std::runtime_error("Invalid parameter type in call to 'get'."); + + return (T const &) *param_ptr; + } + + template + T &get() { + auto *param_ptr = dynamic_cast *>(this); + if (!param_ptr) + throw std::runtime_error("Invalid parameter type in call to 'get'."); + + return (T &) *param_ptr; + } + /** @} + */ +}; +} // namespace psim + +#endif diff --git a/include/psim/core/simulation.hpp b/include/psim/core/simulation.hpp new file mode 100644 index 00000000..6932409b --- /dev/null +++ b/include/psim/core/simulation.hpp @@ -0,0 +1,86 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/model.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_SIMULATION_HPP_ +#define PSIM_CORE_SIMULATION_HPP_ + +#include +#include + +#include +#include + +namespace psim { + +/** @brief Represents a full simulation. + * + * The main purpose of the simulation class is to merge the functionality of a + * state and model. It's how end users are intended to interact with models, + * state fields, and steps. + */ +class Simulation : public State { + private: + /** @brief Model employed by the simulation. + * + * Making this field dynamically allocated was very intentional. It ensures on + * moves that the stored state fields address remain valid. + */ + std::unique_ptr _model; + + /** @brief Private constructor employeed by the factory function. + */ + Simulation(std::unique_ptr &&model); + + public: + Simulation() = delete; + Simulation(Simulation const &) = delete; + Simulation(Simulation &&) = default; + Simulation &operator=(Simulation const &) = delete; + Simulation &operator=(Simulation &&) = default; + + virtual ~Simulation() = default; + + /** @brief Steps the simulation (and all underlying models) forward. + */ + void step(); + + /** @brief Create a simulation based on the given model type. + * + * @tparam C Model type. + * @tparam Ts Model constructor arguments. + * + * @return A simulation. + */ + template + static Simulation make(Ts &&... ts) { + return Simulation(std::make_unique(std::forward(ts)...)); + } +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state.hpp b/include/psim/core/state.hpp new file mode 100644 index 00000000..7857da87 --- /dev/null +++ b/include/psim/core/state.hpp @@ -0,0 +1,134 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_HPP_ +#define PSIM_CORE_STATE_HPP_ + +#include "state_field.hpp" +#include "state_field_writable.hpp" + +#include +#include + +namespace psim { + +/** @brief Collection of state fields comprising a simulation's state. + * + * This class only holds pointers to the actually fields that are held by the + * models themselves. Therefore, the state is invalid if any models are + * deallocated. + * + * The fields are mapped one to one with a string name. It's not possible to have + * a readable and writable field registered to the same name. + */ +class State { + private: + /** @brief Map to the readable fields. + */ + std::unordered_map _readable_fields; + + /** @brief Map to the writable fields. + */ + std::unordered_map _writable_fields; + + public: + State() = default; + State(State const &) = delete; + State(State &&) = default; + State &operator=(State const &) = delete; + State &operator=(State &&) = default; + + virtual ~State() = default; + + /** @param[in] name Field name. + * + * @return True if a field is registered under the given name and false + * otherwise. + */ + bool has(std::string const &name) const; + + /** @param[in] name Field name. + * + * @return True if a writable field is registered under the given name and + * false otherwise. + */ + bool has_writable(std::string const &name) const; + + /** @brief Add a new field to the simulation state. + * + * @param[in] name Field name. + * @param[in] field_ptr Pointer to the new field. + * + * If a field already exists under the given name, a runtime error will be + * thrown. + */ + void add(std::string const &name, StateFieldBase const *field_ptr); + + /** @brief Add a new writable field to the simulation state. + * + * @param[in] name Field name. + * @param[in] field_ptr Pointer to the new writable field. + * + * If a field already exists under the given name, a runtime error will be + * thrown. + */ + void add_writable(std::string const &name, StateFieldWritableBase *field_ptr); + + /** @brief Retrieve a field from the simulation state. + * + * @param[in] name Field name. + * + * @return Pointer to the retrieved field. + * + * If no such field exists, a null pointer is returned. + */ + StateFieldBase const *get(std::string const &name) const; + + /** @brief Retrieve a writable field from the simulation state. + * + * @param[in] name Field name. + * + * @return Pointer to the retrieved writable field. + * + * If no such field exists, a null pointer is returned. + */ + StateFieldWritableBase *get_writable(std::string const &name); + + /** @brief Retrieve a field from the simulation state. + * + * @param[in] name Field name. + * + * @return Reference to the retrieved field. + * + * If no such field exists, a runtime error will be thrown. + */ + StateFieldBase const &operator[](std::string const &name) const; +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state_field.hpp b/include/psim/core/state_field.hpp new file mode 100644 index 00000000..913e9a1f --- /dev/null +++ b/include/psim/core/state_field.hpp @@ -0,0 +1,143 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state_field.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_FIELD_HPP_ +#define PSIM_CORE_STATE_FIELD_HPP_ + +#include "state_field_base.hpp" + +#include +#include + +namespace psim { + +/** @brief Interface to a read only state field. + * + * @tparam T Underlying type. + * + * A generic state field allows reads producing data of the specified underlying + * type. Convenience functions to attempt casts to writable state fields are + * still supported. + */ +template +class StateField : virtual public StateFieldBase { + protected: + StateField() = default; + + public: + virtual ~StateField() = default; + + /** @return Constant reference to the underlying value. + */ + virtual operator T const & () const = 0; + + /** @returns Pointer to this object. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + * + * @{ + */ + template + StateField const *cast() const { + static_assert(std::is_same::value, "Invalid field type in call to 'cast'."); + return this; + } + + template + StateField *cast() { + static_assert(std::is_same::value, "Invalid field type in call to 'cast'."); + return this; + } + /** @} + */ + + /** @brief Attempt to cast this field to a writable field. + * + * @return Casted pointer. + * + * This function will either succeed in casting or throw as exception if the + * field is not writable. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + * + * @{ + */ + template + StateFieldWritable const *cast_writable() const { + static_assert(std::is_same::value, "Invalid field type in call to 'cast_writable'."); + + auto const *field_ptr = dynamic_cast const *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast_writable'."); + + return field_ptr; + } + + template + StateFieldWritable *cast_writable() { + static_assert(std::is_same::value, "Invalid field type in call to 'cast_writable'."); + + auto *field_ptr = dynamic_cast *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast_writable'."); + + return field_ptr; + } + /** @} + */ + + /** @return Constant reference to the underlying value. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + */ + template + U const &get() const { + static_assert(std::is_same::value, "Invalid field type in call to 'get'."); + return (U const &) *this; + } + + /** @brief Attempt to get read write data from the field. + * + * @tparam Expected underlying type. + * + * @return Reference to the underlying value. + * + * If the underlying type doesn't match the expected underlying type or the + * field isn't writable, a runtime error will be thrown. + */ + template + T &get_writable() { + return cast_writable()->get_writable(); + } +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state_field_base.hpp b/include/psim/core/state_field_base.hpp new file mode 100644 index 00000000..5fde166a --- /dev/null +++ b/include/psim/core/state_field_base.hpp @@ -0,0 +1,156 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state_field_base.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_FIELD_BASE_HPP_ +#define PSIM_CORE_STATE_FIELD_BASE_HPP_ + +#include +#include + +namespace psim { + +template +class StateField; + +class StateFieldWritableBase; + +template +class StateFieldWritable; + +/** @brief Parent class for all state fields. + * + * The main purpose of this class is to allow dynamic casting to be used to check + * field types at runtime and provided casting functions for convenience. + */ +class StateFieldBase { + protected: + StateFieldBase() = default; + + public: + StateFieldBase(StateFieldBase const &) = delete; + StateFieldBase(StateFieldBase &&) = delete; + StateFieldBase &operator=(StateFieldBase const &) = delete; + StateFieldBase &operator=(StateFieldBase &&) = delete; + + virtual ~StateFieldBase() = default; + + /** @brief Attempt to cast this field to a specific underlying type. + * + * @tparam Expected underlying type. + * + * @return Casted pointer. + * + * This function will either succeed in casting or throw an expection if the + * expected underlying type does not match. + * + * @{ + */ + template + StateField const *cast() const { + auto const *field_ptr = dynamic_cast const *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast'."); + + return field_ptr; + } + + template + StateField *cast() { + auto *field_ptr = dynamic_cast *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast'."); + + return field_ptr; + } + /** @} + */ + + /** @brief Attempt to cast this field to a writable field with specific + * underlying type. + * + * @tparam T Expected underlying type. + * + * @return Casted pointer. + * + * This function will either succeed in casting or throw as exception if the + * expected underlying type does not match. The field, additionally, must be + * writable or an exception is thrown. + * + * @{ + */ + template + StateFieldWritable const *cast_writable() const { + auto const *field_ptr = dynamic_cast const *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast_writable'."); + + return field_ptr; + } + + template + StateFieldWritable *cast_writable() { + auto *field_ptr = dynamic_cast *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast_writable'."); + + return field_ptr; + } + /** @} + */ + + /** @brief Attempt to get read only data from the field. + * + * @tparam Expected underlying type. + * + * @return Constant reference to the underlying value. + * + * If the underlying type doesn't match the expected underlying type, a runtime + * error will be thrown. + */ + template + T const &get() const { + return cast()->get(); + } + + /** @brief Attempt to get read write data from the field. + * + * @tparam Expected underlying type. + * + * @return Reference to the underlying value. + * + * If the underlying type doesn't match the expected underlying type or the + * field isn't writable, a runtime error will be thrown. + */ + template + T &get_writable() { + return cast_writable()->get_writable(); + } +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state_field_lazy.hpp b/include/psim/core/state_field_lazy.hpp new file mode 100644 index 00000000..2eab7274 --- /dev/null +++ b/include/psim/core/state_field_lazy.hpp @@ -0,0 +1,101 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state_field_lazy.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_FIELD_LAZY_HPP_ +#define PSIM_CORE_STATE_FIELD_LAZY_HPP_ + +#include "state_field.hpp" +#include "state_field_writable.hpp" + +#include +#include + +namespace psim { + +/** @brief State field implemenation whose value is lazily evaluated. + * + * @tparam Underlying type. + * + * This field is particularly useful for fields that may be helpful when + * debugging or devloping but don't need to be evaluated all the time. The field + * take a function that can later be evaluated if the field is read. + */ +template +class StateFieldLazy : public StateField { + private: + /** @brief Lazy evaluation function. + */ + std::function const _function; + + /** @brief Flag specifying if the currently held value is up to date. + */ + bool mutable _evaluated; + + /** @brief Current value (may or may not be valid). + */ + T mutable _value; + + public: + StateFieldLazy() = delete; + + virtual ~StateFieldLazy() = default; + + /** @param[in] function Lazy evaluation function. + * + * @{ + */ + StateFieldLazy(std::function const &function) + : _function(function), _evaluated(false) { } + + StateFieldLazy(std::function &&function) + : _function(std::move(function)), _evaluated(false) { } + /** @} + */ + + /** @return Reference to the underlying value. + * + * Note the actual calculation is only performed if the value is requested and + * then cached until the lazy state field has been reset. + */ + virtual operator T const & () const override { + if (!_evaluated) { + _value = _function(); + _evaluated = true; + } + return _value; + } + + /** @brief Clears the current value held by the state field. + */ + void reset() { + _evaluated = false; + } +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state_field_valued.hpp b/include/psim/core/state_field_valued.hpp new file mode 100644 index 00000000..d244fbf6 --- /dev/null +++ b/include/psim/core/state_field_valued.hpp @@ -0,0 +1,105 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state_field_valued.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_FIELD_VALUED_HPP_ +#define PSIM_CORE_STATE_FIELD_VALUED_HPP_ + +#include "state_field.hpp" +#include "state_field_writable.hpp" + +#include + +namespace psim { + +/** @brief Writable state field implemenation directly backed by a value. + * + * @tparam Underlying type. + */ +template +class StateFieldValued : public StateFieldWritable { + private: + /** @brief Underlying value. + */ + T _value; + + public: + using StateFieldWritable::operator=; + + virtual ~StateFieldValued() = default; + + /** @brief Default constructs the parameter's initial value. + * + * https://stackoverflow.com/questions/2417065/does-the-default-constructor-initialize-built-in-types + */ + StateFieldValued() + : _value() { } + + /** @param[in] value Initial value of the state field. + * + * @{ + */ + StateFieldValued(T const &value) + : _value(value) { } + + StateFieldValued(T &&value) + : _value(std::move(value)) { } + /** @} + */ + + /** @param[in] param Initial value of the state field. + * + * If the parameter's underlying type doesn't match the field's underlying + * type, a runtime error will be thrown. + * + * @{ + */ + StateFieldValued(ParameterBase const ¶m) + : _value(param.get()) { } + + StateFieldValued(ParameterBase &¶m) + : _value(std::move(param.get())) { } + /** @} + */ + + /** @return Reference to the underlying value. + * + * @{ + */ + virtual operator T const & () const override { + return _value; + } + + virtual operator T & () override { + return _value; + } + /** @} + */ +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state_field_writable.hpp b/include/psim/core/state_field_writable.hpp new file mode 100644 index 00000000..bc413164 --- /dev/null +++ b/include/psim/core/state_field_writable.hpp @@ -0,0 +1,139 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state_field_writable.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_FIELD_WRITABLE_HPP_ +#define PSIM_CORE_STATE_FIELD_WRITABLE_HPP_ + +#include "parameter.hpp" +#include "state_field.hpp" +#include "state_field_writable_base.hpp" + +#include +#include + +namespace psim { + +/** @brief Interface to a read write state field + * + * @tparam T Underlying type. + * + * A writable state field allows reads or writes to or from the fields as long as + * they are of the underlying data type. + */ +template +class StateFieldWritable : public StateFieldWritableBase, public StateField { + protected: + StateFieldWritable() = default; + + public: + virtual ~StateFieldWritable() = default; + + /** @return Reference to the underlying value. + */ + virtual operator T & () = 0; + + /** @brief Assign a value to this state field. + * + * @param[in] value Value assigned. + * + * @return Reference to this state field. + * + * @{ + */ + StateFieldWritable &operator=(T const &value) { + (T &) *this = value; + return *this; + } + + StateFieldWritable &operator=(T &&value) { + (T &) *this = std::move(value); + return *this; + } + /** @} + */ + + /** @returns Pointer to this object. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + * + * @{ + */ + template + StateFieldWritable const *cast() const { + static_assert(std::is_same::value, "Invalid field type in call to 'cast'."); + return this; + } + + template + StateFieldWritable *cast() { + static_assert(std::is_same::value, "Invalid field type in call to 'cast'."); + return this; + } + + template + StateFieldWritable const *cast_writable() const { + return cast(); + } + + template + StateFieldWritable *cast_writable() { + return cast(); + } + /** @} + */ + + /** @return Reference to the underlying type. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + * + * @{ + */ + template + U const &get() const { + static_assert(std::is_same::value, "Invalid field type in call to 'get'."); + return (U const &) *this; + } + + template + U &get() { + static_assert(std::is_same::value, "Invalid field type in call to 'get'."); + return (U &) *this; + } + + template + U &get_writable() { + return get(); + } + /** @} + */ +}; +} // namespace psim + +#endif diff --git a/include/psim/core/state_field_writable_base.hpp b/include/psim/core/state_field_writable_base.hpp new file mode 100644 index 00000000..b4fa9c2d --- /dev/null +++ b/include/psim/core/state_field_writable_base.hpp @@ -0,0 +1,125 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/state_field_writable_base.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_STATE_FIELD_WRITABLE_BASE_HPP_ +#define PSIM_CORE_STATE_FIELD_WRITABLE_BASE_HPP_ + +#include "state_field_base.hpp" + +#include + +namespace psim { + +/** @brief + */ +class StateFieldWritableBase : virtual public StateFieldBase { + protected: + StateFieldWritableBase() = default; + + public: + using StateFieldBase::cast_writable; + + virtual ~StateFieldWritableBase() = default; + + /** @brief Attempt to cast this field to a specific underlying type. + * + * @tparam Expected underlying type. + * + * @return Casted pointer. + * + * This function will either succeed in casting or throw an expection if the + * expected underlying type does not match. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + * + * @{ + */ + template + StateFieldWritable const *cast() const { + auto const *field_ptr = dynamic_cast const *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast'."); + + return field_ptr; + } + + template + StateFieldWritable *cast() { + auto *field_ptr = dynamic_cast *>(this); + if (!field_ptr) + throw std::runtime_error("Invalid field type in call to 'cast'."); + + return field_ptr; + } + /** @} + */ + + /** @brief Attempt to get data from the field. + * + * @tparam Expected underlying type. + * + * @return Reference to the underlying type. + * + * If the underlying type doesn't match the expected underlying type, a runtime + * error will be thrown. + * + * @{ + */ + template + T const &get() const { + return cast()->get(); + } + + template + T &get() { + return cast()->get(); + } + /** @} + */ + + /** @brief Attempt to get read write data from the field. + * + * @tparam Expected underlying type. + * + * @return Reference to the underlying value. + * + * If the underlying type doesn't match the expected underlying type or the + * field isn't writable, a runtime error will be thrown. + * + * This function was re-implemented to avoid potential type checking overhead + * and keep a consistant interface. + */ + template + T &get_writable() { + return get(); + } +}; +} // namespace psim + +#endif diff --git a/include/psim/core/types.hpp b/include/psim/core/types.hpp new file mode 100644 index 00000000..4bc5bae2 --- /dev/null +++ b/include/psim/core/types.hpp @@ -0,0 +1,50 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/core/types.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_CORE_TYPES_HPP_ +#define PSIM_CORE_TYPES_HPP_ + +#include + +namespace psim { + +/** @brief Types that are held by parameters and fields. + * + * @{ + */ +typedef long Integer; +typedef double Real; +typedef lin::Vector2d Vector2; +typedef lin::Vector3d Vector3; +typedef lin::Vector4d Vector4; +/** @} + */ + +} // namespace psim + +#endif diff --git a/include/psim/truth/time.hpp b/include/psim/truth/time.hpp new file mode 100644 index 00000000..ba66b8fb --- /dev/null +++ b/include/psim/truth/time.hpp @@ -0,0 +1,53 @@ +// +// MIT License +// +// Copyright (c) 2020 Pathfinder for Autonomous Navigation (PAN) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +/** @file psim/truth/time.hpp + * @author Kyle Krol + */ + +#ifndef PSIM_TRUTH_TIME_HPP_ +#define PSIM_TRUTH_TIME_HPP_ + +#include + +namespace psim { + +class Time : public TimeInterface