diff --git a/.vscode/cspell.json b/.vscode/cspell.json index 2816ad4c..56db7db9 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -18,13 +18,17 @@ "mkdocs", "mypy", "OMPL", + "proto", "Protobuf", + "protofiles", "Raye", + "rclcpp", "roscd", "roslaunch", "rosrun", "srcnew", "srcraye", + "srcs", "struct", "Structs", "uncrustify", diff --git a/docs/current/network_systems/repo_structure.md b/docs/current/network_systems/repo_structure.md new file mode 100644 index 00000000..5978850d --- /dev/null +++ b/docs/current/network_systems/repo_structure.md @@ -0,0 +1,107 @@ +# Repository Structure + +## root + +The Network Systems directory is similar to the following (READMEs and CMakeLists excluded): + +``` +network_systems +| package.xml +| +└───.github +| | ... +| +└───launch +| | ... +| +└───lib +| └───protofiles +| | message.proto +| | ... +| +└───projects + └───example + | └───inc + | | | example.h + | | | ... + | | + | └───src + | | | example.cpp + | | | example_subscriber.cpp + | | | ... + | | + | └───test + | | | test_example.cpp + | | | ... + | + └───... + +``` + +At the root of the directory is a `package.xml`. This file tells ROS2 that the network_systems package exists. It is +what allows us to run, for example: `ros2 run network_systems main_launch.py`. + +`.github/` contains Github specific files like workflows for continuous integration. + +`launch/` contains ROS launch files. + +## lib + +The lib is where we will place static libraries that we want to be accessible to all programs. This means they do not +generate their own executable and will always link to a program in the projects directory. + +To add new libraries, create a folder and add it to `lib/CMakeLists.txt`. Add a `CMakeLists.txt` file to your newly +created folder and fill it out accordingly. + +## projects + +Each directory found under projects is module directory. For example, the CAN transceiver will have its own folder in +this directory. Each module will define its executable, unit test executable, and (optionally) its public interface. + +Additionally, we will separate the functional source file (`example.cpp`) from the the ROS communication interface file +(`example_subscriber.cpp`). The point is to make the unit tests cover only the functional code, while the communication +code is tackled by integration testing. + +To add a new module, create a folder and add it to `projects/CMakeLists.txt`. In your new module folder, add an `inc/` +(optional), `src/`, and `test/` folder, as well as a `CMakeLists.txt` which will need to be filled out accordingly. + +??? example + This is the `CMakeLists.txt` for an example module where the source files are for a Cached Fibonacci program. + + ```cmake + set(module example) + + # Create module library + set(srcs + ${CMAKE_CURRENT_LIST_DIR}/src/cached_fib.cpp + ) + # Make the header accessible to other modules + add_library(${module} ${srcs}) + target_include_directories(${module} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc ${PROTOBUF_INCLUDE_PATH}) + + # Create module ROS executable + set(bin_module bin_${module}) + set(bin_srcs + ${srcs} + ${CMAKE_CURRENT_LIST_DIR}/src/cached_fib_subscriber.cpp + ) + add_executable(${bin_module} ${bin_srcs}) + ament_target_dependencies(${bin_module} rclcpp std_msgs) + target_include_directories(${bin_module} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/inc ${PROTOBUF_INCLUDE_PATH}) + install(TARGETS ${bin_module} DESTINATION lib/${PROJECT_NAME}) + # Rename the output binary to just be the module name + set_target_properties(${bin_module} PROPERTIES OUTPUT_NAME ${module}) + + # Create unit test + set(test_module test_${module}) + set(test_srcs + ${srcs} + ${CMAKE_CURRENT_LIST_DIR}/test/test_cached_fib.cpp + ) + add_executable(${test_module} ${test_srcs}) + target_include_directories(${test_module} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/inc ${PROTOBUF_INCLUDE_PATH}) + target_link_libraries(${test_module} ${GTEST_LINK_LIBS}) + # Make the unit test runnable with CTest (invoked via test.sh) + add_test(NAME ${test_module} COMMAND ${test_module}) + + ``` diff --git a/docs/reference/cpp/differences.md b/docs/reference/cpp/differences.md index 9cdbe9ad..86d82259 100644 --- a/docs/reference/cpp/differences.md +++ b/docs/reference/cpp/differences.md @@ -4,6 +4,11 @@ For most use cases, you can think of C++ as a superset of C. While this is not t you are able to write standard C code for a C++ program without issues. However, doing so ignores a lot of the benefits and reasons to use C++. +!!! note + Function definitions and declarations shown on this page will often look like `type function_name(void);`. This is + standard in C to denote no parameters. In C++, it is considered better practice to use `type function_name();` + without the void. There is some nuance to this which you can read about [here](https://softwareengineering.stackexchange.com/questions/286490/what-is-the-difference-between-function-and-functionvoid){target=_blank}. + ## Classes and Structs In C structs can only contain member variables, but in C++ structs are basically classes but with a default member @@ -13,7 +18,8 @@ visibility of public instead of private. The following code blocks are equivalent. ```C++ - struct foo { + struct foo + { private: int x; void helper(void); @@ -23,7 +29,8 @@ visibility of public instead of private. ``` ```C++ - class foo { + class foo + { private: int x; void helper(void); @@ -63,7 +70,8 @@ Though be aware that namespaces are not necessary everywhere. See the following #include "A.h" #include "B.h" - int main(void) { + int main(void) + { int a = bar(); ... } @@ -73,14 +81,16 @@ Though be aware that namespaces are not necessary everywhere. See the following === "C++" ```C++ title="A.h" - namespace a { + namespace a + { float x; int bar(void); } ``` ```C++ title="B.h" - namespace b { + namespace b + { float x; int bar(void); } @@ -90,7 +100,8 @@ Though be aware that namespaces are not necessary everywhere. See the following #include "A.h" #include "B.h" - int main(void) { + int main(void) + { int a = a::bar(); int b = b::bar(); float xa = a::x; @@ -107,7 +118,8 @@ Though be aware that namespaces are not necessary everywhere. See the following using namespace std; namespace io = std::filesystem; - int main(int argc, char* argv[]) { + int main(int argc, char* argv[]) + { bool isDirectory = io::is_directory(argv[1]); // Equivalent to std::filesystem::is_directory(argv[1]) cout << isDirectory << endl; return 0; @@ -124,11 +136,13 @@ Though be aware that namespaces are not necessary everywhere. See the following === "OK" ```C++ - class string { + class string + { // Insert implementation here } - int main(void) { + int main(void) + { string ourString = "Our own string implementation"; std::string stdString = "Standard Library string implementation"; ... @@ -141,7 +155,8 @@ Though be aware that namespaces are not necessary everywhere. See the following using namespace std; // ERROR - multiple definitions of type string - class string { + class string + { } ``` @@ -154,6 +169,8 @@ Though be aware that namespaces are not necessary everywhere. See the following ## Constant Expressions +TL;DR use constant expressions over macros whenever possible. + In C, if we want to declare a constant or a function/expression that we want to be evaluated at compile time, we need to use `#define` statements. One of the problems with `#define` statements is that they perform a simple copy paste wherever they're used. For example: @@ -164,7 +181,8 @@ wherever they're used. For example: #define PI 3.14F #define AREA_OF_CIRCLE(radius) ((PI) * (radius) * (radius)) - int main(void) { + int main(void) + { float area = AREA_OF_CIRCLE(2.5F); ... } @@ -173,7 +191,8 @@ wherever they're used. For example: === "After Precompile" ```C++ - int main(void) { + int main(void) + { float area = ((3.14F) * (2.5F) * (2.5F)); ... } @@ -192,7 +211,8 @@ In C++, the use of constant expressions are preferred. ```C++ constexpr float pi = 3.14F; -constexpr float area_of_circle(float radius) { +constexpr float area_of_circle(float radius) +{ return pi * radius * radius; } ``` @@ -201,12 +221,14 @@ Constant expressions do *not* get copy pasted, and are instead placed in program or function. They also respect namespaces and function scopes, meaning the following code compiles. ```C++ title="Constant Expression Scoping" -void foo(void) { +void foo(void) +{ constexpr float rand = 123.456; ... } -void bar (void) { +void bar (void) +{ constexpr float rand = 789.123; ... } @@ -236,13 +258,15 @@ costs. ```C #include "stdio.h" - void print_contents(int *arr, int size) { + void print_contents(int *arr, int size) + { for (int i = 0; i < size; i++) { printf("%d\n", *arr); } } - int main(void) { + int main(void) + { int arr[5] = {0, 1, 2, 3, 4}; foo(arr, 5); return 0; @@ -260,13 +284,15 @@ costs. #include #include - void print_contents(std::span container) { + void print_contents(std::span container) + { for (const auto &e : container) { std::cout << e << std::endl; } } - int main(void) { + int main(void) + { std::array arr = {0, 1, 2, 3, 4}; foo(arr); return 0; diff --git a/docs/reference/cpp/tools.md b/docs/reference/cpp/tools.md index 750b2051..76586090 100644 --- a/docs/reference/cpp/tools.md +++ b/docs/reference/cpp/tools.md @@ -2,15 +2,44 @@ A lot goes into making a well structured C++ project, much more than any one team should have to do. +## Boost + +[Boost](https://www.boost.org/){target=_blank} is a collection of C++ libraries and can be thought of as an expanded +standard library. It also backports some more recent C++ libraries like `std::span` (C++ 20 only) to older versions like +C++14, which we use (we would use `boost::span`). + ## CMake CMake is a powerfull build automation tool that makes compiling code for large projects with a lot of interoperating files a lot easier. Steps 1-3 of the [official tutorial](https://cmake.org/cmake/help/latest/guide/tutorial/index.html){target=_blank} are great for understanding the basics. +## Clang + +In its most basic form, [Clang](https://clang.llvm.org/){target=_blank} is a compiler for the C language family. +Clang has multiple +benefits like easier portability compared to, for example, GCC. Clang is actually "half" the compiler, the other half +being LLVM. Without going into unnecessary detail, Clang compiles C++ code to a generic language before LLVM compiles +it to machine specific language. + +### Clangd + +[Clangd](https://clangd.llvm.org/){target=_blank} is the Clang language server. It provides a much more powerful +intellisense than the default one used in VSCode's C/C++ extension. + +### Clang-Tidy + +[Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/){target=_blank} is a linting tool, who's main purpose is to catch potential +programming errors caused by bad programming style/practices using just static analysis. + +### Clang Format + +An autoformatting tool that makes enforcing style guidelines much easier. When se tup, it corrects formatting as soon +as you hit save. + ## GDB -The [GNU Project Debugger](https://www.sourceware.org/gdb/){target=_blank} is the most commonly debugger for the C +The [GNU Project Debugger](https://www.sourceware.org/gdb/){target=_blank} is the most common debugger for the C language family. VSCode also has a degree of integration with GDB that allows an easy to use GUI. This [GDB cheat sheet](https://darkdust.net/files/GDB%20Cheat%20Sheet.pdf){target=_blank} has all the GDB comands you will need to know. Be aware the VSCode has GUI buttons for some of these commands that are @@ -28,36 +57,48 @@ The [GoogleTest Primer](https://google.github.io/googletest/primer.html){target= === "Cached Fibonacci Program" ```C++ title="cached_fib.h" + #pragma once + + #include #include - class CachedFib { - public: - void CachedFib(int n); - int getFib(int n); + + constexpr auto CachedFibTopic = "cached_fib"; + + class CachedFib + { private: std::vector cache; - } + + public: + explicit CachedFib(std::size_t); + int getFib(std::size_t); + }; ``` ```C++ title="cached_fib.cpp" + #include "cached_fib.h" + #include #include - #include "cached_fib.h" - void CachedFib::CachedFib(int n) { - cache.push_back(0); - cache.push_back(1); - for (int i = 2; i < n; i++) { - cache.push_back(cache[i - 1] + cache[i - 2]); + CachedFib::CachedFib(const std::size_t n) + { + this->cache.push_back(0); + this->cache.push_back(1); + for (std::size_t i = 2; i < n; i++) { + this->cache.push_back(cache[i - 1] + this->cache[i - 2]); } } - int CachedFib::getFib(int n) { - if (cache.size() < n) { - for (int i = cache.size(); i < n; i++) { - cache.push_back(cache[i-1] + cache[i-2]); + int CachedFib::getFib(const std::size_t n) + { + if (this->cache.size() < n) { + for (std::size_t i = cache.size(); i < n; i++) { + this->cache.push_back(cache[i - 1] + this->cache[i - 2]); } } - std::cout << cache[n - 1] << std::endl; + std::cout << this->cache[n - 1] << std::endl; + return this->cache[n - 1]; } ``` @@ -68,22 +109,28 @@ The [GoogleTest Primer](https://google.github.io/googletest/primer.html){target= #include "cached_fib.h" #include "gtest/gtest.h" - CachedFib::testFib; + constexpr int defaultSize = 5; + + static CachedFib testFib = CachedFib(defaultSize); - class TestFib : public ::testing::Test { + class TestFib : public ::testing::Test + { protected: - void Setup override { + TestFib() + { // Every time a test is started, testFib is reinitialized with a constructor parameter of 5 - testFib = CachedFib(5); + testFib = CachedFib(defaultSize); } - } - TEST_F(TestFib, TestBasic) { - ASSERT_EQ(getFib(5), 3) << "5th fibonacci number must be 3!"; - } + ~TestFib() override + { + // Clean up after a test + } + }; - // more tests + TEST_F(TestFib, TestBasic) { ASSERT_EQ(testFib.getFib(5), 3) << "5th fibonacci number must be 3!"; } + // more tests ``` @@ -96,29 +143,6 @@ when sending data across an ocean. Unfortunately, there does not seem to be a ea but here are the [C++ basics](https://developers.google.com/protocol-buffers/docs/cpptutorial){target=_blank}. The page is quite dense and can be hard to follow, so do not worry if you do not understand it. -## Clang - -In its most basic form, [Clang](https://clang.llvm.org/){target=_blank} is a compiler for the C language family. -Clang has multiple -benefits like easier portability compared to, for example, GCC. Clang is actually "half" the compiler, the other half -being LLVM. Without going into unnecessary detail, Clang compiles C++ code to a generic language before LLVM compiles -it to machine specific language. - -### Clangd - -[Clangd](https://clangd.llvm.org/){target=_blank} is the Clang language server. It provides a much more powerful -intellisense than the default one used in VSCode's C/C++ extension. - -### Clang-Tidy - -[Clang-Tidy](https://clang.llvm.org/extra/clang-tidy/){target=_blank} is a linting tool, who's main purpose is to catch potential -programming errors caused by bad programming style/practices using just static analysis. - -### Clang Format - -An autoformatting tool that makes enforcing style guidelines much easier. When se tup, it corrects formatting as soon -as you hit save. - ## llvm-cov We will use [llvm-cov](https://llvm.org/docs/CommandGuide/llvm-cov.html){target=_blank} to evaluate our test coverage. diff --git a/mkdocs.yml b/mkdocs.yml index 20e5cc78..5b10ac6c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -138,6 +138,7 @@ nav: - Overview: current/local_pathfinding/overview.md - Network Systems: - Overview: current/network_systems/overview.md + - Repository Structure: current/network_systems/repo_structure.md - Website: - Overview: current/website/overview.md - Reference: