diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..6c44c3a8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,86 @@ +# C++ files +Language: Cpp +BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: true +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakInheritanceList: BeforeComma +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: true +ColumnLimit: 120 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +FixNamespaceComments: true +IncludeBlocks: Regroup +IndentCaseLabels: true +IndentPPDirectives: BeforeHash +IndentWrappedFunctionNames: true +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: Inner +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 9614f60d..7fdfc6df 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -5,14 +5,22 @@ name: Node.js CI on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] + +# Ensure that only one instance of the workflow is run at a time for each branch/tag. +# Jobs currently running on the branch/tag will be cancelled when new commits are pushed. +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency. +concurrency: + # `github.workflow` is the workflow name, `github.ref` is the current branch/tag identifier + group: ${{ format('{0}:{1}', github.workflow, github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: build: - runs-on: ${{ matrix.os }} + continue-on-error: true strategy: matrix: @@ -21,19 +29,23 @@ jobs: node-version: [18.x, 20.x] steps: - # Install node-gyp - - name: Install node-gyp - run: npm install -g node-gyp - - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run build --if-present - # Run the tests up to 5 times if it fails (to avoid the error "Network error when sending - # the announce packet: Bad file descriptor" that occurs sometimes on startup) - # TODO: investigate and fix the actual issue. - - shell: bash - run: ./tests/run-with-retries.sh + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + + - name: Install node-gyp + run: npm install -g node-gyp + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build --if-present + + - name: Run tests + run: npm test diff --git a/.github/workflows/publish-to-npm.yml b/.github/workflows/publish-to-npm.yml index 48027306..ddf41eb8 100644 --- a/.github/workflows/publish-to-npm.yml +++ b/.github/workflows/publish-to-npm.yml @@ -8,20 +8,24 @@ jobs: publish_to_npm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout Code + uses: actions/checkout@v3 with: ref: main + # Installs Node and sets up up the .npmrc file to publish to npm - - uses: actions/setup-node@v2 + - uses: actions/setup-node@v4 with: - node-version: '16.x' - registry-url: 'https://registry.npmjs.org' - - run: npm ci - # Run the tests up to 5 times if it fails (to avoid the error "Network error when sending - # the announce packet: Bad file descriptor" that occurs sometimes on startup) - # TODO: investigate and fix the actual issue. - - shell: bash - run: ./tests/run-with-retries.sh - - run: node .github/auto-publish-action.js + node-version: "18.x" + registry-url: "https://registry.npmjs.org" + + - name: "Install Dependencies" + run: npm ci + + - name: "Test Code" + run: npm test + + - name: "Publish to npm" + run: node .github/auto-publish-action.js env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index d16866a2..b91cb5bc 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ build/Release # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules build + +# # Mac OS X clutter +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..2d0a6152 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.formatOnSave": true, + "editor.tabSize": 4, + "editor.insertSpaces": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.eol": "\n", + "editor.rulers": [120], + "files.associations": { + "*.ipp": "cpp" + } +} diff --git a/binding.gyp b/binding.gyp index 026a2669..40807470 100644 --- a/binding.gyp +++ b/binding.gyp @@ -8,13 +8,14 @@ 'src/NetworkListener.cpp', 'src/nuclear/src/extension/network/NUClearNetwork.cpp', 'src/nuclear/src/util/platform.cpp', - 'src/nuclear/src/util/serialise/xxhash.c', - 'src/nuclear/src/util/network/get_interfaces.cpp' + 'src/nuclear/src/util/network/get_interfaces.cpp', + 'src/nuclear/src/util/network/if_number_from_address.cpp', + 'src/nuclear/src/util/network/resolve.cpp', + 'src/nuclear/src/util/serialise/xxhash.cpp' ], 'cflags': [], 'include_dirs': [ '=10.20 || >=12.17" + } + }, + "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { + "dependencies": { "file-uri-to-path": "1.0.0" } }, - "dequal": { + "node_modules/dequal": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "diff": { + "node_modules/diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.3.1" + } }, - "file-uri-to-path": { + "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, - "kleur": { + "node_modules/kleur": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz", "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "mri": { + "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", - "dev": true - }, - "napi-thread-safe-callback": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/napi-thread-safe-callback/-/napi-thread-safe-callback-0.0.6.tgz", - "integrity": "sha512-X7uHCOCdY4u0yamDxDrv3jF2NtYc8A1nvPzBQgvpoSX+WB3jAe2cVNsY448V1ucq7Whf9Wdy02HEUoLW5rJKWg==" + "dev": true, + "engines": { + "node": ">=4" + } }, - "node-addon-api": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-4.2.0.tgz", - "integrity": "sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q==" + "node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==" }, - "sade": { + "node_modules/sade": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", "dev": true, - "requires": { + "dependencies": { "mri": "^1.1.0" + }, + "engines": { + "node": ">= 6" } }, - "totalist": { + "node_modules/totalist": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "uvu": { + "node_modules/uvu": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.2.tgz", "integrity": "sha512-m2hLe7I2eROhh+tm3WE5cTo/Cv3WQA7Oc9f7JB6uWv+/zVKvfAm53bMyOoGOSZeQ7Ov2Fu9pLhFr7p07bnT20w==", "dev": true, - "requires": { + "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3", "totalist": "^2.0.0" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" } } } diff --git a/package.json b/package.json index 60b7a77e..2b84a74c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nuclearnet.js", - "version": "1.6.1", + "version": "1.7.0", "description": "Node.js module for interacting with the NUClear network", "main": "index.js", "types": "index.d.ts", @@ -32,7 +32,6 @@ }, "dependencies": { "bindings": "^1.5.0", - "napi-thread-safe-callback": "0.0.6", - "node-addon-api": "^4.2.0" + "node-addon-api": "^7.0.0" } } diff --git a/src/NetworkBinding.cpp b/src/NetworkBinding.cpp index 83817bdb..67842275 100644 --- a/src/NetworkBinding.cpp +++ b/src/NetworkBinding.cpp @@ -16,14 +16,16 @@ */ #include "NetworkBinding.hpp" + #include "NetworkListener.hpp" -#include "nuclear/src/util/serialise/xxhash.h" +#include "nuclear/src/util/serialise/xxhash.hpp" namespace NUClear { using extension::network::NUClearNetwork; +using util::serialise::xxhash64; -NetworkBinding::NetworkBinding(const Napi::CallbackInfo& info): Napi::ObjectWrap(info) {} +NetworkBinding::NetworkBinding(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) {} Napi::Value NetworkBinding::Hash(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); @@ -31,11 +33,10 @@ Napi::Value NetworkBinding::Hash(const Napi::CallbackInfo& info) { if (info.Length() > 0 && info[0].IsString()) { // Calculate hash std::string s = info[0].As().Utf8Value(); - uint64_t hash = XXH64(s.c_str(), s.size(), 0x4e55436c); + uint64_t hash = xxhash64(s.c_str(), s.size(), 0x4e55436c); // Return hash - return - Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)).As(); + return Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)).As(); } else { Napi::TypeError::New(env, "Invalid input for hash(): expected a string").ThrowAsJavaScriptException(); @@ -51,21 +52,23 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { return; } - const Napi::Value& arg_hash = info[0]; - const Napi::Value& arg_payload = info[1]; - const Napi::Value& arg_target = info[2]; + const Napi::Value& arg_hash = info[0]; + const Napi::Value& arg_payload = info[1]; + const Napi::Value& arg_target = info[2]; const Napi::Value& arg_reliable = info[3]; uint64_t hash = 0; - std::vector payload; + std::vector payload; std::string target = ""; bool reliable = false; // Read reliability information if (arg_reliable.IsBoolean()) { reliable = arg_reliable.As().Value(); - } else { - Napi::TypeError::New(env, "Invalid `reliable` option for send(): expected a boolean").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New(env, "Invalid `reliable` option for send(): expected a boolean") + .ThrowAsJavaScriptException(); return; } @@ -75,49 +78,56 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { } // Otherwise, we accept null and undefined to mean everybody else if (!arg_target.IsUndefined() && !arg_target.IsNull()) { - Napi::TypeError::New(env, "Invalid `target` option for send(): expected a string (for targeted), or null/undefined (for untargeted)").ThrowAsJavaScriptException(); + Napi::TypeError::New( + env, + "Invalid `target` option for send(): expected a string (for targeted), or null/undefined (for untargeted)") + .ThrowAsJavaScriptException(); return; } // Read the data information if (arg_payload.IsTypedArray()) { Napi::TypedArray typed_array = arg_payload.As(); - Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); + Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); - char* data = reinterpret_cast(buffer.Data()); + char* data = reinterpret_cast(buffer.Data()); char* start = data + typed_array.ByteOffset(); - char* end = start + typed_array.ByteLength(); + char* end = start + typed_array.ByteLength(); payload.insert(payload.begin(), start, end); } else { - Napi::TypeError::New(env, "Invalid `payload` option for send(): expected a Buffer").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Invalid `payload` option for send(): expected a Buffer") + .ThrowAsJavaScriptException(); return; } // If we have a string, apply XXHash to get the hash if (arg_hash.IsString()) { std::string s = arg_hash.As().Utf8Value(); - hash = XXH64(s.c_str(), s.size(), 0x4e55436c); + hash = xxhash64(s.c_str(), s.size(), 0x4e55436c); } // Otherwise try to interpret it as a buffer that contains the hash else if (arg_hash.IsTypedArray()) { Napi::TypedArray typed_array = arg_hash.As(); - Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); + Napi::ArrayBuffer buffer = typed_array.ArrayBuffer(); - uint8_t* data = reinterpret_cast(buffer.Data()); + uint8_t* data = reinterpret_cast(buffer.Data()); uint8_t* start = data + typed_array.ByteOffset(); - uint8_t* end = start + typed_array.ByteLength(); + uint8_t* end = start + typed_array.ByteLength(); if (std::distance(start, end) == 8) { std::memcpy(&hash, start, 8); } else { - Napi::TypeError::New(env, "Invalid `hash` option for send(): provided Buffer length is not 8").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Invalid `hash` option for send(): provided Buffer length is not 8") + .ThrowAsJavaScriptException(); return; } - } else { - Napi::TypeError::New(env, "Invalid `hash` option for send(): expected a string or Buffer").ThrowAsJavaScriptException(); + } + else { + Napi::TypeError::New(env, "Invalid `hash` option for send(): expected a string or Buffer") + .ThrowAsJavaScriptException(); return; } @@ -130,130 +140,92 @@ void NetworkBinding::Send(const Napi::CallbackInfo& info) { } } -void NetworkBinding::On(const Napi::CallbackInfo& info) { +void NetworkBinding::OnPacket(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - if (info[0].IsString() && info[1].IsFunction()) { - std::string event = info[0].As().Utf8Value(); - - // `ThreadSafeCallback` is from the napi-thread-safe-callback npm package. It allows for running a JS callback - // from a thread that isn't the main addon thread (like where the NUClearNet packet callbacks are called from). - // Similar thread-safe callback functionality has been added to NAPI natively, but it's still experimental at - // the time of writing. - auto cb = std::make_shared(info[1].As()); - - if (event == "packet") { - this->net.set_packet_callback([cb = std::move(cb)]( - const NUClearNetwork::NetworkTarget& t, const uint64_t& hash, const bool& reliable, std::vector&& payload) { - - std::string name = t.name; - std::string address; - uint16_t port = 0; - - // Extract the IP address and port - char c[255]; - std::memset(c, 0, sizeof(c)); - switch (t.target.sock.sa_family) { - case AF_INET: - inet_ntop(t.target.sock.sa_family, const_cast(&t.target.ipv4.sin_addr), c, sizeof(c)); - port = ntohs(t.target.ipv4.sin_port); - break; - - case AF_INET6: - inet_ntop( - t.target.sock.sa_family, const_cast(&t.target.ipv6.sin6_addr), c, sizeof(c)); - port = ntohs(t.target.ipv6.sin6_port); - break; - } - address = c; - - cb->call([name, address, port, reliable, hash, payload](Napi::Env env, std::vector &args) { - args = { - Napi::String::New(env, name), - Napi::String::New(env, address), - Napi::Number::New(env, port), - Napi::Boolean::New(env, reliable), - Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)), - Napi::Buffer::Copy(env, payload.data(), payload.size()) - }; + // Function to execute on the network thread + on_packet = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnPacket", 0, 1); + + this->net.set_packet_callback([this](const NUClearNetwork::NetworkTarget& t, + const uint64_t& hash, + const bool& reliable, + std::vector&& payload) { + std::string name = t.name; + std::pair addr = t.target.address(); + on_packet.BlockingCall( + [name, addr, hash, reliable, p = std::move(payload)](Napi::Env env, Napi::Function js_callback) { + js_callback.Call({ + Napi::String::New(env, name), + Napi::String::New(env, addr.first), + Napi::Number::New(env, addr.second), + Napi::Boolean::New(env, reliable), + Napi::Buffer::Copy(env, reinterpret_cast(&hash), sizeof(uint64_t)), + Napi::Buffer::Copy(env, p.data(), p.size()), }); }); - } - else if (event == "join" || event == "leave") { - auto f = [cb = std::move(cb)](const NUClearNetwork::NetworkTarget& t) { - std::string name = t.name; - std::string address; - uint16_t port = 0; - - // Extract the IP address and port - char c[255]; - std::memset(c, 0, sizeof(c)); - switch (t.target.sock.sa_family) { - case AF_INET: - inet_ntop(t.target.sock.sa_family, const_cast(&t.target.ipv4.sin_addr), c, sizeof(c)); - port = ntohs(t.target.ipv4.sin_port); - break; - - case AF_INET6: - inet_ntop( - t.target.sock.sa_family, const_cast(&t.target.ipv6.sin6_addr), c, sizeof(c)); - port = ntohs(t.target.ipv6.sin6_port); - break; - - default: - // The system has a corrupted network peer record, but we can't throw to JS from here, since we - // don't have an env object. cb->callError(string) is available from the - // napi-thread-safe-callback library, but that requires changing the callback signature on the - // JS side to accept a potential error as the first argument. This would be a breaking change, - // but we can do it if deemed necessary, and update all users of nuclearnet.js. - return; - } - address = c; - - cb->call([name, address, port](Napi::Env env, std::vector &args) { - args = { - Napi::String::New(env, name), - Napi::String::New(env, address), - Napi::Number::New(env, port), - }; - }); - }; - - if (event == "join") { - this->net.set_join_callback(std::move(f)); - } - else { - this->net.set_leave_callback(std::move(f)); - } - } - else if (event == "wait") { - this->net.set_next_event_callback([cb = std::move(cb)](std::chrono::steady_clock::time_point t) { - using namespace std::chrono; - int ms = duration_cast>(t - steady_clock::now()).count(); - ms++; // Add 1 to account for any funky rounding - - cb->call([ms](Napi::Env env, std::vector &args) { - args = {Napi::Number::New(env, ms)}; - }); + }); +} + +void NetworkBinding::OnJoin(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Function to execute on the network thread + on_join = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnJoin", 0, 1); + + this->net.set_join_callback([this](const NUClearNetwork::NetworkTarget& t) { + std::string name = t.name; + std::pair addr = t.target.address(); + on_join.BlockingCall([name, addr](Napi::Env env, Napi::Function js_callback) { + js_callback.Call({ + Napi::String::New(env, name), + Napi::String::New(env, addr.first), + Napi::Number::New(env, addr.second), }); - } - else { - Napi::TypeError::New(env, "Invalid `eventName` argument for on(): expected one of 'packet', 'join', 'leave', or 'wait'").ThrowAsJavaScriptException(); - return; - } - } - else { - Napi::TypeError::New(env, "Invalid arguments for on(): expected an event name (string) and a callback (function)").ThrowAsJavaScriptException(); - } + }); + }); +} + +void NetworkBinding::OnLeave(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Function to execute on the network thread + on_leave = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnLeave", 0, 1); + + this->net.set_leave_callback([this](const NUClearNetwork::NetworkTarget& t) { + std::string name = t.name; + std::pair addr = t.target.address(); + on_leave.BlockingCall([name, addr](Napi::Env env, Napi::Function js_callback) { + js_callback.Call({ + Napi::String::New(env, name), + Napi::String::New(env, addr.first), + Napi::Number::New(env, addr.second), + }); + }); + }); +} + +void NetworkBinding::OnWait(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + // Function to execute on the network thread + on_wait = Napi::ThreadSafeFunction::New(env, info[0].As(), "OnWait", 0, 1); + + this->net.set_next_event_callback([this](const std::chrono::steady_clock::time_point& t) { + using namespace std::chrono; + // Add 1 to account for any funky rounding + int ms = 1 + duration_cast>(t - steady_clock::now()).count(); + on_wait.BlockingCall( + [ms](Napi::Env env, Napi::Function js_callback) { js_callback.Call({Napi::Number::New(env, ms)}); }); + }); } void NetworkBinding::Reset(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); - const Napi::Value& arg_name = info[0]; + const Napi::Value& arg_name = info[0]; const Napi::Value& arg_group = info[1]; - const Napi::Value& arg_port = info[2]; - const Napi::Value& arg_mtu = info[3]; + const Napi::Value& arg_port = info[2]; + const Napi::Value& arg_mtu = info[3]; std::string name = ""; std::string group = "239.226.152.162"; @@ -265,7 +237,8 @@ void NetworkBinding::Reset(const Napi::CallbackInfo& info) { group = arg_group.As().Utf8Value(); } else { - Napi::TypeError::New(env, "Invalid `group` option for reset(): multicast group must be a string").ThrowAsJavaScriptException(); + Napi::TypeError::New(env, "Invalid `group` option for reset(): multicast group must be a string") + .ThrowAsJavaScriptException(); return; } @@ -333,24 +306,55 @@ void NetworkBinding::Destroy(const Napi::CallbackInfo& info) { WSASetEvent(this->listenerNotifier); #endif - // Replace the ThreadSafeCallback instances to clean up the extra threads they created - this->net.set_packet_callback([](const NUClearNetwork::NetworkTarget& t, const uint64_t& hash, const bool& reliable, std::vector&& payload) {}); + // Create empty lambdas for the callbacks, to prevent them from being called + this->net.set_packet_callback([](const NUClearNetwork::NetworkTarget& t, + const uint64_t& hash, + const bool& reliable, + std::vector&& payload) {}); this->net.set_join_callback([](const NUClearNetwork::NetworkTarget& t) {}); this->net.set_leave_callback([](const NUClearNetwork::NetworkTarget& t) {}); this->net.set_next_event_callback([](std::chrono::steady_clock::time_point t) {}); + + // Release the thread safe functions + on_packet.Release(); + on_join.Release(); + on_leave.Release(); + on_wait.Release(); } void NetworkBinding::Init(Napi::Env env, Napi::Object exports) { - Napi::Function func = - DefineClass(env, - "NetworkBinding", - {InstanceMethod<&NetworkBinding::Send>("send", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::On>("on", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Reset>("reset", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Process>("process", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Shutdown>("shutdown", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Hash>("hash", static_cast(napi_writable | napi_configurable)), - InstanceMethod<&NetworkBinding::Destroy>("destroy", static_cast(napi_writable | napi_configurable))}); + Napi::Function func = DefineClass(env, + "NetworkBinding", + {InstanceMethod<&NetworkBinding::Send>( + "send", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnPacket>( + "onPacket", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnJoin>( + "onJoin", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnLeave>( + "onLeave", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::OnWait>( + "onWait", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Reset>( + "reset", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Process>( + "process", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Shutdown>( + "shutdown", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Hash>( + "hash", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&NetworkBinding::Destroy>( + "destroy", + static_cast(napi_writable | napi_configurable))}); Napi::FunctionReference* constructor = new Napi::FunctionReference(); diff --git a/src/NetworkBinding.hpp b/src/NetworkBinding.hpp index 8b36da02..4d1340a9 100644 --- a/src/NetworkBinding.hpp +++ b/src/NetworkBinding.hpp @@ -18,10 +18,10 @@ #ifndef NETWORKBINDING_H #define NETWORKBINDING_H -#include "nuclear/src/extension/network/NUClearNetwork.hpp" -#include "napi-thread-safe-callback.hpp" #include +#include "nuclear/src/extension/network/NUClearNetwork.hpp" + namespace NUClear { class NetworkBinding : public Napi::ObjectWrap { @@ -30,7 +30,10 @@ class NetworkBinding : public Napi::ObjectWrap { Napi::Value Hash(const Napi::CallbackInfo& info); void Send(const Napi::CallbackInfo& info); - void On(const Napi::CallbackInfo& info); + void OnPacket(const Napi::CallbackInfo& info); + void OnJoin(const Napi::CallbackInfo& info); + void OnLeave(const Napi::CallbackInfo& info); + void OnWait(const Napi::CallbackInfo& info); void Reset(const Napi::CallbackInfo& info); void Process(const Napi::CallbackInfo& info); void Shutdown(const Napi::CallbackInfo& info); @@ -38,6 +41,10 @@ class NetworkBinding : public Napi::ObjectWrap { extension::network::NUClearNetwork net; bool destroyed = false; + Napi::ThreadSafeFunction on_packet; + Napi::ThreadSafeFunction on_join; + Napi::ThreadSafeFunction on_leave; + Napi::ThreadSafeFunction on_wait; #ifdef _WIN32 WSAEVENT listenerNotifier; diff --git a/src/NetworkListener.cpp b/src/NetworkListener.cpp index f6be0134..91c421dd 100644 --- a/src/NetworkListener.cpp +++ b/src/NetworkListener.cpp @@ -21,7 +21,7 @@ namespace NUClear { NetworkListener::NetworkListener(Napi::Env& env, NetworkBinding* binding) -: Napi::AsyncProgressWorker(env), binding(binding) { + : Napi::AsyncProgressWorker(env), binding(binding) { std::vector notifyfds = this->binding->net.listen_fds(); #ifdef _WIN32 @@ -29,9 +29,7 @@ NetworkListener::NetworkListener(Napi::Env& env, NetworkBinding* binding) for (auto& fd : notifyfds) { auto event = WSACreateEvent(); if (event == WSA_INVALID_EVENT) { - throw std::system_error(WSAGetLastError(), - std::system_category(), - "WSACreateEvent() for notify fd failed"); + throw std::system_error(WSAGetLastError(), std::system_category(), "WSACreateEvent() for notify fd failed"); } if (WSAEventSelect(fd, event, FD_READ | FD_CLOSE) == SOCKET_ERROR) { @@ -61,7 +59,8 @@ NetworkListener::~NetworkListener() { #ifdef _WIN32 for (auto& event : this->events) { if (!WSACloseEvent(event)) { - std::cerr << "[NUClearNet.js NetworkListener] WSACloseEvent() failed, error code " << WSAGetLastError() << std::endl; + std::cerr << "[NUClearNet.js NetworkListener] WSACloseEvent() failed, error code " << WSAGetLastError() + << std::endl; } } #endif @@ -76,28 +75,30 @@ void NetworkListener::Execute(const Napi::AsyncProgressWorker::ExecutionPr #ifdef _WIN32 // Wait for events and check for shutdown - auto event_index = WSAWaitForMultipleEvents(this->events.size(), this->events.data(), false, WSA_INFINITE, false); + auto event_index = + WSAWaitForMultipleEvents(this->events.size(), this->events.data(), false, WSA_INFINITE, false); // Check if the return value is an event in our list if (event_index >= WSA_WAIT_EVENT_0 && event_index < WSA_WAIT_EVENT_0 + this->events.size()) { // Get the signalled event - auto& event = this->events[event_index - WSA_WAIT_EVENT_0]; + auto& event = this->events[event_index - WSA_WAIT_EVENT_0]; if (event == this->notifier) { // Reset the notifier signal if (!WSAResetEvent(event)) { - throw std::system_error( - WSAGetLastError(), std::system_category(), "WSAResetEvent() for notifier failed"); + throw std::system_error(WSAGetLastError(), + std::system_category(), + "WSAResetEvent() for notifier failed"); } - } else { + } + else { // Get the corresponding fd for the event auto& fd = this->fds[event_index - WSA_WAIT_EVENT_0]; // Enumumerate the socket events to work out which ones fired WSANETWORKEVENTS wsne; if (WSAEnumNetworkEvents(fd, event, &wsne) == SOCKET_ERROR) { - throw std::system_error( - WSAGetLastError(), std::system_category(), "WSAEnumNetworkEvents() failed"); + throw std::system_error(WSAGetLastError(), std::system_category(), "WSAEnumNetworkEvents() failed"); } // Exit the run loop if the fd was closed @@ -128,16 +129,20 @@ void NetworkListener::Execute(const Napi::AsyncProgressWorker::ExecutionPr // Notify the system something happened if we're running and have data to read. // Will trigger OnProgress() below to read the data. if (run && data) { - // Should really be `p.Signal()` here, but it has a bug at the moment - // See https://github.com/nodejs/node-addon-api/issues/1081 - p.Send(nullptr, 0); + p.Signal(); } } } void NetworkListener::OnProgress(const char*, size_t) { // If we're here in OnProgress(), then there's data to process - this->binding->net.process(); + try { + this->binding->net.process(); + } + catch (const std::exception&) { + // We can't throw to javascript as we are in another thread + // Still we don't want to crash the process so swallow the exception + } } void NetworkListener::OnOK() {} diff --git a/src/NetworkListener.hpp b/src/NetworkListener.hpp index c5c5d4e5..5f222088 100644 --- a/src/NetworkListener.hpp +++ b/src/NetworkListener.hpp @@ -18,9 +18,10 @@ #ifndef NETWORKLISTENER_H #define NETWORKLISTENER_H -#include "NetworkBinding.hpp" #include +#include "NetworkBinding.hpp" + namespace NUClear { class NetworkListener : public Napi::AsyncProgressWorker { diff --git a/src/nuclear/.clang-format b/src/nuclear/.clang-format index c89e85dd..6c44c3a8 100644 --- a/src/nuclear/.clang-format +++ b/src/nuclear/.clang-format @@ -1,38 +1,47 @@ ---- # C++ files Language: Cpp BasedOnStyle: Google AccessModifierOffset: -4 AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: false AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true -AllowAllParametersOfDeclarationOnNextLine: true -AllowShortBlocksOnASingleLine: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty -AllowShortIfStatementsOnASingleLine: true +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false -AlwaysBreakTemplateDeclarations: true +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false -BreakBeforeBinaryOperators: NonAssignment -BreakBeforeBraces: Custom BraceWrapping: - AfterClass: false + AfterCaseLabel: false + AfterClass: false AfterControlStatement: false - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterStruct: false - AfterUnion: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterStruct: false + AfterUnion: false AfterExternBlock: false - BeforeCatch: true - BeforeElse: true - IndentBraces: false -BreakBeforeInheritanceComma: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakInheritanceList: BeforeComma BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakStringLiterals: true @@ -46,7 +55,7 @@ DerivePointerAlignment: false FixNamespaceComments: true IncludeBlocks: Regroup IndentCaseLabels: true -IndentPPDirectives: AfterHash +IndentPPDirectives: BeforeHash IndentWrappedFunctionNames: true IndentWidth: 4 KeepEmptyLinesAtTheStartOfBlocks: true @@ -57,9 +66,14 @@ ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false @@ -70,4 +84,3 @@ SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 4 UseTab: Never -... diff --git a/src/nuclear/.clang-tidy b/src/nuclear/.clang-tidy index 76d4c714..6be676c2 100644 --- a/src/nuclear/.clang-tidy +++ b/src/nuclear/.clang-tidy @@ -1,87 +1,70 @@ ---- -Checks: "clang-analyzer-*\ -,cppcoreguidelines-*\ -,-cppcoreguidelines-pro-type-union-access\ -,-cppcoreguidelines-pro-type-reinterpret-cast\ -,-cppcoreguidelines-pro-type-vararg\ -,-cppcoreguidelines-pro-bounds-pointer-arithmetic\ -,-cppcoreguidelines-pro-bounds-constant-array-index\ -,cert-err60-cpp\ -,cert-flp30-c\ -,google-readability-todo\ -,google-runtime-member-string-references\ -,google-runtime-memset\ -,google-readability-casting\ -,llvm-header-guard\ -,llvm-namespace-comment\ -,misc-*\ -,modernize-*\ -,performance-*\ -,readability-*" -WarningsAsErrors: '' -HeaderFilterRegex: '' +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-implicit-widening-of-multiplication-result, + cert-*, + clang-diagnostic-*, + clang-analyzer-*, + cppcoreguidelines-*, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-owning-memory, + -cppcoreguidelines-pro-bounds-array-to-pointer-decay, + -cppcoreguidelines-pro-bounds-constant-array-index, + -cppcoreguidelines-pro-bounds-pointer-arithmetic, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-type-union-access, + -cppcoreguidelines-pro-type-vararg, + google-*, + -google-explicit-constructor, + -google-readability-casting, + -google-readability-function-size, + -google-runtime-references, + llvm-namespace-comment, + misc-*, + -misc-non-private-member-variables-in-classes, + -misc-no-recursion, + performance-*, + readability-*, + -readability-function-cognitive-complexity, + -readability-function-size, + -readability-identifier-length, + -readability-magic-numbers, + -readability-uppercase-literal-suffix, + modernize-*, + -modernize-use-trailing-return-type, + -modernize-use-emplace +WarningsAsErrors: "" +HeaderFilterRegex: ".*" AnalyzeTemporaryDtors: false +FormatStyle: file CheckOptions: - - key: llvm-header-guard.HeaderFileExtensions - value: ',h,hh,hpp,hxx' - - key: llvm-namespace-comment.ShortNamespaceLines - value: '1' - - key: llvm-namespace-comment.SpacesBeforeComments - value: '2' - - key: misc-definitions-in-headers.HeaderFileExtensions - value: ',h,hh,hpp,hxx,ipp' - - key: misc-definitions-in-headers.UseHeaderFileExtension - value: '1' - - key: misc-move-constructor-init.IncludeStyle - value: google - - key: modernize-loop-convert.NamingStyle - value: lower_case - - key: modernize-pass-by-value.IncludeStyle - value: google - - key: modernize-replace-auto-ptr.IncludeStyle - value: google - - key: performance-for-range-copy.WarnOnAllAutoCopies - value: '1' - - key: performance-type-promotion-in-math-fn.IncludeStyle - value: google - - key: performance-unnecessary-value-param.IncludeStyle - value: google - - key: readability-braces-around-statements.ShortStatementLines - value: '1' - - key: readability-identifier-naming.ClassCase - value: CamelCase - - key: readability-identifier-naming.ConstantCase - value: UPPER_CASE - - key: readability-identifier-naming.EnumCase - value: CamelCase - - key: readability-identifier-naming.EnumConstantCase - value: UPPER_CASE - - key: readability-identifier-naming.FunctionCase - value: lower_case - - key: readability-identifier-naming.MacroDefinitionCase - value: UPPER_CASE - - key: readability-identifier-naming.MemberCase - value: lower_case - - key: readability-identifier-naming.MethodCase - value: lower_case - - key: readability-identifier-naming.NamespaceCase - value: aNy_CasE - - key: readability-identifier-naming.ParameterCase - value: lower_case - - key: readability-identifier-naming.ParameterPackCase - value: lower_case - - key: readability-identifier-naming.StructCase - value: CamelCase - - key: readability-identifier-naming.TemplateParameterCase - value: CamelCase - - key: readability-identifier-naming.TypeAliasCase - value: CamelCase - - key: readability-identifier-naming.TypedefCase - value: CamelCase - - key: readability-identifier-naming.UnionCase - value: CamelCase - - key: readability-identifier-naming.ValueTemplateParameterCase - value: lower_case - - key: readability-identifier-naming.VariableCase - value: lower_case -... + - key: cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues + value: "0.5;1.0;2.0;3.0;4.0;100.0;" + - key: readability-magic-numbers.IgnoredFloatingPointValues + value: "0.5;1.0;2.0;3.0;4.0;100.0;" + - key: cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues + value: "1;2;3;4;" + - key: readability-magic-numbers.IgnoredIntegerValues + value: "1;2;3;4;" + - key: llvm-namespace-comment.ShortNamespaceLines + value: '1' + - key: llvm-namespace-comment.SpacesBeforeComments + value: '2' + - key: misc-move-constructor-init.IncludeStyle + value: google + - key: modernize-loop-convert.NamingStyle + value: lower_case + - key: modernize-pass-by-value.IncludeStyle + value: google + - key: modernize-replace-auto-ptr.IncludeStyle + value: google + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: '1' + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: google + - key: performance-unnecessary-value-param.IncludeStyle + value: google + - key: readability-braces-around-statements.ShortStatementLines + value: '1' diff --git a/src/nuclear/.cmake-format b/src/nuclear/.cmake-format deleted file mode 100644 index 242171f5..00000000 --- a/src/nuclear/.cmake-format +++ /dev/null @@ -1,46 +0,0 @@ -# How wide to allow formatted cmake files -line_width: 120 - -# How many spaces to tab for indent -tab_size: 2 - -# If arglists are longer than this, break them always -max_subargs_per_line: 5 - -# If true, separate flow control names from their parentheses with a space -separate_ctrl_name_with_space: False - -# If true, separate function names from parentheses with a space -separate_fn_name_with_space: False - -# If a statement is wrapped to more than one line, than dangle the closing -# parenthesis on it's own line -dangle_parens: True - -# What style line endings to use in the output. -line_ending: unix - -# Format command names consistently as 'lower' or 'upper' case -command_case: lower - -# Format keywords consistently as 'lower' or 'upper' case -keyword_case: upper - -# Setup all the additional commands -additional_commands: - header_library: - kwargs: - NAME: "*" - HEADER: "*" - PATH_SUFFIX: "*" - URL: "*" - header_library: - kwargs: - NAME: "*" - HEADER: "*" - LIBRARY: "*" - PATH_SUFFIX: "*" - BINARY: "*" - VERSION_FILE: "*" - VERSION_BINARY_ARGUMENTS: "*" - VERSION_REGEX: "*" diff --git a/src/nuclear/.cmake-format.py b/src/nuclear/.cmake-format.py new file mode 100644 index 00000000..521da4e0 --- /dev/null +++ b/src/nuclear/.cmake-format.py @@ -0,0 +1,218 @@ +# ---------------------------------- +# Options affecting listfile parsing +# ---------------------------------- +with section("parse"): + + # Specify structure for custom cmake functions + additional_commands = { + "HeaderLibrary": {"kwargs": {"NAME": "*", "HEADER": "*", "PATH_SUFFIX": "*", "URL": "*"}}, + } + + # Specify variable tags. + vartags = [] + + # Specify property tags. + proptags = [] + +# ----------------------------- +# Options affecting formatting. +# ----------------------------- +with section("format"): + + # How wide to allow formatted cmake files + line_width = 120 + + # How many spaces to tab for indent + tab_size = 2 + + # If an argument group contains more than this many sub-groups (parg or kwarg + # groups) then force it to a vertical layout. + max_subgroups_hwrap = 2 + + # If a positional argument group contains more than this many arguments, then + # force it to a vertical layout. + max_pargs_hwrap = 6 + + # If a cmdline positional group consumes more than this many lines without + # nesting, then invalidate the layout (and nest) + max_rows_cmdline = 2 + + # If true, separate flow control names from their parentheses with a space + separate_ctrl_name_with_space = False + + # If true, separate function names from parentheses with a space + separate_fn_name_with_space = False + + # If a statement is wrapped to more than one line, than dangle the closing + # parenthesis on its own line. + dangle_parens = True + + # If the trailing parenthesis must be 'dangled' on its on line, then align it + # to this reference: `prefix`: the start of the statement, `prefix-indent`: + # the start of the statement, plus one indentation level, `child`: align to + # the column of the arguments + dangle_align = "prefix" + + # If the statement spelling length (including space and parenthesis) is + # smaller than this amount, then force reject nested layouts. + min_prefix_chars = 4 + + # If the statement spelling length (including space and parenthesis) is larger + # than the tab width by more than this amount, then force reject un-nested + # layouts. + max_prefix_chars = 10 + + # If a candidate layout is wrapped horizontally but it exceeds this many + # lines, then reject the layout. + max_lines_hwrap = 2 + + # What style line endings to use in the output. + line_ending = "unix" + + # Format command names consistently as 'lower' or 'upper' case + command_case = "canonical" + + # Format keywords consistently as 'lower' or 'upper' case + keyword_case = "upper" + + # If true, the argument lists which are known to be sortable will be sorted + # lexicographicall + enable_sort = True + + # If true, the parsers may infer whether or not an argument list is sortable + # (without annotation). + autosort = True + + # By default, if cmake-format cannot successfully fit everything into the + # desired linewidth it will apply the last, most agressive attempt that it + # made. If this flag is True, however, cmake-format will print error, exit + # with non-zero status code, and write-out nothing + require_valid_layout = False + + # A dictionary mapping layout nodes to a list of wrap decisions. See the + # documentation for more information. + layout_passes = {} + +# ------------------------------------------------ +# Options affecting comment reflow and formatting. +# ------------------------------------------------ +with section("markup"): + + # What character to use for bulleted lists + bullet_char = "*" + + # What character to use as punctuation after numerals in an enumerated list + enum_char = "." + + # If comment markup is enabled, don't reflow the first comment block in each + # listfile. Use this to preserve formatting of your copyright/license + # statements. + first_comment_is_literal = False + + # If comment markup is enabled, don't reflow any comment block which matches + # this (regex) pattern. Default is `None` (disabled). + literal_comment_pattern = None + + # Regular expression to match preformat fences in comments default= + # ``r'^\s*([`~]{3}[`~]*)(.*)$'`` + fence_pattern = "^\\s*([`~]{3}[`~]*)(.*)$" + + # Regular expression to match rulers in comments default= + # ``r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'`` + ruler_pattern = "^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$" + + # If a comment line matches starts with this pattern then it is explicitly a + # trailing comment for the preceeding argument. Default is '#<' + explicit_trailing_pattern = "#<" + + # If a comment line starts with at least this many consecutive hash + # characters, then don't lstrip() them off. This allows for lazy hash rulers + # where the first hash char is not separated by space + hashruler_min_length = 10 + + # If true, then insert a space between the first hash char and remaining hash + # chars in a hash ruler, and normalize its length to fill the column + canonicalize_hashrulers = True + + # enable comment markup parsing and reflow + enable_markup = False + +# ---------------------------- +# Options affecting the linter +# ---------------------------- +with section("lint"): + + # a list of lint codes to disable + disabled_codes = [] + + # regular expression pattern describing valid function names + function_pattern = "[0-9a-z_]+" + + # regular expression pattern describing valid macro names + macro_pattern = "[0-9A-Z_]+" + + # regular expression pattern describing valid names for variables with global + # scope + global_var_pattern = "[0-9A-Z][0-9A-Z_]+" + + # regular expression pattern describing valid names for variables with global + # scope (but internal semantic) + internal_var_pattern = "_[0-9A-Z][0-9A-Z_]+" + + # regular expression pattern describing valid names for variables with local + # scope + local_var_pattern = "[0-9a-z_]+" + + # regular expression pattern describing valid names for privatedirectory + # variables + private_var_pattern = "_[0-9a-z_]+" + + # regular expression pattern describing valid names for publicdirectory + # variables + public_var_pattern = "[0-9A-Z][0-9A-Z_]+" + + # regular expression pattern describing valid names for keywords used in + # functions or macros + keyword_pattern = "[0-9A-Z_]+" + + # In the heuristic for C0201, how many conditionals to match within a loop in + # before considering the loop a parser. + max_conditionals_custom_parser = 2 + + # Require at least this many newlines between statements + min_statement_spacing = 1 + + # Require no more than this many newlines between statements + max_statement_spacing = 1 + max_returns = 6 + max_branches = 12 + max_arguments = 5 + max_localvars = 15 + max_statements = 50 + +# ------------------------------- +# Options affecting file encoding +# ------------------------------- +with section("encode"): + + # If true, emit the unicode byte-order mark (BOM) at the start of the file + emit_byteorder_mark = False + + # Specify the encoding of the input file. Defaults to utf-8 + input_encoding = "utf-8" + + # Specify the encoding of the output file. Defaults to utf-8. Note that cmake + # only claims to support utf-8 so be careful when using anything else + output_encoding = "utf-8" + +# ------------------------------------- +# Miscellaneous configurations options. +# ------------------------------------- +with section("misc"): + + # A dictionary containing any per-command configuration overrides. Currently + # only `command_case` is supported. + per_command = { + "HeaderLibrary": {"command_case": "unchanged"}, + "ToolchainLibraryFinder": {"command_case": "unchanged"}, + } diff --git a/src/nuclear/.github/dependabot.yaml b/src/nuclear/.github/dependabot.yaml new file mode 100644 index 00000000..54779793 --- /dev/null +++ b/src/nuclear/.github/dependabot.yaml @@ -0,0 +1,9 @@ +# Required fields are `version` and `updates` +version: 2 +updates: + # Required fields are `package-ecosystem`, `directory`, and `schedule.interval` + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/src/nuclear/.github/workflows/gcc.yaml b/src/nuclear/.github/workflows/gcc.yaml new file mode 100644 index 00000000..4bec64c0 --- /dev/null +++ b/src/nuclear/.github/workflows/gcc.yaml @@ -0,0 +1,101 @@ +# Continuous Integration tests +name: GCC + +on: + push: + branches: [main] + pull_request: + branches: [main] + +# Ensure that only one instance of the workflow is run at a time for each branch/tag. +# Jobs currently running on the branch/tag will be cancelled when new commits are pushed. +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency. +concurrency: + # `github.workflow` is the workflow name, `github.ref` is the current branch/tag identifier + group: ${{ format('{0}:{1}', github.workflow, github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + linux-gcc: + strategy: + matrix: + toolchain: + - container: ubuntu:22.04 + version: "13" + - container: ubuntu:22.04 + version: "12" + - container: ubuntu:22.04 + version: "11" + - container: ubuntu:22.04 + version: "10" + - container: ubuntu:22.04 + version: "9" + - container: ubuntu:20.04 + version: "8" + - container: ubuntu:20.04 + version: "7" + - container: ubuntu:18.04 + version: "6" + - container: ubuntu:18.04 + version: "5" + + name: Linux GCC-${{ matrix.toolchain.version }} + runs-on: ubuntu-latest + continue-on-error: true + + # Use the container for this specific version of gcc + container: ${{ matrix.toolchain.container }} + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + # Update for all the actions that need to install stuff + - run: | + apt-get update + apt-get install -y software-properties-common + + - name: Install GCC + run: | + add-apt-repository ppa:ubuntu-toolchain-r/test + apt-get update + apt-get install -y gcc-${{ matrix.toolchain.version }} g++-${{ matrix.toolchain.version }} + + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.27.1 + ninjaVersion: 1.11.1 + + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }}-gcc-${{ matrix.toolchain.version }} + max-size: 100M + + - name: Configure CMake + run: | + cmake -E make_directory build + cmake -S . -B build \ + -GNinja \ + -DCMAKE_C_COMPILER=/usr/bin/gcc-${{ matrix.toolchain.version }} \ + -DCMAKE_CXX_COMPILER=/usr/bin/g++-${{ matrix.toolchain.version }} \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DBUILD_TESTS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCI_BUILD=ON \ + -DENABLE_CLANG_TIDY=OFF + + - name: Build + timeout-minutes: 30 + run: cmake --build build --config Release + + - name: CCache Stats + run: ccache --show-stats + + - name: Test + timeout-minutes: 10 + run: | + build/tests/test_nuclear + for f in build/tests/individual/*; do echo "Testing $f"; ./$f; done diff --git a/src/nuclear/.github/workflows/linting.yaml b/src/nuclear/.github/workflows/linting.yaml new file mode 100644 index 00000000..b7f93371 --- /dev/null +++ b/src/nuclear/.github/workflows/linting.yaml @@ -0,0 +1,69 @@ +name: Linting + +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [main] + pull_request: + branches: [main] + +# Ensure that only one instance of the workflow is run at a time for each branch/tag. +# Jobs currently running on the branch/tag will be cancelled when new commits are pushed. +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency. +concurrency: + # `github.workflow` is the workflow name, `github.ref` is the current branch/tag identifier + group: ${{ format('{0}:{1}', github.workflow, github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + linux-clang-tidy: + name: Linux Clang-Tidy + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install clang-tidy-15 + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" | sudo tee /etc/apt/sources.list.d/llvm-15 + echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-15 main" | sudo tee -a /etc/apt/sources.list.d/llvm-15 + sudo apt-get update + sudo apt-get install -y clang-tidy-15 + + # Download and install cmake + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.27.1 + ninjaVersion: 1.11.1 + + # Download and setup ccache + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }} + max-size: 100M + + - name: Configure CMake + run: | + cmake -E make_directory build + cmake -S . -B build \ + -GNinja \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DBUILD_TESTS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCI_BUILD=ON \ + -DENABLE_CLANG_TIDY=ON + + - name: Build + timeout-minutes: 30 + # Execute the build. You can specify a specific target with "--target " + run: cmake --build build --config Release --parallel 2 + + - name: CCache Stats + run: ccache --show-stats diff --git a/src/nuclear/.github/workflows/macos.yaml b/src/nuclear/.github/workflows/macos.yaml new file mode 100644 index 00000000..c9028379 --- /dev/null +++ b/src/nuclear/.github/workflows/macos.yaml @@ -0,0 +1,63 @@ +# Continuous Integration tests +name: MacOS + +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [main] + pull_request: + branches: [main] + +# Ensure that only one instance of the workflow is run at a time for each branch/tag. +# Jobs currently running on the branch/tag will be cancelled when new commits are pushed. +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency. +concurrency: + # `github.workflow` is the workflow name, `github.ref` is the current branch/tag identifier + group: ${{ format('{0}:{1}', github.workflow, github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + macos-latest: + name: MacOS Latest + runs-on: macos-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }} + max-size: 100M + + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.27.1 + ninjaVersion: 1.11.1 + + - name: Configure CMake + run: | + cmake -E make_directory build + cmake -S . -B build \ + -GNinja \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DBUILD_TESTS=ON \ + -DCMAKE_BUILD_TYPE=Release \ + -DCI_BUILD=ON \ + -DENABLE_CLANG_TIDY=OFF + + - name: Build + timeout-minutes: 30 + run: cmake --build build --config Release + + - name: CCache Stats + run: ccache --show-stats + + - name: Test + timeout-minutes: 10 + run: | + build/tests/test_nuclear + for f in build/tests/individual/*; do echo "Testing $f"; ./$f; done diff --git a/src/nuclear/.github/workflows/main.yaml b/src/nuclear/.github/workflows/main.yaml deleted file mode 100644 index fa31b53c..00000000 --- a/src/nuclear/.github/workflows/main.yaml +++ /dev/null @@ -1,108 +0,0 @@ -# Continuous Integration tests -name: CI - -# Controls when the action will run. -on: - # Triggers the workflow on push or pull request events but only for the main branch - push: - branches: [main] - pull_request: - branches: [main] - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - build-linux: - name: Linux GCC - - strategy: - matrix: - container: ['gcc:4.9', 'gcc:5', 'gcc:7', 'gcc:9', 'gcc:10'] - - # The type of runner that the job will run on - runs-on: ubuntu-latest - - # Use the container for this specific version of gcc - container: ${{ matrix.container }} - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout Code - uses: actions/checkout@v2 - - # Download and install cmake - - name: Install CMake v3.19.1 - uses: lukka/get-cmake@v3.19.1 - - - name: Configure CMake - run: | - cmake -E make_directory build - cmake -S . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release - - - name: Build - # Execute the build. You can specify a specific target with "--target " - run: cmake --build build --config Release --parallel 2 - - - name: Test - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: | - build/tests/test_nuclear - for f in build/tests/individual/*; do echo "Testing $f"; ./$f; done - - build-osx: - name: MacOS Clang - - # The type of runner that the job will run on - runs-on: macos-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout Code - uses: actions/checkout@v2 - - - name: Configure CMake - run: | - cmake -E make_directory build - cmake -S . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release - - - name: Build - # Execute the build. You can specify a specific target with "--target " - run: cmake --build build --config Release --parallel 2 - - - name: Test - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: | - build/tests/test_nuclear - for f in build/tests/individual/*; do echo "Testing $f"; ./$f; done - - build-windows: - name: Windows MSVC - - # The type of runner that the job will run on - runs-on: windows-latest - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout Code - uses: actions/checkout@v2 - - - name: Configure CMake - run: | - cmake -E make_directory build - cmake -S . -B build -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release - - - name: Build - # Execute the build. You can specify a specific target with "--target " - run: cmake --build build --config Release --parallel 2 - - - name: Test - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: | - build/tests/Release/test_nuclear.exe - for f in build/tests/individual/Release/*; do echo "Testing $f"; ./$f; done - shell: bash diff --git a/src/nuclear/.github/workflows/sonarcloud.yaml b/src/nuclear/.github/workflows/sonarcloud.yaml new file mode 100644 index 00000000..9f0a1d84 --- /dev/null +++ b/src/nuclear/.github/workflows/sonarcloud.yaml @@ -0,0 +1,80 @@ +name: Sonar +on: + push: + branches: + - main + - sonar + pull_request: + types: [opened, synchronize, reopened] + +# Ensure that only one instance of the workflow is run at a time for each branch/tag. +# Jobs currently running on the branch/tag will be cancelled when new commits are pushed. +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency. +concurrency: + # `github.workflow` is the workflow name, `github.ref` is the current branch/tag identifier + group: ${{ format('{0}:{1}', github.workflow, github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + sonarcloud: + name: SonarCloud + runs-on: ubuntu-latest + env: + BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: Install gcovr + run: pip install gcovr==6.0 + + - name: Install sonar-scanner and build-wrapper + uses: SonarSource/sonarcloud-github-c-cpp@v2 + + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.27.1 + ninjaVersion: 1.11.1 + + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }} + max-size: 100M + + - name: Configure CMake + run: | + cmake -E make_directory build + cmake -S . -B build \ + -GNinja \ + -DCMAKE_CXX_FLAGS="--coverage" \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DBUILD_TESTS=ON \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCI_BUILD=ON \ + -DENABLE_CLANG_TIDY=OFF + + - name: Build with Sonar Wrapper + timeout-minutes: 30 + run: build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} cmake --build build/ --config Debug + + - name: Run tests to generate coverage statistics + timeout-minutes: 10 + run: | + build/tests/test_nuclear + for f in build/tests/individual/*; do echo "Testing $f"; ./$f; done + + - name: Collect coverage into one XML report + run: gcovr --exclude-unreachable-branches --exclude-noncode-lines --sonarqube > coverage.xml + + - name: Run sonar-scanner + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + sonar-scanner \ + --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" \ + --define sonar.coverageReportPaths=coverage.xml diff --git a/src/nuclear/.github/workflows/windows.yaml b/src/nuclear/.github/workflows/windows.yaml new file mode 100644 index 00000000..500b9ea7 --- /dev/null +++ b/src/nuclear/.github/workflows/windows.yaml @@ -0,0 +1,85 @@ +# Continuous Integration tests +name: Windows + +on: + push: + branches: [main] + pull_request: + branches: [main] + +# Ensure that only one instance of the workflow is run at a time for each branch/tag. +# Jobs currently running on the branch/tag will be cancelled when new commits are pushed. +# See https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#concurrency. +concurrency: + # `github.workflow` is the workflow name, `github.ref` is the current branch/tag identifier + group: ${{ format('{0}:{1}', github.workflow, github.ref) }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + windows-latest: + name: Windows Latest ${{ matrix.toolchain.name }} + + strategy: + matrix: + toolchain: + - name: MSVC + c: cl + cxx: cl + # Code does not compile on GCC on windows + # - name: GCC + # c: gcc + # cxx: g++ + + runs-on: windows-latest + continue-on-error: true + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Setup CCache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{ github.job }}-${{ matrix.toolchain.name }} + max-size: 100M + variant: sccache + + - name: Install CMake + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.27.1 + ninjaVersion: 1.11.1 + + # This lets ninja find MSVC + - name: Add MSVC to path + uses: ilammy/msvc-dev-cmd@v1 + + - name: Configure CMake + shell: cmd + run: | + cmake -E make_directory build + cmake -S . -B build ^ + -GNinja ^ + -DCMAKE_C_COMPILER=${{ matrix.toolchain.c }} ^ + -DCMAKE_CXX_COMPILER=${{ matrix.toolchain.cxx }} ^ + -DCMAKE_C_COMPILER_LAUNCHER=sccache ^ + -DCMAKE_CXX_COMPILER_LAUNCHER=sccache ^ + -DBUILD_TESTS=ON ^ + -DCMAKE_BUILD_TYPE=Release ^ + -DCI_BUILD=ON ^ + -DENABLE_CLANG_TIDY=OFF + + - name: Build + timeout-minutes: 30 + run: cmake --build build --config Release + + - name: SCCache Stats + run: sccache --show-stats + + - name: Test + timeout-minutes: 10 + shell: bash + run: | + build/tests/test_nuclear.exe + for f in build/tests/individual/*; do echo "Testing $f"; ./$f; done diff --git a/src/nuclear/.readthedocs.yaml b/src/nuclear/.readthedocs.yaml new file mode 100644 index 00000000..9712e405 --- /dev/null +++ b/src/nuclear/.readthedocs.yaml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# We recommend specifying your dependencies to enable reproducible builds: +# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: docs/requirements.txt diff --git a/src/nuclear/.vscode/settings.json b/src/nuclear/.vscode/settings.json index 85cddfce..b4761bf5 100644 --- a/src/nuclear/.vscode/settings.json +++ b/src/nuclear/.vscode/settings.json @@ -1,17 +1,97 @@ { - "cmake.generator": "Ninja", - "editor.formatOnSave": true, - "editor.tabSize": 4, - "editor.insertSpaces": true, - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true, - "files.eol": "\n", - "C_Cpp.clang_format_style": "file", - "C_Cpp.default.cppStandard": "c++14", - "C_Cpp.preferredPathSeparator": "Forward Slash", - "editor.rulers": [ - 120 - ], - "cmake.configureOnOpen": true, - "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools" + "cmake.generator": "Ninja", + "editor.formatOnSave": true, + "editor.tabSize": 4, + "editor.insertSpaces": true, + "files.insertFinalNewline": true, + "files.trimFinalNewlines": true, + "files.eol": "\n", + "C_Cpp.clang_format_style": "file", + "C_Cpp.default.cppStandard": "c++14", + "C_Cpp.preferredPathSeparator": "Forward Slash", + "editor.rulers": [120], + "cmake.configureOnOpen": true, + "C_Cpp.default.configurationProvider": "vector-of-bool.cmake-tools", + "files.associations": { + "*.ipp": "cpp", + "__bit_reference": "cpp", + "__bits": "cpp", + "__config": "cpp", + "__debug": "cpp", + "__errc": "cpp", + "__hash_table": "cpp", + "__locale": "cpp", + "__mutex_base": "cpp", + "__node_handle": "cpp", + "__nullptr": "cpp", + "__split_buffer": "cpp", + "__string": "cpp", + "__threading_support": "cpp", + "__tree": "cpp", + "__tuple": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "exception": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "map": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "numeric": "cpp", + "optional": "cpp", + "ostream": "cpp", + "queue": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "set": "cpp", + "sstream": "cpp", + "stack": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "string": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "variant": "cpp", + "vector": "cpp", + "algorithm": "cpp", + "__verbose_abort": "cpp", + "thread": "cpp" + } } diff --git a/src/nuclear/CMakeLists.txt b/src/nuclear/CMakeLists.txt index 8b01536f..e5e0299a 100644 --- a/src/nuclear/CMakeLists.txt +++ b/src/nuclear/CMakeLists.txt @@ -1,20 +1,33 @@ -# Copyright (C) 2013 Trent Houliston , Jake Woods 2014-2017 Trent -# Houliston -# -# 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. - -cmake_minimum_required(VERSION 3.1.0) +#[[ +MIT License + +Copyright (c) 2013 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] + +cmake_minimum_required(VERSION 3.15.0) + +# Set the project after the build type as the Project command can change the build type +project( + NUClear + VERSION 1.0.0 + LANGUAGES C CXX +) # We use additional modules that cmake needs to know about set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") @@ -27,13 +40,16 @@ endif() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${default_build_type}' as none was specified.") - set(CMAKE_BUILD_TYPE ${default_build_type} CACHE STRING "Choose the type of build." FORCE) + set(CMAKE_BUILD_TYPE + ${default_build_type} + CACHE STRING "Choose the type of build." FORCE + ) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() -# Set the project after the build type as the Project command can change the build type -project(NUClear VERSION 1.0.0 LANGUAGES C CXX) +# NUClear targets c++14 +set(CMAKE_CXX_STANDARD 14) # Determine if NUClear is built as a subproject (using add_subdirectory) or if it is the master project. set(MASTER_PROJECT OFF) @@ -41,6 +57,56 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) set(MASTER_PROJECT ON) endif() +if(MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -pedantic) +endif(MSVC) + +# If this option is set we are building using continous integration +option(CI_BUILD "Enable build options for building in the CI server" OFF) + +# Default not to run the clang-tidy checks, default to whatever our CI_BUILD is +option(ENABLE_CLANG_TIDY "Enable building with clang-tidy checks.") +if(ENABLE_CLANG_TIDY) + # Search for clang-tidy-15 first as this is the version installed in CI + find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy-15 clang-tidy) + if(NOT CLANG_TIDY_EXECUTABLE) + message(FATAL_ERROR "clang-tidy-15 not found.") + endif() + + # Report clang-tidy executable details + execute_process(COMMAND "${CLANG_TIDY_EXECUTABLE}" "--version" OUTPUT_VARIABLE CLANG_TIDY_VERSION) + string(REGEX REPLACE ".*LLVM version ([0-9.]*).*" "\\1" CLANG_TIDY_VERSION "${CLANG_TIDY_VERSION}") + message(STATUS "Found clang-tidy: ${CLANG_TIDY_EXECUTABLE} ${CLANG_TIDY_VERSION}") + + # Build clang-tidy command line + set(CLANG_TIDY_ARGS "${CLANG_TIDY_EXECUTABLE}" "--use-color" "--config-file=${PROJECT_SOURCE_DIR}/.clang-tidy") + if(CI_BUILD) + set(CLANG_TIDY_ARGS "${CLANG_TIDY_EXECUTABLE}" "-warnings-as-errors=*") + endif(CI_BUILD) + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_ARGS}" "--extra-arg=-std=c++14") + set(CMAKE_C_CLANG_TIDY "${CLANG_TIDY_ARGS}" "--extra-arg=-std=c99") +endif(ENABLE_CLANG_TIDY) + +# If we are doing a CI build then we want to enable -Werror when compiling warnings are bad. We will also make it fail +# if clang-tidy has an error +if(CI_BUILD) + if(CMAKE_CXX_COMPILER_ID MATCHES MSVC) + add_compile_options(/WX) + else() + add_compile_options(-Werror) + endif() +endif(CI_BUILD) + +# Make the compiler display colours always (even when we build with ninja) +if(CMAKE_CXX_COMPILER_ID MATCHES GNU) + add_compile_options(-fdiagnostics-color=always) +endif() +if(CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-fcolor-diagnostics) +endif() + # Add the src directory add_subdirectory(src) diff --git a/src/nuclear/LICENSE b/src/nuclear/LICENSE index 7ce680cf..02cd0a76 100644 --- a/src/nuclear/LICENSE +++ b/src/nuclear/LICENSE @@ -1,5 +1,4 @@ -Copyright (C) 2013 Trent Houliston , Jake Woods - 2014-2017 Trent Houliston +Copyright (C) 2013-2023 NUClear Contributors 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: diff --git a/src/nuclear/README.md b/src/nuclear/README.md index 34b74e04..8978133a 100644 --- a/src/nuclear/README.md +++ b/src/nuclear/README.md @@ -8,7 +8,7 @@ It is highly extensible and provides several attachment points to develop new DS These metaprograms reduce the cost of routing messages between modules resulting in a much faster system. -For help getting started check the [wiki](https://github.com/Fastcode/NUClear/wiki) +For help getting started check the [docs](https://nuclear.readthedocs.io/en/latest/quick_start.html) If you're starting a new project using NUClear the [NUClear Roles system](https://github.com/Fastcode/NUClearRoles) is highly recommended @@ -26,6 +26,6 @@ make install ``` ### Dependencies -* g++ 4.9, clang (with c++14 support) or Visual Studio 2015 +* g++ 5.0+, clang (with c++14 support) or Visual Studio 2015 * cmake 3.1.0 * [Catch Unit Testing Framework](https://github.com/philsquared/Catch) for building tests diff --git a/src/nuclear/cmake/Modules/FindCATCH.cmake b/src/nuclear/cmake/Modules/FindCATCH.cmake index c35c86cb..4f6181c3 100644 --- a/src/nuclear/cmake/Modules/FindCATCH.cmake +++ b/src/nuclear/cmake/Modules/FindCATCH.cmake @@ -1,21 +1,29 @@ -# Copyright (C) 2013-2016 Trent Houliston -# -# 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. +#[[ +MIT License + +Copyright (c) 2013 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] include(HeaderLibrary) -header_library( +HeaderLibrary( NAME CATCH - HEADER catch.hpp URL "https://raw.githubusercontent.com/catchorg/Catch2/v2.x/single_include/catch2/catch.hpp" + HEADER catch.hpp + URL "https://raw.githubusercontent.com/catchorg/Catch2/v2.x/single_include/catch2/catch.hpp" ) diff --git a/src/nuclear/cmake/Modules/FindSphinx.cmake b/src/nuclear/cmake/Modules/FindSphinx.cmake index 22397379..e7f3584d 100644 --- a/src/nuclear/cmake/Modules/FindSphinx.cmake +++ b/src/nuclear/cmake/Modules/FindSphinx.cmake @@ -1,18 +1,24 @@ -# Copyright (C) 2013 Trent Houliston , Jake Woods 2014-2017 Trent -# Houliston -# -# 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. +#[[ +MIT License + +Copyright (c) 2013 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] include(ToolchainLibraryFinder) -toolchainlibraryfinder(NAME Sphinx BINARY sphinx-build) +ToolchainLibraryFinder(NAME Sphinx BINARY sphinx-build) diff --git a/src/nuclear/cmake/Modules/HeaderLibrary.cmake b/src/nuclear/cmake/Modules/HeaderLibrary.cmake index 7b0d1e39..6fd31852 100644 --- a/src/nuclear/cmake/Modules/HeaderLibrary.cmake +++ b/src/nuclear/cmake/Modules/HeaderLibrary.cmake @@ -1,20 +1,27 @@ -# Copyright (C) 2013-2016 Trent Houliston -# -# 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. +#[[ +MIT License + +Copyright (c) 2013 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] include(CMakeParseArguments) -function(header_library) +function(HeaderLibrary) # Extract the arguments from our function call set(options, "") set(oneValueArgs "NAME") @@ -62,22 +69,25 @@ function(header_library) # Setup and export our variables set(required_vars ${required_vars} "${PACKAGE_NAME}_INCLUDE_DIR") - set(${PACKAGE_NAME}_INCLUDE_DIRS ${${PACKAGE_NAME}_INCLUDE_DIR} PARENT_SCOPE) + set(${PACKAGE_NAME}_INCLUDE_DIRS + ${${PACKAGE_NAME}_INCLUDE_DIR} + PARENT_SCOPE + ) mark_as_advanced(${PACKAGE_NAME}_INCLUDE_DIR ${PACKAGE_NAME}_INCLUDE_DIRS) # Find the package include(FindPackageHandleStandardArgs) find_package_handle_standard_args( ${PACKAGE_NAME} - FOUND_VAR - ${PACKAGE_NAME}_FOUND - REQUIRED_VARS - ${required_vars} - VERSION_VAR - ${PACKAGE_NAME}_VERSION + FOUND_VAR ${PACKAGE_NAME}_FOUND + REQUIRED_VARS ${required_vars} + VERSION_VAR ${PACKAGE_NAME}_VERSION ) # Export our found variable to parent scope - set(${PACKAGE_NAME}_FOUND ${PACKAGE_NAME}_FOUND PARENT_SCOPE) + set(${PACKAGE_NAME}_FOUND + ${PACKAGE_NAME}_FOUND + PARENT_SCOPE + ) -endfunction(header_library) +endfunction(HeaderLibrary) diff --git a/src/nuclear/cmake/Modules/ToolchainLibraryFinder.cmake b/src/nuclear/cmake/Modules/ToolchainLibraryFinder.cmake index 7ab6aa10..17e7314d 100644 --- a/src/nuclear/cmake/Modules/ToolchainLibraryFinder.cmake +++ b/src/nuclear/cmake/Modules/ToolchainLibraryFinder.cmake @@ -1,18 +1,24 @@ -# Copyright (C) 2013 Trent Houliston , Jake Woods 2014-2017 Trent -# Houliston -# -# 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. +#[[ +MIT License + +Copyright (c) 2013 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] include(CMakeParseArguments) function(ToolchainLibraryFinder) @@ -20,15 +26,14 @@ function(ToolchainLibraryFinder) # Extract the arguments from our function call set(options, "") set(oneValueArgs "NAME") - set( - multiValueArgs - "HEADER" - "LIBRARY" - "PATH_SUFFIX" - "BINARY" - "VERSION_FILE" - "VERSION_BINARY_ARGUMENTS" - "VERSION_REGEX" + set(multiValueArgs + "HEADER" + "LIBRARY" + "PATH_SUFFIX" + "BINARY" + "VERSION_FILE" + "VERSION_BINARY_ARGUMENTS" + "VERSION_REGEX" ) cmake_parse_arguments(PACKAGE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) @@ -48,7 +53,10 @@ function(ToolchainLibraryFinder) # Setup and export our variables set(required_vars ${required_vars} "${PACKAGE_NAME}_INCLUDE_DIR") - set(${PACKAGE_NAME}_INCLUDE_DIRS ${${PACKAGE_NAME}_INCLUDE_DIR} PARENT_SCOPE) + set(${PACKAGE_NAME}_INCLUDE_DIRS + ${${PACKAGE_NAME}_INCLUDE_DIR} + PARENT_SCOPE + ) mark_as_advanced(${PACKAGE_NAME}_INCLUDE_DIR ${PACKAGE_NAME}_INCLUDE_DIRS) endif(PACKAGE_HEADER) @@ -64,7 +72,10 @@ function(ToolchainLibraryFinder) # Setup and export our variables set(required_vars ${required_vars} "${PACKAGE_NAME}_LIBRARY") - set(${PACKAGE_NAME}_LIBRARIES ${${PACKAGE_NAME}_LIBRARY} PARENT_SCOPE) + set(${PACKAGE_NAME}_LIBRARIES + ${${PACKAGE_NAME}_LIBRARY} + PARENT_SCOPE + ) mark_as_advanced(${PACKAGE_NAME}_LIBRARY ${PACKAGE_NAME}_LIBRARIES) endif(PACKAGE_LIBRARY) @@ -80,7 +91,10 @@ function(ToolchainLibraryFinder) # Setup and export our variables set(required_vars ${required_vars} "${PACKAGE_NAME}_BINARY") - set(${PACKAGE_NAME}_BINARY ${${PACKAGE_NAME}_BINARY} PARENT_SCOPE) + set(${PACKAGE_NAME}_BINARY + ${${PACKAGE_NAME}_BINARY} + PARENT_SCOPE + ) mark_as_advanced(${PACKAGE_NAME}_BINARY) endif(PACKAGE_BINARY) @@ -97,7 +111,9 @@ function(ToolchainLibraryFinder) # Execute our binary to get a version string if(PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY) exec_program( - ${${PACKAGE_NAME}_BINARY} ARGS ${PACKAGE_VERSION_BINARY_ARGUMENTS} OUTPUT_VARIABLE full_version_string + ${${PACKAGE_NAME}_BINARY} ARGS + ${PACKAGE_VERSION_BINARY_ARGUMENTS} + OUTPUT_VARIABLE full_version_string ) endif(PACKAGE_VERSION_BINARY_ARGUMENTS AND PACKAGE_BINARY) @@ -114,16 +130,15 @@ function(ToolchainLibraryFinder) include(FindPackageHandleStandardArgs) find_package_handle_standard_args( ${PACKAGE_NAME} - FOUND_VAR - ${PACKAGE_NAME}_FOUND - REQUIRED_VARS - ${required_vars} - VERSION_VAR - ${PACKAGE_NAME}_VERSION - # VERSION_VAR "${MAJOR}.${MINOR}.${PATCH}") + FOUND_VAR ${PACKAGE_NAME}_FOUND + REQUIRED_VARS ${required_vars} + VERSION_VAR ${PACKAGE_NAME}_VERSION # VERSION_VAR "${MAJOR}.${MINOR}.${PATCH}") ) # Export our found variable to parent scope - set(${PACKAGE_NAME}_FOUND ${PACKAGE_NAME}_FOUND PARENT_SCOPE) + set(${PACKAGE_NAME}_FOUND + ${PACKAGE_NAME}_FOUND + PARENT_SCOPE + ) endfunction(ToolchainLibraryFinder) diff --git a/src/nuclear/docs/CMakeLists.txt b/src/nuclear/docs/CMakeLists.txt index 0a576e35..41ace683 100644 --- a/src/nuclear/docs/CMakeLists.txt +++ b/src/nuclear/docs/CMakeLists.txt @@ -1,3 +1,24 @@ +#[[ +MIT License + +Copyright (c) 2017 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] option(BUILD_DOCUMENTATION "Create and install the HTML based API documentation (requires Doxygen and Sphinx)" FALSE) if(BUILD_DOCUMENTATION) @@ -12,6 +33,10 @@ if(BUILD_DOCUMENTATION) COMMENT "Generating documentation pages using sphinx" ) - install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DESTINATION share/doc OPTIONAL) + install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DESTINATION share/doc + OPTIONAL + ) endif(BUILD_DOCUMENTATION) diff --git a/src/nuclear/docs/extension.rst b/src/nuclear/docs/extension.rst index dd7ff1bf..f5b2f6ce 100644 --- a/src/nuclear/docs/extension.rst +++ b/src/nuclear/docs/extension.rst @@ -5,46 +5,217 @@ Extension Fusion Engine ************* -TODO what the fusion engine is and how it works +The fusion engine combines a number of templated types into a single "fused" type. This allows it to be treated as a +single type. The fused type, most importantly, exposes the methods described below which it then calls in an iterative +manner for each individual type fused. + +The parse type is used as the interface into the fusion engine, it handles calling the fused type with the correct +template and filling in undefined methods with no-operations. + +Both the fused type and parsed type only has static methods defined, so it would be pointless to instantiate it. + +This means calling `Parse, Sync, Startup>.bind()` will call `Trigger.bind()`, then a no-operation, +then will call `Startup.bind()`, using `DSL = Parse, Sync, Startup>`. + +Fusions can be nested, this can be useful for creating a DSL word that combines the functionality of multiple other +words. Extending the DSL ***************** -TODO What extending the dsl means and what bits there are +The DSL words are types, adding a word is as simple as declaring a new type with at least one of the following static +template methods defined. Each undefined static template method will automatically behave as a no-operation. + +The template of each of these static methods should be `template `, this will have the parsed DSL words +passed in. It is important to note that the type will only be considered by NUClear in a static context. If any +attributes need to be stored in the DSL word type template it and use static variables, see `Sync`. + +There are DSL words that are not meant to be used directly but as a part of other words, see `CacheGet` and `TypeBind`. +`TypeBind` adds the reaction to the list of reactions to be run when a `Local` or `Direct` emit is called for the data +type. `CacheGet` gets the last value from a thread-local cache (see `ThreadSore` below) this cache is usually populated +in the last a `Local` or `Direct` emit call for the data type. + +If the type you want to become a DSL extension word is not defined within your control specialise `DSLProxy<>` with the +type. Provide the template methods to the specialisation of `DSLProxy<>` as if it were the type. Bind ---- -TODO What extension via bind means and how it works +.. codeblock:: c++ + template + static inline void bind(const std::shared_ptr& reaction, /*More arguments can be declared*/) + +This function is called when the reaction is bound, it should be thought of as the constructor. It is used to setup +anything that is required by the DSL word. + +A common use for extensions is to setup something that will generate tasks for this reaction. This can be done by +communicating to an extension reactor via a helper type that the extension reactor triggers on. + +An unbinder, if needed, should be passed to the reaction's unbinders callback list from the bind call. This is used as a +destructor. +e.g. for the `IO` word we have +.. codeblock:: c++ + reaction->unbinders.push_back([](const threading::Reaction& r) { + r.reactor.emit(std::make_unique>(r.id)); + }); + +which will tell the extension reactor that this reaction no longer exists. + +The extra arguments are passed in, in order, from the `on` call. Get --- -TODO what extension via get means and how it works -Is used to get data to run a function with -If the type can be dereferenced, then this is also an acceptable type in the argument list +.. codeblock:: c++ + template + static inline T get(threading::Reaction&) + +This is used to get the data for the callback. The returned value is passed to the callback. + +If the return type can be dereferenced, then either the return type or the type returned by the dereference of the +return type can be used in the callback. + +If data needs to be passed to a task when it is submitted to the `Powerplant` use `ThreadStore`. The thread store is +a static variable that can be accessed from within the get method. Make sure to clear the `ThreadStore` after use to +ensure future invocations won't get stale data. Precondition ------------ -TODO what extension via precondition means and how it works -Used to determine if a reaction should run -if any of the functions return false it doesn't run +.. codeblock:: c++ + template + static inline bool precondition(threading::Reaction& reaction) + +A precondition is used to test if the reaction should run. On a true return the reaction will run as normal. On a false +return the reaction will be dropped. Postcondition ------------- -TODO what extension via postcondition means and how it works -All of these always run after a reaction has finished running +.. codeblock:: c++ + template + static void postcondition(threading::ReactionTask& task) + +This will run after the callback for a reaction task has run and finished. Reschedule ---------- -TODO what extension via reschedule means and how it works -'Steals' reactions so they can be run elsewhere -Can also be used to replace a reaction with a different reaction, or a modified one +.. codeblock:: c++ + template + static inline std::unique_ptr reschedule(std::unique_ptr&& task) + +The ownership of the reaction task is passed to the DSL word. The task returned will be run instead of the passed in +reaction task. If the returned task is the one passed in the task will be run normally. + +If a null pointer is returned, no task is run. + +When it is time to schedule the task either return it in another reschedule call or call +`task.parent.reactor.powerplant.submit(std::move(task));`. Both these will pass the ownership of the task on. + +Transient +--------- + +.. codeblock:: c++ + template <> + struct is_transient : public std::true_type {}; + +When the data returned from a `get` is falsy and its type is marked transient the latest truthy data from the `get` +return is instead used. If the data is falsy and is either not marked transient or nothing truthy has yet been returned +then the reaction is cancelled. + +Custom Emit Handler +******************* + +.. codeblock:: c++ + template + struct EmitType { + static void emit(PowerPlant& powerplant, ...) + }; + +Emit can be extended by creating a template struct that has at least one method called `emit`. This is then called from +a Reactor with `emit` and the arguments will be passed through. + +.. codeblock:: c++ + static void emit(PowerPlant& powerplant, std::shared_ptr data, ...) + +If the second parameter is a shared pointer to the templated type when calling emit a unique pointer will be +automatically converted to a shared pointer. Example Case ************ -TODO take one of the parts (sync maybe?) and explain how it is built using extensions +Sync +---- + +Here, we have an ordinary C++ class. In this case we start by defining the attributes we need in a static context. +The template is used to have multiple static contexts. +.. codeblock:: c++ + template + struct Sync { + + using task_ptr = std::unique_ptr; + + /// @brief our queue which sorts tasks by priority + static std::priority_queue queue; + /// @brief how many tasks are currently running + static volatile bool running; + /// @brief a mutex to ensure data consistency + static std::mutex mutex; + +Now we define the `reschedule` to interrupt any new tasks if we are currently running. Recall that NUClear is +multithreaded so a mutex is needed when accessing the static members. +.. codeblock:: c++ + template + static inline std::unique_ptr reschedule( + std::unique_ptr&& task) { + + // Lock our mutex + std::lock_guard lock(mutex); + + // If we are already running then queue, otherwise return and set running + if (running) { + queue.push(std::move(task)); + return std::unique_ptr(nullptr); + } + else { + running = true; + return std::move(task); + } + } + +To run any queued tasks after the current one is done we define `postcondition`. When there is a task in the queue we +resubmit it to the PowerPlant to be run. +.. codeblock:: c++ + template + static void postcondition(threading::ReactionTask& task) { + + // Lock our mutex + std::lock_guard lock(mutex); + + // We are finished running + running = false; + + // If we have another task, add it + if (!queue.empty()) { + std::unique_ptr next_task( + std::move(const_cast&>(queue.top()))); + queue.pop(); + + // Resubmit this task to the reaction queue + task.parent.reactor.powerplant.submit(std::move(next_task)); + } + } + +We need to instantiate our static members outside the class definition. +.. codeblock:: c++ + }; + template + std::priority_queue::task_ptr> Sync::queue; + + template + volatile bool Sync::running = false; + + template + std::mutex Sync::mutex; + diff --git a/src/nuclear/docs/networking.rst b/src/nuclear/docs/networking.rst index 97fc5bac..f80a561a 100644 --- a/src/nuclear/docs/networking.rst +++ b/src/nuclear/docs/networking.rst @@ -7,4 +7,44 @@ TODO Explain the NUClear network mesh Serialisation ************* -TODO explain how serialisation works and how to serialise your own types +Serialisation is used to convert data to a type that can be written to files or sent over a network connection and be +usable by the receiving device. See `Wikipedia `_ for further information. + +NUClear provides helper functions for dealing with serialisation, from the namespace ``NUClear::util::serialise`` there +are ``Searialise::serialise``, ``Searialise::deserialise`` and ``Searialise::hash`` for serialisation, +deserialisation and hashing the type's name respectively. The functions are used internally by NUClear to try to +serialise/deserialise data sent/received via ``Network`` or ``UDP``. These functions are only defined for specific +types. + +Trivial Data Types +------------------ + +NUClear defines serialisation for `Trivial Types `_ with some +caveats. The serialisation of trivial data is dependant on both the +`endianness `_ and the +`alignment `_ of the data on the computer running the code. + +NUClear also defines serialisation of iterators of trivial data types. + +Google Protobuf Types +--------------------- + +NUClear wraps the serialisation and deserialisation of google +`protobuf `_ types. Use protobuf over trivial data types when +communicating between machines or programs. + +Custom Types +------------ + +To add another type to be able to be serialised add another partial specialisation to ``Serialise`` declaring +the type of ``Check``. The easiest ``Check`` is `is_same `_ as this +checks explicitly for an explicit type. Be careful about multiple declarations. + +For this partial specialisation three static methods need to be defined. + +.. codeblock:: c++ + static inline std::vector serialise(const T& in) + + static inline T deserialise(const std::vector& in) + + static inline uint64_t hash() diff --git a/src/nuclear/docs/startup.rst b/src/nuclear/docs/startup.rst index 72eec4bf..01cc0554 100644 --- a/src/nuclear/docs/startup.rst +++ b/src/nuclear/docs/startup.rst @@ -22,7 +22,7 @@ file for the process. .. code-block:: C++ - NUClear::PowerPlant::Configuration config; + NUClear::Configuration config; config.thread_count = 1; NUClear::PowerPlant plant(config); diff --git a/src/nuclear/sonar-project.properties b/src/nuclear/sonar-project.properties new file mode 100644 index 00000000..60dc88e4 --- /dev/null +++ b/src/nuclear/sonar-project.properties @@ -0,0 +1,9 @@ +sonar.projectName=NUClear +sonar.projectKey=Fastcode_NUClear +sonar.organization=fastcode +sonar.projectVersion=1.0 + +sonar.sources=src +sonar.tests=tests + +sonar.sourceEncoding=UTF-8 diff --git a/src/nuclear/src/CMakeLists.txt b/src/nuclear/src/CMakeLists.txt index 0827a8f1..911a429b 100644 --- a/src/nuclear/src/CMakeLists.txt +++ b/src/nuclear/src/CMakeLists.txt @@ -1,3 +1,25 @@ +#[[ +MIT License + +Copyright (c) 2013 NUClear Contributors + +This file is part of the NUClear codebase. +See https://github.com/Fastcode/NUClear for further info. + +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. +]] + # Configure the two versions of the main header (in source vs nuclear_bits) set(nuclear_include_base_directory "") configure_file(nuclear.in ${PROJECT_BINARY_DIR}/include/nuclear) @@ -13,32 +35,7 @@ add_library(NUClear::nuclear ALIAS nuclear) # Set compile options for NUClear target_link_libraries(nuclear ${CMAKE_THREAD_LIBS_INIT}) set_target_properties(nuclear PROPERTIES POSITION_INDEPENDENT_CODE ON) -target_compile_features( - nuclear - PUBLIC - c_std_99 - cxx_constexpr - cxx_decltype - cxx_deleted_functions - cxx_generic_lambdas - cxx_inheriting_constructors - cxx_lambdas - cxx_lambda_init_captures - cxx_long_long_type - cxx_noexcept - cxx_nonstatic_member_init - cxx_nullptr - cxx_range_for - cxx_return_type_deduction - cxx_right_angle_brackets - cxx_rvalue_references - cxx_static_assert - cxx_thread_local - cxx_trailing_return_types - cxx_uniform_initialization - cxx_variadic_templates - cxx_template_template_parameters -) +target_compile_features(nuclear PUBLIC cxx_std_14) # Enable warnings, and all warnings are errors if(MSVC) @@ -53,23 +50,27 @@ include(CMakePackageConfigHelpers) set(INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_FULL_INCLUDEDIR}) write_basic_package_version_file(${PROJECT_BINARY_DIR}/NUClearConfigVersion.cmake COMPATIBILITY SameMajorVersion) configure_package_config_file( - ${PROJECT_SOURCE_DIR}/cmake/NUClearConfig.cmake.in - ${PROJECT_BINARY_DIR}/NUClearConfig.cmake - INSTALL_DESTINATION - ${CMAKE_INSTALL_LIBDIR}/cmake/NUClear - PATH_VARS - INSTALL_INCLUDE_DIR + ${PROJECT_SOURCE_DIR}/cmake/NUClearConfig.cmake.in ${PROJECT_BINARY_DIR}/NUClearConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/NUClear + PATH_VARS INSTALL_INCLUDE_DIR ) # Install version, config and target files. +install(FILES ${PROJECT_BINARY_DIR}/NUClearConfigVersion.cmake ${PROJECT_BINARY_DIR}/NUClearConfig.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/NUClear +) install( - FILES ${PROJECT_BINARY_DIR}/NUClearConfigVersion.cmake ${PROJECT_BINARY_DIR}/NUClearConfig.cmake + EXPORT nuclear-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/NUClear + NAMESPACE NUClear:: ) -install(EXPORT nuclear-targets DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/NUClear NAMESPACE NUClear::) # Install headers and targets -install(TARGETS nuclear EXPORT nuclear-targets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install( + TARGETS nuclear + EXPORT nuclear-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) install( DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/nuclear_bits @@ -80,4 +81,8 @@ install( ) install(FILES ${PROJECT_BINARY_DIR}/nuclear DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -export(TARGETS nuclear NAMESPACE NUClear:: FILE nuclear-targets.cmake) +export( + TARGETS nuclear + NAMESPACE NUClear:: + FILE nuclear-targets.cmake +) diff --git a/src/nuclear/src/Configuration.hpp b/src/nuclear/src/Configuration.hpp new file mode 100644 index 00000000..c51d678a --- /dev/null +++ b/src/nuclear/src/Configuration.hpp @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2023 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. + * + * 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. + */ + +#ifndef NUCLEAR_CONFIGURATION_HPP +#define NUCLEAR_CONFIGURATION_HPP + +#include +#include + +namespace NUClear { + +/** + * @brief This class holds the configuration for a PowerPlant. + */ +struct Configuration { + /// @brief The number of threads the system will use + size_t thread_count = std::thread::hardware_concurrency() == 0 ? 2 : std::thread::hardware_concurrency(); +}; + +} // namespace NUClear + +#endif // NUCLEAR_CONFIGURATION_HPP diff --git a/src/nuclear/src/Environment.hpp b/src/nuclear/src/Environment.hpp index 69afe8db..5bf2d43d 100644 --- a/src/nuclear/src/Environment.hpp +++ b/src/nuclear/src/Environment.hpp @@ -1,6 +1,10 @@ /* - * Copyright (C) 2013 Trent Houliston , Jake Woods - * 2014-2017 Trent Houliston + * MIT License + * + * Copyright (c) 2013 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. * * 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 @@ -40,8 +44,8 @@ class PowerPlant; */ class Environment { public: - Environment(PowerPlant& powerplant, std::string&& reactor_name, LogLevel log_level) - : powerplant(powerplant), log_level(log_level), reactor_name(reactor_name) {} + Environment(PowerPlant& powerplant, std::string reactor_name) + : powerplant(powerplant), reactor_name(std::move(reactor_name)) {} private: friend class PowerPlant; @@ -49,8 +53,6 @@ class Environment { /// @brief The PowerPlant to use in this reactor PowerPlant& powerplant; - /// @brief The log level for this reactor - LogLevel log_level; /// @brief The name of the reactor std::string reactor_name; }; diff --git a/src/nuclear/src/LogLevel.hpp b/src/nuclear/src/LogLevel.hpp index 2e95c7d9..ebf1494c 100644 --- a/src/nuclear/src/LogLevel.hpp +++ b/src/nuclear/src/LogLevel.hpp @@ -1,6 +1,10 @@ /* - * Copyright (C) 2013 Trent Houliston , Jake Woods - * 2014-2017 Trent Houliston + * MIT License + * + * Copyright (c) 2013 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. * * 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 @@ -35,6 +39,15 @@ namespace NUClear { * The logging level of a reactor can be changed by setting it in the install function. */ enum LogLevel { + /** + * @brief + * Don't use this log level when emitting logs, it is for setting reactor log level from non reactor sources. + * + * Specifically when a NUClear::log is called from code that is not running in a reaction (even transitively) then + * the reactor_level will be set to UNKNOWN. + */ + UNKNOWN, + /** * @brief * The Trace level contains messages that are used to trace the exact flow of execution. diff --git a/src/nuclear/src/PowerPlant.cpp b/src/nuclear/src/PowerPlant.cpp index 9655691e..4e20eb7e 100644 --- a/src/nuclear/src/PowerPlant.cpp +++ b/src/nuclear/src/PowerPlant.cpp @@ -1,6 +1,10 @@ /* - * Copyright (C) 2013 Trent Houliston , Jake Woods - * 2014-2017 Trent Houliston + * MIT License + * + * Copyright (c) 2013 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. * * 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 @@ -18,96 +22,73 @@ #include "PowerPlant.hpp" -#include "threading/ThreadPoolTask.hpp" - namespace NUClear { -PowerPlant* PowerPlant::powerplant = nullptr; // NOLINT +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +PowerPlant* PowerPlant::powerplant = nullptr; PowerPlant::~PowerPlant() { + // Make sure reactors are destroyed before anything else + while (!reactors.empty()) { + reactors.pop_back(); + } // Bye bye powerplant powerplant = nullptr; } -void PowerPlant::on_startup(std::function&& func) { - if (is_running) { throw std::runtime_error("Unable to do on_startup as the PowerPlant has already started"); } - else { - startup_tasks.push_back(func); - } -} - -void PowerPlant::add_thread_task(std::function&& task) { - tasks.push_back(task); -} - void PowerPlant::start() { // We are now running - is_running = true; - - // Run all our Initialise scope tasks - for (auto&& func : startup_tasks) { - func(); - } - startup_tasks.clear(); + is_running.store(true); - // Direct emit startup event + // Direct emit startup event and command line arguments emit(std::make_unique()); + emit_shared(dsl::store::DataStore::get()); - // Start all our threads - for (size_t i = 0; i < configuration.thread_count; ++i) { - tasks.push_back(threading::make_thread_pool_task(scheduler)); - } - - // Start all our tasks - for (auto& task : tasks) { - threads.push_back(std::make_unique(task)); - } + // Start all of the threads + scheduler.start(); +} - // Start our main thread using our main task scheduler - threading::make_thread_pool_task(main_thread_scheduler)(); +void PowerPlant::submit(const NUClear::id_t& id, + const int& priority, + const util::GroupDescriptor& group, + const util::ThreadPoolDescriptor& pool, + const bool& immediate, + std::function&& task) { + scheduler.submit(id, priority, group, pool, immediate, std::move(task)); +} - // Now wait for all the threads to finish executing - for (auto& thread : threads) { +void PowerPlant::submit(std::unique_ptr&& task, const bool& immediate) noexcept { + // Only submit non null tasks + if (task) { try { - if (thread->joinable()) { thread->join(); } + const std::shared_ptr t(std::move(task)); + submit(t->id, t->priority, t->group_descriptor, t->thread_pool_descriptor, immediate, [t]() { t->run(); }); } - // This gets thrown some time if between checking if joinable and joining - // the thread is no longer joinable - catch (const std::system_error&) { + catch (const std::exception& ex) { + task->parent.reactor.log("There was an exception while submitting a reaction", ex.what()); + } + catch (...) { + task->parent.reactor.log("There was an unknown exception while submitting a reaction"); } } } -void PowerPlant::submit(std::unique_ptr&& task) { - scheduler.submit(std::move(task)); -} - -void PowerPlant::submit_main(std::unique_ptr&& task) { - main_thread_scheduler.submit(std::move(task)); -} - void PowerPlant::shutdown() { - // Stop running before we emit events the Shutdown event + // Stop running before we emit the Shutdown event // Some things such as on depend on this flag and it's possible to miss it - is_running = false; + is_running.store(false); // Emit our shutdown event emit(std::make_unique()); // Shutdown the scheduler scheduler.shutdown(); - - // Shutdown the main threads scheduler - main_thread_scheduler.shutdown(); - - // Bye bye powerplant - powerplant = nullptr; } bool PowerPlant::running() const { - return is_running; + return is_running.load(); } } // namespace NUClear diff --git a/src/nuclear/src/PowerPlant.hpp b/src/nuclear/src/PowerPlant.hpp index a06b867d..ca180748 100644 --- a/src/nuclear/src/PowerPlant.hpp +++ b/src/nuclear/src/PowerPlant.hpp @@ -1,6 +1,10 @@ /* - * Copyright (C) 2013 Trent Houliston , Jake Woods - * 2014-2017 Trent Houliston + * MIT License + * + * Copyright (c) 2013 NUClear Contributors + * + * This file is part of the NUClear codebase. + * See https://github.com/Fastcode/NUClear for further info. * * 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 @@ -19,6 +23,7 @@ #ifndef NUCLEAR_POWERPLANT_HPP #define NUCLEAR_POWERPLANT_HPP +#include #include #include #include @@ -32,11 +37,15 @@ #include // Utilities +#include "Configuration.hpp" #include "LogLevel.hpp" +#include "id.hpp" #include "message/LogMessage.hpp" +#include "threading/ReactionTask.hpp" #include "threading/TaskScheduler.hpp" #include "util/FunctionFusion.hpp" #include "util/demangle.hpp" +#include "util/main_thread_id.hpp" #include "util/unpack.hpp" namespace NUClear { @@ -57,48 +66,28 @@ class PowerPlant { friend class Reactor; public: - /** - * @brief This class holds the configuration for a PowerPlant. - * - * @details - * It configures the number of threads that will be in the PowerPlants thread pool - */ - struct Configuration { - /// @brief default to the amount of hardware concurrency (or 2) threads - Configuration() - : thread_count(std::thread::hardware_concurrency() == 0 ? 2 : std::thread::hardware_concurrency()) {} - - /// @brief The number of threads the system will use - size_t thread_count; - }; - - /// @brief Holds the configuration information for this PowerPlant (such as number of pool threads) - const Configuration configuration; - - // There can only be one powerplant, so this is it - static PowerPlant* powerplant; + static PowerPlant* powerplant; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) /** * @brief * Constructs a PowerPlant with the given configuration and provides access * to argv for all reactors. * - * @details - * If PowerPlant is constructed with argc and argv then a CommandLineArguments - * message will be emitted and available to all reactors. + * @details Arguments passed to this function will be emitted as a CommandLineArguments message. * * @param config The PowerPlant's configuration * @param argc The number of command line arguments * @param argv The command line argument strings */ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays) PowerPlant(Configuration config = Configuration(), int argc = 0, const char* argv[] = nullptr); ~PowerPlant(); // There can only be one! - PowerPlant(const PowerPlant& other) = delete; - PowerPlant(const PowerPlant&& other) = delete; - PowerPlant& operator=(const PowerPlant& other) = delete; + PowerPlant(const PowerPlant& other) = delete; + PowerPlant(const PowerPlant&& other) = delete; + PowerPlant& operator=(const PowerPlant& other) = delete; PowerPlant& operator=(const PowerPlant&& other) = delete; /** @@ -123,22 +112,6 @@ class PowerPlant { */ bool running() const; - /** - * @brief Adds a function to the set of startup tasks. - * - * @param func the task being added to the set of startup tasks. - * - * @throws std::runtime_error if the PowerPlant is already running/has already started. - */ - void on_startup(std::function&& func); - - /** - * @brief Adds a function to the set of tasks to be run when the PowerPlant starts up - * - * @param task The function to add to the task list - */ - void add_thread_task(std::function&& task); - /** * @brief Installs a reactor of a particular type to the system. * @@ -148,24 +121,40 @@ class PowerPlant { * in the environment of that reactor so that it can be used to filter logs. * * @tparam T The type of the reactor to build and install + * @tparam Args The types of the extra arguments to pass to the reactor constructor * @tparam level The initial logging level for this reactor to use + * + * @param arg Extra arguments to pass to the reactor constructor + * + * @return A reference to the installed reactor */ - template - void install(); + template + T& install(Args&&... args); /** - * @brief Submits a new task to the ThreadPool to be queued and then executed. + * @brief Generic submit function for submitting tasks to the thread pool. * - * @param task The Reaction task to be executed in the thread pool + * @param id an id for ordering the task + * @param priority the priority of the task between 0 and 1000 + * @param group the details of the execution group this task will run in + * @param pool the details of the thread pool this task will run from + * @param immediate if this task should run immediately in the current thread + * @param task the wrapped function to be executed */ - void submit(std::unique_ptr&& task); + void submit(const NUClear::id_t& id, + const int& priority, + const util::GroupDescriptor& group, + const util::ThreadPoolDescriptor& pool, + const bool& immediate, + std::function&& task); /** - * @brief Submits a new task to the main threads thread pool to be queued and then executed. + * @brief Submits a new task to the ThreadPool to be queued and then executed. * * @param task The Reaction task to be executed in the thread pool + * @param immediate if this task should run immediately in the current thread */ - void submit_main(std::unique_ptr&& task); + void submit(std::unique_ptr&& task, const bool& immediate = false) noexcept; /** * @brief Log a message through NUClear's system. @@ -226,7 +215,7 @@ class PowerPlant { class... Remainder, typename T, typename... Arguments> - void emit_shared(std::shared_ptr&& data, Arguments&&... args); + void emit_shared(std::shared_ptr data, Arguments&&... args); template