diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 41d9f6d..b905ae3 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -23,7 +23,7 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.os }} continue-on-error: true @@ -34,15 +34,14 @@ jobs: submodules: recursive - name: Install Packages (Ubuntu) - run: | - sudo apt-get install -y uuid-dev if: matrix.os == 'ubuntu-latest' + run: sudo apt-get install -y uuid-dev - name: Configure CMake - run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dflow-core_BUILD_TESTS=ON + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dflow-core_BUILD_TESTS=ON -Dflow-core_BUILD_BENCHMARKS=ON - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 18 + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 10 - name: Upload Artifacts uses: actions/upload-artifact@v4 @@ -50,6 +49,68 @@ jobs: name: Test Module (${{matrix.os}}) path: ${{github.workspace}}/build/tests/test_module.fmod + - name: Cache Build Output + uses: actions/cache@v4 + with: + path: ${{github.workspace}}/build + key: ${{matrix.os}}-build-${{github.sha}} + + test: + needs: build + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + + runs-on: ${{ matrix.os }} + continue-on-error: true + + steps: + - uses: actions/checkout@v4 + - name: Restore build output cache + uses: actions/cache@v4 + with: + path: ${{github.workspace}}/build + key: ${{matrix.os}}-build-${{github.sha}} + - name: Test working-directory: ${{github.workspace}}/build run: ctest -C ${{env.BUILD_TYPE}} --output-on-failure + + benchmark: + needs: build + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + + runs-on: ${{ matrix.os }} + continue-on-error: true + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - name: Restore build output cache + uses: actions/cache@v4 + with: + path: ${{github.workspace}}/build + key: ${{matrix.os}}-build-${{github.sha}} + + - name: Benchmark + run: ${{github.workspace}}/build/benchmarks/${{ matrix.os == 'windows-latest' && env.BUILD_TYPE || '' }}/flow_core_bench + + cleanup_cache: + needs: [test, benchmark] + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + + runs-on: ${{ matrix.os }} + continue-on-error: true + permissions: write-all + timeout-minutes: 10 + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + + if: always() # Run even if previous jobs failed + steps: + - name: Delete Cache + run: gh cache delete ${{matrix.os}}-build-${{github.sha}} diff --git a/CMakeLists.txt b/CMakeLists.txt index f208656..c95d0fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.10) +set (CMAKE_POLICY_VERSION_MINIMUM 3.5) + project(flow-core VERSION 1.1.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) @@ -19,6 +21,7 @@ endif() # ----------------------------------------------------------------------------- option(${PROJECT_NAME}_BUILD_TESTS "Build tests (gtest)" OFF) +option(${PROJECT_NAME}_BUILD_BENCHMARKS "Build benchmarks (googlebenchmark)" OFF) option(${PROJECT_NAME}_BUILD_TOOLS "Build tools" OFF) option(${PROJECT_NAME}_INSTALL "Add installation targets" OFF) @@ -152,6 +155,14 @@ if (${PROJECT_NAME}_BUILD_TESTS) add_subdirectory(tests) endif() +# ----------------------------------------------------------------------------- +# Benchmarks +# ----------------------------------------------------------------------------- + +if (${PROJECT_NAME}_BUILD_BENCHMARKS) + add_subdirectory(benchmarks) +endif() + # ----------------------------------------------------------------------------- # Tools # ----------------------------------------------------------------------------- diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt new file mode 100644 index 0000000..7f3b064 --- /dev/null +++ b/benchmarks/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.21) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +#=============================================================================# +# Dependencies +#=============================================================================# + +set(BENCHMARK_USE_BUNDLED_GTEST OFF) +set(BENCHMARK_ENABLE_TESTING OFF) +CPMAddPackage("gh:google/benchmark@1.9.4") + +#=============================================================================# +# Test Executable +#=============================================================================# + +set(BENCH_EXE flow_core_bench) +add_executable( + ${BENCH_EXE} + + event_bench.cpp + graph_bench.cpp + indexable_name_bench.cpp + node_data_bench.cpp + type_name_bench.cpp + uuid_bench.cpp +) + +if(MSVC) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + target_compile_options(${BENCH_EXE} PRIVATE /W4 /MP) +endif() + +target_link_libraries(${BENCH_EXE} PRIVATE + flow-core::flow-core + benchmark::benchmark_main +) + +target_include_directories(${BENCH_EXE} PRIVATE + ${CMAKE_SOURCE_DIR}/include/flow/core + ${thread_pool_SOURCE_DIR}/include +) + +if(MSVC) + add_custom_command(TARGET ${BENCH_EXE} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $ + $ + ) +endif() diff --git a/benchmarks/event_bench.cpp b/benchmarks/event_bench.cpp new file mode 100644 index 0000000..8426804 --- /dev/null +++ b/benchmarks/event_bench.cpp @@ -0,0 +1,49 @@ +// Copyright (c) 2025, Cisco Systems, Inc. +// All rights reserved. + +#include + +#include +#include +#include +#include + +#include +#include + +static void EventDispatcher_BroadcastBrutal(benchmark::State& state) +{ + flow::EventDispatcher<> dispatcher; + std::array names; + + for (int i = 0; i < names.size(); ++i) + { + names[i] = "Event_" + std::to_string(i); + dispatcher.Bind(flow::IndexableName{names[i]}, [&] { ++i; }); + } + + for ([[maybe_unused]] const auto& _ : state) + { + dispatcher.Broadcast(); + } +} + +static void EventDispatcher_BroadcastRealistic(benchmark::State& state) +{ + flow::EventDispatcher<> dispatcher; + std::array names; + + for (int i = 0; i < names.size(); ++i) + { + names[i] = "Event_" + std::to_string(i); + dispatcher.Bind(flow::IndexableName{names[i]}, [&] { ++i; }); + } + + for ([[maybe_unused]] const auto& _ : state) + { + dispatcher.Broadcast(); + } +} + +BENCHMARK(EventDispatcher_BroadcastBrutal); +BENCHMARK(EventDispatcher_BroadcastRealistic); diff --git a/benchmarks/graph_bench.cpp b/benchmarks/graph_bench.cpp new file mode 100644 index 0000000..1e85428 --- /dev/null +++ b/benchmarks/graph_bench.cpp @@ -0,0 +1,23 @@ +// Copyright (c) 2025, Cisco Systems, Inc. +// All rights reserved. + +#include + +#include +#include +#include +#include + +#include + +static void Graph_Construct(benchmark::State& state) +{ + auto factory = flow::NodeFactory::Create(); + auto env = flow::Env::Create(factory); + for ([[maybe_unused]] const auto& _ : state) + { + flow::Graph("benchmark", env); + } +} + +BENCHMARK(Graph_Construct); diff --git a/benchmarks/indexable_name_bench.cpp b/benchmarks/indexable_name_bench.cpp new file mode 100644 index 0000000..64f6a97 --- /dev/null +++ b/benchmarks/indexable_name_bench.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2025, Cisco Systems, Inc. +// All rights reserved. + +#include + +#include + +static void IndexableName_Construct(benchmark::State& state) +{ + for ([[maybe_unused]] const auto& _ : state) + { + benchmark::DoNotOptimize(flow::IndexableName{"benchmark"}); + } +} + +static void IndexableName_Hash(benchmark::State& state) +{ + constexpr flow::IndexableName name{"benchmark"}; + for ([[maybe_unused]] const auto& _ : state) + { + benchmark::DoNotOptimize(std::hash{}(name)); + } +} + +BENCHMARK(IndexableName_Construct); +BENCHMARK(IndexableName_Hash); diff --git a/benchmarks/node_data_bench.cpp b/benchmarks/node_data_bench.cpp new file mode 100644 index 0000000..87fd903 --- /dev/null +++ b/benchmarks/node_data_bench.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2025, Cisco Systems, Inc. +// All rights reserved. + +#include + +#include + +static void NodeData_Construct(benchmark::State& state) +{ + for ([[maybe_unused]] const auto& _ : state) + { + flow::NodeData data; + benchmark::DoNotOptimize(data); + benchmark::ClobberMemory(); + } +} + +static void NodeData_ConstructShared(benchmark::State& state) +{ + for ([[maybe_unused]] const auto& _ : state) + { + auto data = flow::MakeNodeData(0); + benchmark::DoNotOptimize(data); + benchmark::ClobberMemory(); + } +} + +BENCHMARK(NodeData_Construct); +BENCHMARK(NodeData_ConstructShared); diff --git a/benchmarks/type_name_bench.cpp b/benchmarks/type_name_bench.cpp new file mode 100644 index 0000000..3c93f74 --- /dev/null +++ b/benchmarks/type_name_bench.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2025, Cisco Systems, Inc. +// All rights reserved. + +#include + +#include + +static void TypeName_Construct(benchmark::State& state) +{ + for ([[maybe_unused]] const auto& _ : state) + { + flow::TypeName name; + benchmark::DoNotOptimize(name); + benchmark::ClobberMemory(); + } +} + +BENCHMARK(TypeName_Construct); diff --git a/benchmarks/uuid_bench.cpp b/benchmarks/uuid_bench.cpp new file mode 100644 index 0000000..1fa0a18 --- /dev/null +++ b/benchmarks/uuid_bench.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2025, Cisco Systems, Inc. +// All rights reserved. + +#include + +#include + +static void UUID_ConstructGenerate(benchmark::State& state) +{ + for ([[maybe_unused]] const auto& _ : state) + { + flow::UUID id{}; + benchmark::DoNotOptimize(id); + benchmark::ClobberMemory(); + } +} + +static void UUID_ConstructFromString(benchmark::State& state) +{ + for ([[maybe_unused]] const auto& _ : state) + { + flow::UUID id{"b24f917e-3626-4246-bf13-c2543145abfd"}; + benchmark::DoNotOptimize(id); + benchmark::ClobberMemory(); + } +} + +static void UUID_ConstructToString(benchmark::State& state) +{ + flow::UUID id{"b24f917e-3626-4246-bf13-c2543145abfd"}; + for ([[maybe_unused]] const auto& _ : state) + { + std::string uuid_str = std::string(id); + benchmark::DoNotOptimize(id); + benchmark::ClobberMemory(); + } +} + +static void UUID_Hash(benchmark::State& state) +{ + flow::UUID id{"b24f917e-3626-4246-bf13-c2543145abfd"}; + for ([[maybe_unused]] const auto& _ : state) + { + auto h = std::hash{}(id); + benchmark::DoNotOptimize(h); + benchmark::ClobberMemory(); + } +} + +BENCHMARK(UUID_ConstructGenerate); +BENCHMARK(UUID_ConstructFromString); +BENCHMARK(UUID_ConstructToString); +BENCHMARK(UUID_Hash); diff --git a/cabin.toml b/cabin.toml deleted file mode 100644 index 3fd60b0..0000000 --- a/cabin.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "flow-core" -version = "1.2.0" -authors = ["Scott Henning ", "Tomas Rigaux "] -edition = "20" - -[dependencies] -json = { git = "https://github.com/nlohmann/json.git" } -thread_pool = { git = "https://github.com/bshoshany/thread-pool.git" } diff --git a/docs/getting-started.md b/docs/getting-started.md index 9c57e49..2c95533 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -19,7 +19,7 @@ Flow Core is built around these key concepts: #include int main() { - auto factory = std::make_shared(); + auto factory = flow::NodeFactory::Create(); auto env = flow::Env::Create(factory); auto graph = std::make_shared("MyGraph", env); diff --git a/include/flow/core/Env.hpp b/include/flow/core/Env.hpp index 32c1834..44bd145 100644 --- a/include/flow/core/Env.hpp +++ b/include/flow/core/Env.hpp @@ -4,7 +4,6 @@ #pragma once #include "Core.hpp" -#include "NodeFactory.hpp" #include @@ -49,7 +48,7 @@ class Env public: /// Type alias for a function that visits a shared node. - using VisitorFunction = std::function; + using VisitorFunction = std::function&)>; Env(const Env&) = delete; @@ -60,10 +59,7 @@ class Env * @param settings The settings for the environment. Defaults to default-constructed Settings. * @returns A shared pointer to the created Env. */ - static std::shared_ptr Create(std::shared_ptr factory, const Settings& settings = {}) - { - return std::shared_ptr(new Env(std::move(factory), settings)); - } + static std::shared_ptr Create(std::shared_ptr factory, const Settings& settings = {}); /** * @brief Gets the current factory for building nodes. diff --git a/include/flow/core/Event.hpp b/include/flow/core/Event.hpp index 4507811..cafe3aa 100644 --- a/include/flow/core/Event.hpp +++ b/include/flow/core/Event.hpp @@ -33,7 +33,7 @@ class EventDispatcher * @param name The unique identifier of the event to be bound. * @param event The event to be bound. */ - void Bind(IndexableName name, EventType&& event) noexcept { _events.emplace(name, std::move(event)); } + void Bind(IndexableName name, EventType&& event) noexcept { _events.try_emplace(name, std::move(event)); } /** * @brief Unbinds an event by name. @@ -48,7 +48,7 @@ class EventDispatcher void UnbindAll() { _events.clear(); } /** - * @brief Broadcasts the given arguments to all bound events. + * @brief Broadcasts the given arguments to all bound events asynchronously. * * @param args Variadic arguments to pass to all bound events. */ diff --git a/include/flow/core/FunctionNode.hpp b/include/flow/core/FunctionNode.hpp index 948979a..b3aa35d 100644 --- a/include/flow/core/FunctionNode.hpp +++ b/include/flow/core/FunctionNode.hpp @@ -13,12 +13,21 @@ FLOW_NAMESPACE_BEGIN +using json = nlohmann::json; + +template +concept JsonSerialisable = requires(T t) { + { + json(t) + }; +}; + /** * @brief Extract function signature information at compile-time * * @tparam F Function type to analyze */ -template +template struct FunctionTraits; /** @@ -35,6 +44,26 @@ struct FunctionTraits static constexpr std::size_t arg_count = sizeof...(Args); }; +template +struct FunctionTraits : FunctionTraits +{ +}; + +template +struct FunctionTraits : FunctionTraits +{ +}; + +template +struct FunctionTraits : FunctionTraits +{ +}; + +template +struct FunctionTraits> : FunctionTraits +{ +}; + /** * @brief Node that wraps a function into the graph system * @@ -43,10 +72,9 @@ struct FunctionTraits * - Output port named "return" for the return value * - Reference parameters become output ports * - * @tparam F Function type (e.g., int(float, bool)) * @tparam Func Pointer to concrete function implementation */ -template> Func> +template class FunctionNode : public Node { /// Helper to decay tuple types while preserving references @@ -63,7 +91,8 @@ class FunctionNode : public Node using decayed_tuple_t = typename decayed_tuple::type; protected: - using traits = FunctionTraits>; + using F = decltype(Func); + using traits = FunctionTraits>>; using output_t = typename traits::ReturnType; using arg_ts = typename traits::ArgTypes; @@ -128,18 +157,17 @@ class FunctionNode : public Node template json SaveInputs(std::integer_sequence) const { - json inputs_json; + json inputs_json = json::object(); const auto& inputs = GetInputPorts(); ( [&, this] { - if constexpr (!std::is_convertible_v, json>) + if constexpr (!JsonSerialisable>) { return; } else { - const auto& key = _arg_names[Idx]; if (!inputs.contains(IndexableName{key})) { @@ -150,6 +178,10 @@ class FunctionNode : public Node { inputs_json[key] = x->Get(); } + else if (auto y = GetInputData>>(IndexableName{key})) + { + inputs_json[key] = y->Get(); + } } }(), ...); @@ -179,7 +211,7 @@ class FunctionNode : public Node public: explicit FunctionNode(const UUID& uuid, const std::string& name, std::shared_ptr env, std::vector arg_names = {}) - : Node(uuid, TypeName_v>, name, std::move(env)), _func{Func} + : Node(uuid, TypeName_v>, name, std::move(env)), _func{Func} { ParseArguments(std::make_integer_sequence>{}, arg_names); @@ -230,21 +262,21 @@ class FunctionNode : public Node } private: - std::add_pointer_t> _func; + std::decay_t _func; static inline std::array> _arg_names{""}; decayed_tuple_t _arguments; }; -template +template void NodeFactory::RegisterFunction(const std::string& category, const std::string& name, std::vector arg_names) { - constexpr std::string_view class_name = TypeName_v>; + constexpr std::string_view class_name = TypeName_v>; _constructor_map.emplace( class_name, [names = std::move(arg_names)](const std::string& uuid_str, const std::string& name, std::shared_ptr env) { - return new FunctionNode(uuid_str, name, std::move(env), std::move(names)); + return new FunctionNode(uuid_str, name, std::move(env), std::move(names)); }); _category_map.emplace(category, class_name); _friendly_names.emplace(class_name, name); @@ -252,4 +284,10 @@ void NodeFactory::RegisterFunction(const std::string& category, const std::strin OnNodeClassRegistered.Broadcast(std::string_view{class_name}); } +template +SharedNode NodeFactory::CreateFunctionNode(const UUID& uuid, const std::string& name, std::shared_ptr env) +{ + return NodeFactory::CreateNode>(uuid, name, env); +} + FLOW_NAMESPACE_END diff --git a/include/flow/core/Graph.hpp b/include/flow/core/Graph.hpp index 2b535e5..cba681b 100644 --- a/include/flow/core/Graph.hpp +++ b/include/flow/core/Graph.hpp @@ -169,15 +169,6 @@ class Graph void DisconnectNodes(const UUID& start, const IndexableName& start_key, const UUID& end, const IndexableName& end_key); - /** - * @brief Propagates data through the connections of the given ID. - * - * @param id The ID of the connection where the data came from. - * @param key The name of the port from which data is flowing. - * @param data The data to propagate. - */ - void PropagateConnectionsData(const UUID& id, const IndexableName& key, SharedNodeData data); - /** * @brief Sets the name of the graph. * @param new_name The new Name of the graph. @@ -208,6 +199,16 @@ class Graph */ friend void from_json(const json& j, Graph& g); + protected: + /** + * @brief Propagates data through the connections of the given ID. + * + * @param id The ID of the connection where the data came from. + * @param key The name of the port from which data is flowing. + * @param data The data to propagate. + */ + void PropagateConnectionsData(const UUID& id, const IndexableName& key, SharedNodeData data); + public: /// Event run on Graph errors being thrown. EventDispatcher OnError; diff --git a/include/flow/core/IndexableName.hpp b/include/flow/core/IndexableName.hpp index d17aaa8..2c90e1a 100644 --- a/include/flow/core/IndexableName.hpp +++ b/include/flow/core/IndexableName.hpp @@ -6,8 +6,11 @@ #include "Core.hpp" #include +#include #include +#include #include +#include #include #include @@ -16,75 +19,261 @@ FLOW_NAMESPACE_BEGIN /** * @brief Unique hashable integer representation of string. * - * @details IndexableName creates an integer representation of string using CRC-64-ECMA for + * @details IndexableName creates an integer representation of string using CityHash64 for * collision-resistant hashing. The hash is computed at compile-time when possible. * The name reference is kept to help with visualization and debugging, but the hash * is used for all comparisons and storage. * - * @note Uses CRC-64-ECMA polynomial: 0xC96C5795D7870F42 + * @note Uses CityHash64 */ class IndexableName { private: - /// CRC-64-ECMA lookup table for efficient hash computation - static constexpr std::array crc_table = [] { - std::array table; - for (std::size_t c = 0; c < 256; ++c) + /** + * @brief Computes a CityHash64 hash for a span of bytes. + * + * @note Original algorithm from https://github.com/google/cityhash + * + * Copyright (c) 2011 Google, Inc. + * + * 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. + */ + class CityHash64 + { + using uint128_t = std::array; + + static constexpr std::uint64_t k0 = 0xc3a5c85c97cb3127ULL; + static constexpr std::uint64_t k1 = 0xb492b66fbe98f273ULL; + static constexpr std::uint64_t k2 = 0x9ae16a3b2f90404fULL; + + private: + constexpr std::uint32_t SwapBytes(const std::uint32_t value) + { + return ((value >> 24) & 0x000000FF) | ((value >> 8) & 0x0000FF00) | ((value << 8) & 0x00FF0000) | + ((value << 24) & 0xFF000000); + } + + constexpr std::uint64_t SwapBytes(const std::uint64_t value) { - std::size_t crc = c; - for (std::size_t i = 0; i < 8; ++i) + return ((value >> 56) & 0x00000000000000FF) | ((value >> 40) & 0x000000000000FF00) | + ((value >> 24) & 0x0000000000FF0000) | ((value >> 8) & 0x00000000FF000000) | + ((value << 8) & 0x000000FF00000000) | ((value << 24) & 0x0000FF0000000000) | + ((value << 40) & 0x00FF000000000000) | ((value << 56) & 0xFF00000000000000); + } + + template + constexpr T UnalignedLoad(std::string_view bytes) + { + T result = 0; + if (std::is_constant_evaluated()) + { + for (size_t i = 0; i < sizeof(T); ++i) + { + result += bytes[i]; + result <<= 8; + } + } + else { - std::size_t b = (crc & 1); - crc >>= 1; - crc ^= (0 - b) & 0xc96c5795d7870f42ull; + std::memcpy(&result, bytes.data(), sizeof(T)); } - table[c] = crc; + + return result; } - return table; - }(); + template + constexpr T Fetch(std::string_view bytes) + { + return SwapBytes(UnalignedLoad(bytes)); + } - /** - * @brief Reverses bits in a 64-bit integer for CRC finalization. - * @param value The value to reverse - * @returns The bit-reversed value - */ - static constexpr std::size_t reverse_bits(std::size_t value) noexcept - { - std::size_t result = 0; - for (std::size_t i = 0; i < 64; ++i, value >>= 1) + template + constexpr std::uint64_t Rotate(T val, int shift) { - result = (result << 1) | (value & 1); + return shift == 0 ? val : ((val >> shift) | (val << ((sizeof(T) * 8) - shift))); } - return result; - } + constexpr std::uint64_t ShiftMix(std::uint64_t val) { return val ^ (val >> 47); } - /** - * @brief Computes CRC-64-ECMA hash of string. - * @param str The string to hash - * @returns The CRC-64 hash of the given string - * @throws std::invalid_argument if str is empty - */ - static constexpr std::size_t hash(std::string_view str) - { - if (str.empty()) + constexpr std::uint64_t Hash128to64(const uint128_t& x) { - throw std::invalid_argument("IndexableName cannot be empty"); + constexpr std::uint64_t kMul = 0x9ddfea08eb382d69ULL; + std::uint64_t a = (x[0] ^ x[1]) * kMul; + a ^= (a >> 47); + std::uint64_t b = (x[1] ^ a) * kMul; + b ^= (b >> 47); + b *= kMul; + return b; } - std::size_t crc = 0; - for (auto c : str) + constexpr std::uint64_t HashLen16(std::uint64_t u, std::uint64_t v) { return Hash128to64(uint128_t{u, v}); } + + constexpr std::uint64_t HashLen16(std::uint64_t u, std::uint64_t v, std::uint64_t mul) { - crc = crc_table[(crc & 0xFF) ^ c] ^ (crc >> 8); + // Murmur-inspired hashing. + std::uint64_t a = (u ^ v) * mul; + a ^= (a >> 47); + std::uint64_t b = (v ^ a) * mul; + b ^= (b >> 47); + b *= mul; + return b; } - return reverse_bits(crc); - } + constexpr std::uint64_t HashLen0to16(std::string_view bytes) + { + const std::size_t len = bytes.size(); + + if (len >= 8) + { + std::uint64_t mul = k2 + len * 2; + std::uint64_t a = Fetch(bytes) + k2; + std::uint64_t b = Fetch(bytes.substr(len - 8)); + std::uint64_t c = Rotate(b, 37) * mul + a; + std::uint64_t d = (Rotate(a, 25) + b) * mul; + return HashLen16(c, d, mul); + } + if (len >= 4) + { + std::uint64_t mul = k2 + len * 2; + std::uint64_t a = Fetch(bytes); + return HashLen16(len + (a << 3), Fetch(bytes.substr(len - 4)), mul); + } + if (len > 0) + { + std::uint8_t a = static_cast(bytes[0]); + std::uint8_t b = static_cast(bytes[len >> 1]); + std::uint8_t c = static_cast(bytes[len - 1]); + std::uint32_t y = static_cast(a) + (static_cast(b) << 8); + std::uint32_t z = static_cast(len) + (static_cast(c) << 2); + return ShiftMix(y * k2 ^ z * k0) * k2; + } + return k2; + } + + constexpr std::uint64_t HashLen17to32(std::string_view bytes) + { + const std::size_t len = bytes.size(); + + std::uint64_t mul = k2 + len * 2; + std::uint64_t a = Fetch(bytes) * k1; + std::uint64_t b = Fetch(bytes.substr(8)); + std::uint64_t c = Fetch(bytes.substr(len - 8)) * mul; + std::uint64_t d = Fetch(bytes.substr(len - 16)) * k2; + return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, a + Rotate(b + k2, 18) + c, mul); + } + + constexpr uint128_t WeakHashLen32WithSeeds(std::uint64_t w, std::uint64_t x, std::uint64_t y, std::uint64_t z, + std::uint64_t a, std::uint64_t b) + { + a += w; + b = Rotate(b + a + z, 21); + std::uint64_t c = a; + a += x; + a += y; + b += Rotate(a, 44); + return {a + z, b + c}; + } + + constexpr uint128_t WeakHashLen32WithSeeds(std::string_view bytes, std::uint64_t a, std::uint64_t b) + { + return WeakHashLen32WithSeeds(Fetch(bytes), Fetch(bytes.substr(8)), + Fetch(bytes.substr(16)), + Fetch(bytes.substr(24)), a, b); + } + + constexpr std::uint64_t HashLen33to64(std::string_view bytes) + { + const std::size_t len = bytes.size(); + std::uint64_t mul = k2 + len * 2; + std::uint64_t a = Fetch(bytes) * k2; + std::uint64_t b = Fetch(bytes.substr(8)); + std::uint64_t c = Fetch(bytes.substr(len - 24)); + std::uint64_t d = Fetch(bytes.substr(len - 32)); + std::uint64_t e = Fetch(bytes.substr(16)) * k2; + std::uint64_t f = Fetch(bytes.substr(24)) * 9; + std::uint64_t g = Fetch(bytes.substr(len - 8)); + std::uint64_t h = Fetch(bytes.substr(len - 16)) * mul; + std::uint64_t u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9; + std::uint64_t v = ((a + g) ^ d) + f + 1; + std::uint64_t w = SwapBytes((u + v) * mul) + h; + std::uint64_t x = Rotate(e + f, 42) + c; + std::uint64_t y = (SwapBytes((v + w) * mul) + g) * mul; + std::uint64_t z = e + f + c; + + a = SwapBytes((x + z) * mul + y) + b; + b = ShiftMix((z + a) * mul + d + h) * mul; + + return b + x; + } + + public: + constexpr std::uint64_t operator()(std::string_view bytes) + { + std::size_t len = bytes.size(); + + if (len <= 16) + { + return HashLen0to16(bytes); + } + else if (len <= 32) + { + return HashLen17to32(bytes); + } + else if (len <= 64) + { + return HashLen33to64(bytes); + } + + // For strings over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + std::uint64_t x = Fetch(bytes.substr(len - 40)); + std::uint64_t y = + Fetch(bytes.substr(len - 16)) + Fetch(bytes.substr(len - 56)); + std::uint64_t z = HashLen16(Fetch(bytes.substr(len - 48)) + len, + Fetch(bytes.substr(len - 24))); + uint128_t v = WeakHashLen32WithSeeds(bytes.substr(len - 64), len, z); + uint128_t w = WeakHashLen32WithSeeds(bytes.substr(len - 32), y + k1, x); + x = x * k1 + Fetch(bytes); + + // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. + len = (len - 1) & ~static_cast(63); + do + { + x = Rotate(x + y + v[0] + Fetch(bytes.substr(8)), 37) * k1; + y = Rotate(y + v[1] + Fetch(bytes.substr(48)), 42) * k1; + x ^= w[1]; + y += v[0] + Fetch(bytes.substr(40)); + z = Rotate(z + w[0], 33) * k1; + v = WeakHashLen32WithSeeds(bytes, v[1] * k1, x + w[0]); + w = WeakHashLen32WithSeeds(bytes.substr(32), z + w[1], y + Fetch(bytes.substr(16))); + std::swap(z, x); + bytes = bytes.substr(64); + len -= 64; + } while (len != 0); + + return HashLen16(HashLen16(v[0], w[0]) + ShiftMix(y) * k1 + z, HashLen16(v[1], w[1]) + x); + } + }; public: /// Construct from string view, computing hash at compile time when possible - constexpr IndexableName(std::string_view name) noexcept : _value{hash(name)}, _name{name} {} + constexpr IndexableName(std::string_view name) noexcept : _value{CityHash64{}(name)}, _name{name} {} /// Construct from C-string, computing hash at compile time when possible constexpr IndexableName(const char* name) noexcept : IndexableName(std::string_view(name)) {} @@ -124,7 +313,7 @@ class IndexableName static const IndexableName None; private: - /// CRC-64-ECMA hash of the name + /// Hash of the name std::size_t _value; /// Reference to the original string @@ -144,5 +333,5 @@ FLOW_NAMESPACE_END template<> struct std::hash { - std::size_t operator()(const flow::IndexableName& name) const { return std::size_t(name); } + constexpr std::size_t operator()(const flow::IndexableName& name) const { return name.value(); } }; diff --git a/include/flow/core/Node.hpp b/include/flow/core/Node.hpp index 6f56cde..3b2edc9 100644 --- a/include/flow/core/Node.hpp +++ b/include/flow/core/Node.hpp @@ -148,7 +148,7 @@ class Node template [[nodiscard]] auto GetInputData(const IndexableName& key) const noexcept { - return CastNodeData(this->GetInputData(key)); + return DynamicCastNodeData(this->GetInputData(key)); } /** @@ -163,7 +163,7 @@ class Node template [[nodiscard]] auto GetOutputData(const IndexableName& key) const noexcept { - return CastNodeData(this->GetOutputData(key)); + return DynamicCastNodeData(this->GetOutputData(key)); } /** @@ -230,6 +230,7 @@ class Node virtual void Compute() = 0; virtual json SaveInputs() const; + virtual void RestoreInputs(const json&); protected: diff --git a/include/flow/core/NodeData.hpp b/include/flow/core/NodeData.hpp index 544d36d..3512e06 100644 --- a/include/flow/core/NodeData.hpp +++ b/include/flow/core/NodeData.hpp @@ -437,9 +437,27 @@ template } template -[[nodiscard]] constexpr TSharedNodeData CastNodeData(const SharedNodeData& value) +[[nodiscard]] constexpr TSharedNodeData StaticCastNodeData(const SharedNodeData& value) +{ + return std::static_pointer_cast>(value); +} + +template +[[nodiscard]] constexpr TSharedNodeData DynamicCastNodeData(const SharedNodeData& value) { return std::dynamic_pointer_cast>(value); } +template +[[nodiscard]] constexpr TSharedNodeData ReinterpretCastNodeData(const SharedNodeData& value) +{ + return std::reinterpret_pointer_cast>(value); +} + +template +[[deprecated]] [[nodiscard]] constexpr TSharedNodeData CastNodeData(const SharedNodeData& value) +{ + return DynamicCastNodeData(value); +} + FLOW_NAMESPACE_END diff --git a/include/flow/core/NodeFactory.hpp b/include/flow/core/NodeFactory.hpp index c6bb37d..445a9ac 100644 --- a/include/flow/core/NodeFactory.hpp +++ b/include/flow/core/NodeFactory.hpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -34,9 +33,18 @@ class NodeFactory { using ConstructorCallback = std::function)>; + protected: + NodeFactory() = default; + public: virtual ~NodeFactory() = default; + /** + * @brief Creates a shared NodeFactory. + * @returns Shared ptr to a NodeFactory. + */ + static std::shared_ptr Create() { return std::shared_ptr(new NodeFactory()); } + /** * @brief Registers a node's construction method by it's friendly name and a category. * @@ -54,7 +62,7 @@ class NodeFactory template void UnregisterNodeClass(const std::string& category); - template + template void RegisterFunction(const std::string& category, const std::string& name, std::vector arg_names = {}); @@ -77,6 +85,15 @@ class NodeFactory SharedNode CreateNode(const std::string& class_name, const UUID& uuid, const std::string& name, std::shared_ptr env); + template + SharedNode CreateNode(const UUID& uuid, const std::string& name, std::shared_ptr env) + { + return CreateNode(std::string{TypeName_v}, uuid, name, env); + } + + template + SharedNode CreateFunctionNode(const UUID& uuid, const std::string& name, std::shared_ptr env); + const CategoryMap& GetCategories() const; std::string GetFriendlyName(const std::string& class_name) const; @@ -220,8 +237,6 @@ class NodeFactory CategoryMap _category_map; std::unordered_map _friendly_names; TypeRegistry _conversion_registry; - - mutable std::mutex _mutex; }; /** @@ -324,7 +339,7 @@ void NodeFactory::RegisterCompleteConversion() template TSharedNodeData NodeFactory::Convert(const SharedNodeData& data) { - return CastNodeData(_conversion_registry.Convert(data, TypeName_v)); + return DynamicCastNodeData(_conversion_registry.Convert(data, TypeName_v)); } template diff --git a/include/flow/core/TypeConversion.hpp b/include/flow/core/TypeConversion.hpp index f521793..da3b07a 100644 --- a/include/flow/core/TypeConversion.hpp +++ b/include/flow/core/TypeConversion.hpp @@ -37,7 +37,7 @@ class TypeRegistry template static SharedNodeData Convert(const SharedNodeData& data) { - if (auto from_data = CastNodeData(data)) + if (auto from_data = DynamicCastNodeData(data)) { if constexpr (std::is_rvalue_reference_v) { @@ -48,6 +48,7 @@ class TypeRegistry return MakeNodeData(*from_data); } } + return data; } @@ -151,16 +152,17 @@ void TypeRegistry::RegisterUnidirectionalConversion(const ConversionFunc& conver if constexpr (std::is_same_v, std::decay_t>) { - RegisterConversion, std::add_lvalue_reference_t>>(); - RegisterConversion, std::add_lvalue_reference_t>>>(); - RegisterConversion, std::add_rvalue_reference_t>>(); - RegisterConversion>, - std::add_lvalue_reference_t>>>(); - RegisterConversion>, std::decay_t>(); - RegisterConversion>, - std::add_lvalue_reference_t>>>(); - RegisterConversion>>, std::decay_t>(); - RegisterConversion>, std::decay_t>(); + using T = std::decay_t; + using U = std::decay_t; + + RegisterConversion>(); + RegisterConversion>>(); + RegisterConversion>(); + RegisterConversion, std::add_lvalue_reference_t>>(); + RegisterConversion, U>(); + RegisterConversion, std::add_lvalue_reference_t>>(); + RegisterConversion>, U>(); + RegisterConversion, U>(); } } diff --git a/include/flow/core/TypeName.hpp b/include/flow/core/TypeName.hpp index 83636c3..2f19274 100644 --- a/include/flow/core/TypeName.hpp +++ b/include/flow/core/TypeName.hpp @@ -73,9 +73,6 @@ constexpr std::string_view get_typename() noexcept template struct TypeName { - /** - * @brief The string representation of the given type. - */ static constexpr std::string_view value = detail::get_typename(); static constexpr bool is_reference = false; diff --git a/include/flow/core/UUID.hpp b/include/flow/core/UUID.hpp index 769585e..7da78a7 100644 --- a/include/flow/core/UUID.hpp +++ b/include/flow/core/UUID.hpp @@ -60,12 +60,10 @@ class UUID */ [[nodiscard]] std::size_t hash() const noexcept { - const auto* half = std::bit_cast(_id.data()); + const auto* half = reinterpret_cast(_id.data()); return half[0] ^ half[1]; } - friend struct std::hash; - private: /// Storage for 16-byte UUID value std::array _id; diff --git a/src/Env.cpp b/src/Env.cpp index 5e47407..6160e20 100644 --- a/src/Env.cpp +++ b/src/Env.cpp @@ -25,6 +25,11 @@ Env::Env(std::shared_ptr factory, const Settings& settings) std::chrono::days, std::chrono::months, std::chrono::years>(); } +std::shared_ptr Env::Create(std::shared_ptr factory, const Settings& settings) +{ + return std::shared_ptr(new Env(std::move(factory), settings)); +} + void Env::Wait() { _pool->wait(); } std::string Env::GetVar(const std::string& varname) const diff --git a/src/Port.cpp b/src/Port.cpp index 99fa719..908dac0 100644 --- a/src/Port.cpp +++ b/src/Port.cpp @@ -2,6 +2,7 @@ // All rights reserved. #include "flow/core/Port.hpp" +#include "Port.hpp" FLOW_NAMESPACE_BEGIN @@ -51,4 +52,4 @@ void Port::SetData(SharedNodeData data, bool output) void Port::SetCaption(std::string new_caption) { _caption = std::move(new_caption); } -FLOW_NAMESPACE_END +FLOW_NAMESPACE_END \ No newline at end of file diff --git a/src/lib.cpp b/src/lib.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6a0d73..2a5b774 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,12 +18,13 @@ set(TEST_EXE flow_core_tests) add_executable( ${TEST_EXE} + data_test.cpp factory_test.cpp graph_test.cpp indexable_name_test.cpp + module_test.cpp node_test.cpp type_name_test.cpp - module_test.cpp ) if(MSVC) diff --git a/tests/data_test.cpp b/tests/data_test.cpp new file mode 100644 index 0000000..e2bf30c --- /dev/null +++ b/tests/data_test.cpp @@ -0,0 +1,38 @@ +// Copyright (c) 2024, Cisco Systems, Inc. +// All rights reserved. + +#include "flow/core/Env.hpp" +#include "flow/core/NodeData.hpp" +#include "flow/core/NodeFactory.hpp" + +#include +#include + +using namespace flow; + +TEST(Data, Construction) +{ + auto data = MakeNodeData(101); + EXPECT_EQ(data->Get(), 101); +} + +TEST(Data, Copy) +{ + auto data = MakeNodeData(101); + auto x = data->Get(); + EXPECT_EQ(x, 101); +} + +TEST(Data, Move) +{ + auto data = MakeNodeData(101); + auto x = std::move(data); + EXPECT_EQ(x->Get(), 101); +} + +TEST(Data, Get) +{ + auto data = MakeNodeData(101); + auto x = data->Get(); + EXPECT_EQ(x, 101); +} diff --git a/tests/factory_test.cpp b/tests/factory_test.cpp index 3be8ced..faf957e 100644 --- a/tests/factory_test.cpp +++ b/tests/factory_test.cpp @@ -36,17 +36,17 @@ struct TestNode : public Node }; } // namespace -TEST(FactoryTest, Construction) { ASSERT_NO_THROW(auto factory = std::make_shared()); } +TEST(FactoryTest, Construction) { ASSERT_NO_THROW(auto factory = NodeFactory::Create()); } TEST(FactoryTest, RegisterNodeClass) { - auto factory = std::make_shared(); + auto factory = NodeFactory::Create(); ASSERT_NO_THROW(factory->RegisterNodeClass("Test")); } TEST(FactoryTest, CreateNode) { - auto factory = std::make_shared(); + auto factory = NodeFactory::Create(); auto env = Env::Create(factory); ASSERT_NO_THROW(factory->RegisterNodeClass("Test")); ASSERT_NO_THROW(auto node = factory->CreateNode(std::string{TypeName_v}, UUID{}, "test", env)); diff --git a/tests/graph_test.cpp b/tests/graph_test.cpp index f8e150a..50dbdbc 100644 --- a/tests/graph_test.cpp +++ b/tests/graph_test.cpp @@ -2,6 +2,7 @@ // All rights reserved. #include "flow/core/Env.hpp" +#include "flow/core/FunctionNode.hpp" #include "flow/core/Graph.hpp" #include "flow/core/Node.hpp" #include "flow/core/NodeData.hpp" @@ -9,16 +10,18 @@ #include +#include + using namespace flow; namespace { -auto factory = std::make_shared(); -auto env = Env::Create(factory); +auto factory = NodeFactory::Create(); +auto test_env = Env::Create(factory); struct TestNode : public Node { - TestNode() : Node(UUID{}, TypeName_v, "Test", env) + TestNode() : Node(UUID{}, TypeName_v, "Test", test_env) { AddInput("in", ""); AddInput("other_in", ""); @@ -40,11 +43,11 @@ struct TestNode : public Node }; } // namespace -TEST(GraphTest, Construction) { ASSERT_NO_THROW(auto graph = std::make_shared("test", env)); } +TEST(GraphTest, Construction) { ASSERT_NO_THROW(auto graph = std::make_shared("test", test_env)); } TEST(GraphTest, AddNodes) { - auto graph = std::make_shared("test", env); + auto graph = std::make_shared("test", test_env); auto node1 = std::make_shared<::TestNode>(); auto node2 = std::make_shared<::TestNode>(); @@ -61,7 +64,7 @@ TEST(GraphTest, AddNodes) TEST(GraphTest, RemoveNodes) { - auto graph = std::make_shared("test", env); + auto graph = std::make_shared("test", test_env); auto node1 = std::make_shared<::TestNode>(); auto node2 = std::make_shared<::TestNode>(); @@ -81,7 +84,7 @@ TEST(GraphTest, RemoveNodes) TEST(GraphTest, ConnectNodes) { - auto graph = std::make_shared("test", env); + auto graph = std::make_shared("test", test_env); auto node1 = std::make_shared<::TestNode>(); auto node2 = std::make_shared<::TestNode>(); @@ -97,7 +100,7 @@ TEST(GraphTest, ConnectNodes) TEST(GraphTest, DisconnectNodes) { - auto graph = std::make_shared("test", env); + auto graph = std::make_shared("test", test_env); auto node1 = std::make_shared<::TestNode>(); auto node2 = std::make_shared<::TestNode>(); @@ -114,7 +117,7 @@ TEST(GraphTest, DisconnectNodes) TEST(GraphTest, PropagateConnectionData) { - auto graph = std::make_shared("test", env); + auto graph = std::make_shared("test", test_env); auto node1 = std::make_shared<::TestNode>(); auto node2 = std::make_shared<::TestNode>(); @@ -130,7 +133,7 @@ TEST(GraphTest, PropagateConnectionData) EXPECT_NE(node1->GetOutputData("out"), nullptr); EXPECT_EQ(node1->GetOutputData("other_out"), nullptr); - env->Wait(); + test_env->Wait(); EXPECT_NE(node2->GetInputData("in"), nullptr); EXPECT_EQ(node2->GetInputData("other_in"), nullptr); @@ -139,7 +142,7 @@ TEST(GraphTest, PropagateConnectionData) ASSERT_NO_THROW(node1->SetInputData("other_in", MakeNodeData(202))); - env->Wait(); + test_env->Wait(); EXPECT_NE(node2->GetInputData("in"), nullptr); EXPECT_NE(node2->GetInputData("other_in"), nullptr); @@ -150,7 +153,7 @@ TEST(GraphTest, PropagateConnectionData) TEST(GraphTest, DistinguishNodes) { - auto graph = std::make_shared("test", env); + auto graph = std::make_shared("test", test_env); auto node1 = std::make_shared<::TestNode>(); auto node2 = std::make_shared<::TestNode>(); auto node3 = std::make_shared<::TestNode>(); diff --git a/tests/indexable_name_test.cpp b/tests/indexable_name_test.cpp index 4fb1163..1228634 100644 --- a/tests/indexable_name_test.cpp +++ b/tests/indexable_name_test.cpp @@ -24,6 +24,7 @@ TEST(IndexableNameTest, Equality) ASSERT_EQ(IndexableName{"tests"}, IndexableName{std::string("tests")}); ASSERT_EQ(IndexableName{"tests"}, IndexableName{std::string_view("tests")}); + ASSERT_NE(IndexableName{"tests"}, IndexableName{"test"}); ASSERT_NE(IndexableName{"tests"}, IndexableName{"stset"}); } diff --git a/tests/module_test.cpp b/tests/module_test.cpp index bbf86b9..1a33702 100644 --- a/tests/module_test.cpp +++ b/tests/module_test.cpp @@ -14,7 +14,7 @@ using namespace flow; const std::filesystem::path module_path = std::filesystem::current_path() / "test_module.fmod"; -auto factory = std::make_shared(); +auto factory = NodeFactory::Create(); auto env = Env::Create(factory); TEST(ModuleTest, Load) diff --git a/tests/node_test.cpp b/tests/node_test.cpp index 89b9a6e..cbe7632 100644 --- a/tests/node_test.cpp +++ b/tests/node_test.cpp @@ -14,7 +14,7 @@ using namespace flow; namespace test { -auto factory = std::make_shared(); +auto factory = NodeFactory::Create(); auto env = Env::Create(factory); } // namespace test @@ -52,7 +52,7 @@ struct TestNode : public Node }; } // namespace NodeTest -TEST(NodeTest, Construction) +TEST(Node, Construction) { ASSERT_NO_THROW(NodeTest::TestNode()); @@ -62,7 +62,7 @@ TEST(NodeTest, Construction) EXPECT_EQ(node.GetEnv(), test::env); } -TEST(NodeTest, AddInputPorts) +TEST(Node, AddInputPorts) { NodeTest::TestNode node; @@ -97,7 +97,8 @@ TEST(NodeTest, AddInputPorts) ASSERT_EQ(data->Get(), 101); } } -TEST(NodeTest, AddOutputPorts) + +TEST(Node, AddOutputPorts) { NodeTest::TestNode node; @@ -133,7 +134,7 @@ TEST(NodeTest, AddOutputPorts) } } -TEST(NodeTest, Compute) +TEST(Node, Compute) { NodeTest::TestNode node; node.AddInput("in", ""); @@ -157,21 +158,38 @@ int return_ref_test_method(int& i) { return i; } struct TestData { }; -void custom_type(const TestData&) {} +void custom_type_test_method(const TestData&) {} -TEST(NodeTest, WrapFunctions) +struct TestFunctor { - FunctionNode void_node({}, "void_test_method", test::env); - FunctionNode return_node({}, "return_test_method", test::env); - FunctionNode return_ref_node({}, "return_ref_test_method", - test::env); - FunctionNode custom_type_node({}, "custom_type", test::env); - - ASSERT_EQ(void_node.GetInputPorts().size(), 1); - ASSERT_EQ(return_node.GetInputPorts().size(), 1); - ASSERT_TRUE(return_ref_node.GetInputPorts().empty()); - - ASSERT_TRUE(void_node.GetOutputPorts().empty()); - ASSERT_EQ(return_node.GetOutputPorts().size(), 1); - ASSERT_EQ(return_ref_node.GetOutputPorts().size(), 2); + void operator()(int) {} +}; + +TEST(Node, WrapFunctions) +{ + FunctionNode void_test_method_node({}, "void_test_method", test::env); + FunctionNode return_test_method_node({}, "return_test_method", test::env); + FunctionNode return_ref_test_method_node({}, "return_ref_test_method", test::env); + FunctionNode custom_type_test_method_node({}, "custom_type_test_method", test::env); + FunctionNode functor_node({}, "functor", test::env); + + ASSERT_EQ(void_test_method_node.GetInputPorts().size(), 1); + ASSERT_EQ(return_test_method_node.GetInputPorts().size(), 1); + ASSERT_TRUE(return_ref_test_method_node.GetInputPorts().empty()); + ASSERT_EQ(custom_type_test_method_node.GetInputPorts().size(), 1); + ASSERT_EQ(functor_node.GetInputPorts().size(), 1); + + ASSERT_TRUE(void_test_method_node.GetOutputPorts().empty()); + ASSERT_EQ(return_test_method_node.GetOutputPorts().size(), 1); + ASSERT_EQ(return_ref_test_method_node.GetOutputPorts().size(), 2); + ASSERT_TRUE(custom_type_test_method_node.GetOutputPorts().empty()); + ASSERT_TRUE(functor_node.GetOutputPorts().empty()); +} + +TEST(Node, Save) +{ + NodeTest::TestNode node; + + auto x = node.Save(); + EXPECT_EQ(x, json({{"id", node.ID()}, {"class", node.GetClass()}, {"name", node.GetName()}, {"inputs", {}}}) ); }