├── .github └── workflows │ ├── gcov_file_generation.py │ └── runTests.yml ├── .gitignore ├── CMakeLists.txt ├── README.md ├── Readme-picture.png ├── Readme-picture.svg ├── cmake ├── FetchJSONlib.cmake ├── GCov.cmake └── MakeLibrary.cmake ├── samples ├── CMakeLists.txt ├── Sample-01-big-vector-samples │ ├── CMakeLists.txt │ ├── Main.cpp │ ├── README.md │ └── src │ │ ├── Sampler.cpp │ │ └── Sampler.h ├── Sample-02-long-text-process │ ├── CMakeLists.txt │ ├── Flow-Structure.svg │ ├── Main.cpp │ ├── README.md │ ├── assets │ │ ├── LoremIpsum │ │ └── Novel │ └── src │ │ ├── TextIO.cpp │ │ ├── TextIO.h │ │ ├── WordsParser.cpp │ │ └── WordsParser.h ├── Sample-03-concurrent-update-read │ ├── CMakeLists.txt │ ├── Main.cpp │ ├── README.md │ └── src │ │ ├── ValueListener.cpp │ │ └── ValueListener.h ├── Sample-04-collisions-detection │ ├── CMakeLists.txt │ ├── Main.cpp │ ├── README.md │ ├── script │ │ └── ShowResults.py │ └── src │ │ ├── CollisionDetectionFlow.cpp │ │ ├── CollisionDetectionFlow.h │ │ ├── Polygon.cpp │ │ ├── Polygon.h │ │ ├── ResultsAccumulator.cpp │ │ └── ResultsAccumulator.h ├── Sample-README │ ├── CMakeLists.txt │ └── Main.cpp ├── Sample-utils │ ├── CMakeLists.txt │ ├── RunScript.cpp │ ├── RunScript.h │ └── ShowGraph.py └── cmake │ └── make-sample.cmake ├── src ├── CMakeLists.txt ├── header │ └── DynamicFlow │ │ ├── CallBack.h │ │ ├── Error.h │ │ ├── Network.h │ │ ├── Network.hxx │ │ ├── NetworkSerialization.h │ │ ├── Node.h │ │ ├── Node.hxx │ │ ├── TypeTraits.h │ │ ├── Value.h │ │ └── Value.hxx └── src │ ├── Error.cpp │ ├── Network.cpp │ ├── NetworkSerialization.cpp │ └── Node.cpp └── tests ├── CMakeLists.txt ├── Common.h ├── Test00-callback.cpp ├── Test00-type-traits.cpp ├── Test01-value.cpp ├── Test02-node.cpp ├── Test03-network-handlers.cpp ├── Test04-network-update.cpp ├── Test05-network-update-lazyness.cpp ├── Test05-network-update-lazyness.svg ├── Test06-network-thread-safety.cpp └── Test06-network-thread-safety.svg /.github/workflows/gcov_file_generation.py: -------------------------------------------------------------------------------- 1 | # import required module 2 | import os 3 | 4 | def is_gcno_file(file_name): 5 | return file_name.endswith('.gcno') 6 | 7 | exluded_folders = ['_deps'] 8 | def is_not_excluded_folder(folder_name): 9 | for exluded in exluded_folders: 10 | if(exluded == folder_name): 11 | return False 12 | return True 13 | 14 | def build_gcov_files(directory): 15 | # iterate over files in that directory 16 | for filename in os.listdir(directory): 17 | f = os.path.join(directory, filename) 18 | # checking if it is a file 19 | if os.path.isfile(f): 20 | if is_gcno_file(f): 21 | command = "gcov " + f 22 | print(command) 23 | os.system(command) 24 | elif is_not_excluded_folder(f): 25 | build_gcov_files(f) 26 | 27 | root_directory = './' 28 | build_gcov_files(root_directory) 29 | -------------------------------------------------------------------------------- /.github/workflows/runTests.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Unit Tests 3 | 4 | on: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | unitTests: 14 | strategy: 15 | matrix: 16 | name: [ubuntu-gcc, ubuntu-clang, windows-VS] 17 | include: 18 | - name: ubuntu-gcc 19 | install_omp: 'Yes' 20 | os: ubuntu-latest 21 | compiler_opt: "-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -G \"Unix Makefiles\"" 22 | - name: ubuntu-clang 23 | install_omp: 'Yes' 24 | os: ubuntu-latest 25 | compiler_opt: "-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -G \"Unix Makefiles\"" 26 | - name: windows-VS 27 | install_omp: 'No' 28 | os: windows-latest 29 | compiler_opt: "" 30 | 31 | runs-on: ${{ matrix.os }} 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v2.0.0 35 | - name: Checkout submodules 36 | run: git submodule update --init --recursive 37 | - name: Install openMP 38 | if: matrix.install_omp == 'Yes' 39 | run: sudo apt install libomp-dev 40 | - name: CMake configure 41 | run: cmake -B./build -DCMAKE_INSTALL_PREFIX:STRING=./artifacts/ -DBUILD_DYN_FLOW_SAMPLES=OFF -DBUILD_DYN_FLOW_TESTS=ON -DCMAKE_CONFIGURATION_TYPES="Release" -DCMAKE_BUILD_TYPE:STRING=Release ${{ matrix.compiler_opt }} 42 | - name: Build 43 | run: cmake --build ./build --config Release 44 | - name: Install 45 | run: cmake --install ./build --config Release 46 | - name: Tests 47 | run: ./artifacts/bin/DynamicFlowTests 48 | - uses: actions/upload-artifact@v4.4.1 49 | with: 50 | path: artifacts 51 | name: ${{ matrix.name }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | TODO 4 | install 5 | cmake-build-* 6 | .idea 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0 FATAL_ERROR) 2 | 3 | option(LIB_OPT "Compile shared libraries (ON) or static (OFF)" OFF) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CMAKE_CXX_EXTENSIONS OFF) 8 | 9 | # include cmake custom functions 10 | set(WITH_SOURCE_TREE ON) 11 | include(${CMAKE_SOURCE_DIR}/cmake/MakeLibrary.cmake) 12 | include(${CMAKE_SOURCE_DIR}/cmake/GCov.cmake) 13 | include(${CMAKE_SOURCE_DIR}/cmake/FetchJSONlib.cmake) 14 | 15 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") 16 | 17 | project(DynamicFlow) 18 | 19 | add_subdirectory(src) 20 | 21 | option(BUILD_DYN_FLOW_SAMPLES "Build the samples" ON) 22 | if(BUILD_DYN_FLOW_SAMPLES) 23 | add_subdirectory(samples) 24 | endif() 25 | 26 | option(BUILD_DYN_FLOW_TESTS "" OFF) 27 | if(BUILD_DYN_FLOW_TESTS) 28 | add_subdirectory(tests) 29 | endif() 30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DynamicFlow 2 | 3 | ![binaries_compilation](https://github.com/andreacasalino/DynamicFlow/actions/workflows/runTests.yml/badge.svg) 4 | 5 | ![How a data flow looks like:](Readme-picture.png) 6 | 7 | The code quality is kept tracked by [SonarCloud](https://sonarcloud.io/summary/new_code?id=andreacasalino_DynamicFlow). 8 | 9 | - [What is this library about](#intro) 10 | - [Features](#features) 11 | - [Usage](#usage) 12 | - [CMake support](#cmake-support) 13 | 14 | ## INTRO 15 | 16 | Haven't left a **star** already? Do it now ;)! 17 | 18 | **DynamicFlow** is a heavily templatized library for handling **data flow programming**. 19 | [**Data flow programming**](https://devopedia.org/dataflow-programming) refers to the generation and handling of **data flow** having group of variables connected in a directed acyclic graph. 20 | Each node of the network contains a value that is obtained by applying a certain specified function to the values previously computed for to the ancestors nodes. 21 | This architecture can be used to model network of variables whose computation absorb a significant amount of time, like rendering engine, machine learning pipeline, long text handling, etc... 22 | The shape of the dependencies is exploited in order to enforce lazyness, re-computing the least possible number of nodes, every time that one or more sources of the network are update. 23 | 24 | **DynamicFlow** allows you to create and handle the entities of the network, re-setting at the proper time the sources. 25 | Then, you can subsequently update the entire network, re-computing only the nodes that were dependent by the updated sources. 26 | 27 | ### CONTENT 28 | 29 | * the sources of the library are contained in the [src](./src) folder. 30 | 31 | * example of usage are contained in the [samples](./samples) folder. Refer to the specific **README** of each sample: 32 | 33 | - [samples/Sample-01-big-vector-samples/README.md](samples/Sample-01-big-vector-samples/README.md) 34 | - [samples/Sample-02-long-text-process/README.md](samples/Sample-02-long-text-process/README.md) 35 | - [samples/Sample-03-concurrent-update-read/README.md](samples/Sample-03-concurrent-update-read/README.md) 36 | - [samples/Sample-04-collisions-detection/README.md](samples/Sample-04-collisions-detection/README.md) 37 | 38 | ## FEATURES 39 | 40 | Haven't left a **star** already? Do it now ;)! 41 | 42 | These are the most notable properties of **DynamicFlow**: 43 | 44 | * **DynamicFlow** relies on modern **C++** and generic programming to give you a nice interface to generate the nodes that are part of the flow. Indeed, you just need to specify the lambda expression to use every time for generating a new value and the list of ancestors node, see also the [Usage](#usage) Section. 45 | 46 | * **DynamicFlow** is be completely thread-safe. You can also update a network while adding new sources/nodes. By the way, feel free to report any bug if you discover a strange behaviour ;). 47 | 48 | * More than one thread can be used for updating the entire network. From the outside, only the number of threads to use must be specified. 49 | 50 | * For each node af a network, it is possible to register specific exception type that should be saved if an exception occour while evaluating the lambda expression associated to the node. Later, such exceptions can be rethrowned wihtout losing the exception type. See also the [Usage](#usage) Section. 51 | 52 | * You can export a dat flow network built with **DynamicFlow** into a: 53 | * **.json** file 54 | * **.dot** file, which can be rendered for example by making use of [Graphviz](https://graphviz.org/). The samples inside this repo already do something similar using [this](samples/python/ShowGraph.py) **Python** script. 55 | 56 | ## USAGE 57 | 58 | Haven't left a **star** already? Do it now ;)! 59 | 60 | You can build a dat flow step by step, defining each sources/nodes the flow should be made of. 61 | Creating the sources of the network can be easily done on this way: 62 | ```cpp 63 | #include 64 | 65 | // build a new flow 66 | flw::Flow flow; 67 | 68 | // Define the sources nodes, passing the initial values 69 | auto source_1 = flow.makeSource(576, "Source-1"); 70 | auto source_2 = flow.makeSource("Some value", "Source-2"); 71 | ``` 72 | 73 | Now you are ready to create the first node: 74 | ```cpp 75 | // Define a node combining the 2 sources 76 | // The intial value for the node will be computed right after built, as the default policy of the network 77 | // is OnNewNodePolicy::IMMEDIATE_UPDATE 78 | auto node = flow.makeNode( 79 | // here you pass or define the lambda used to processing the values of the ancestors 80 | [](const int &source_1, const std::string &source_2) { 81 | // use sources values to get the new node value 82 | std::string result = source_2 + std::to_string(source_1); 83 | return result; 84 | }, 85 | // the ancestors 86 | source_1, source_2, 87 | // label to assing to the node (is actually optional ... needed if you want to retrieve the node later from the network) 88 | "Node", 89 | // we want in this case to also register a call back, triggered every time a new value is computed for the node 90 | [](const std::string &new_value) { 91 | // do something fancy with new_value ... 92 | }); 93 | ``` 94 | 95 | Every time the value of a generated node is re-computed, the lambda expression passed when creating that node is invoked. 96 | Clearly, the execution of that expression may lead to an exception throw. 97 | In order to react to such circumnstances, **DynamicFlow** allows you to register a set of callbacks for the different kind of exception that may be thrown: 98 | ```cpp 99 | // It may happen that an exception is thrown when executing the labda passed to a node, when trying 100 | // to update the value stored in that node. 101 | // You can register a call back for each specific exception type that can be thrown. 102 | // In this way, the call back of the exception is given the exception without loosing its type. 103 | class CustomException1 : public std::runtime_error { 104 | public: 105 | CustomException1() : std::runtime_error{"Bla bla"} {}; 106 | }; 107 | class CustomException2 : public std::runtime_error { 108 | public: 109 | CustomException2() : std::runtime_error{"Bla bla"} {}; 110 | }; 111 | flow.makeNodeWithErrorsCB( 112 | [](const std::string &source_2) { 113 | std::string result; 114 | // ops ... an exception is thrown 115 | throw CustomException1{}; 116 | return result; 117 | }, 118 | source_2, 119 | flw::ValueCallBacks{} 120 | .addOnValue([](const std::string& val) { 121 | // do something with the value 122 | }) 123 | .addOnError([](const CustomException1& e) { 124 | // inspect e and trigger the proper reaction 125 | }) 126 | .addOnError([](const CustomException2& e) { 127 | // inspect e and trigger the proper reaction 128 | }) 129 | .extract()); 130 | ``` 131 | 132 | Values for the sources can be update, in order to trigger the required update on the other nodes of the network: 133 | ```cpp 134 | // Now is the time to update one of the source (we could update multiple ones or them all if needed). 135 | source_2.update("Some other value"); 136 | // Update those nodes in the flow requiring a recomputation 137 | flow.update(); 138 | ``` 139 | 140 | You can also tell **DynamicFlow** to use multiple threads to make the update of the entire network: 141 | ```cpp 142 | // You can also decide to update the flow, using multiple threads. 143 | // This is actually recommended only for very big network. 144 | flow.setThreads(3); 145 | // 3 threads will be used by the next update as 3 was passed to setThreads(...) 146 | flow.update(); 147 | ``` 148 | 149 | ## CMAKE SUPPORT 150 | 151 | Haven't yet left a **star**? Do it now! ;). 152 | 153 | To consume this library you can rely on [CMake](https://cmake.org). 154 | More precisely, You can fetch this package and link to the **DynamicFlow** library: 155 | ```cmake 156 | include(FetchContent) 157 | FetchContent_Declare( 158 | dyn_flow 159 | GIT_REPOSITORY https://github.com/andreacasalino/DynamicFlow 160 | GIT_TAG main 161 | ) 162 | FetchContent_MakeAvailable(dyn_flow) 163 | ``` 164 | 165 | and then link to the **DynamicFlow** library: 166 | ```cmake 167 | target_link_libraries(${TARGET_NAME} 168 | DynamicFlow 169 | ) 170 | ``` 171 | 172 | By default, only the functionalities to export generated data flow into **.dot** file are enabled. 173 | Instead, convertions to **json** are not. In order to enable it, you should turn to **ON** the camke option named **DYNAMIC_FLOW_ENABLE_JSON_EXPORT**. Notice that such a functionality relies on the well known [**nlohmann**](https://github.com/nlohmann/json) library, which automatically fetched from github for you by **CMake**. 174 | -------------------------------------------------------------------------------- /Readme-picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreacasalino/DynamicFlow/518d39652a3ecddeeac22e21c31cd7717a21297e/Readme-picture.png -------------------------------------------------------------------------------- /Readme-picture.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 43 | 45 | 46 | 48 | image/svg+xml 49 | 51 | 52 | 53 | 54 | 55 | 60 | 67 | 68 | 73 | 80 | Source 92 | 99 | Source 111 | 118 | Source 130 | 136 | Sum 148 | 154 | Extract Features 166 | 172 | Contatenate 184 | 190 | Log 202 | 208 | 214 | 221 | Node 233 | 240 | Node 252 | 258 | 264 | 271 | Node 283 | 290 | Node 302 | 308 | 314 | 320 | 326 | 327 | 328 | -------------------------------------------------------------------------------- /cmake/FetchJSONlib.cmake: -------------------------------------------------------------------------------- 1 | function(FECTH_JSON_LIB) 2 | if (NOT TARGET nlohmann_json::nlohmann_json) 3 | message("fetching nlohmann") 4 | include(FetchContent) 5 | FetchContent_Declare( 6 | json_lib 7 | GIT_REPOSITORY https://github.com/nlohmann/json.git 8 | GIT_TAG v3.10.5 9 | ) 10 | FetchContent_MakeAvailable(json_lib) 11 | endif() 12 | endfunction() 13 | -------------------------------------------------------------------------------- /cmake/GCov.cmake: -------------------------------------------------------------------------------- 1 | option(GCOV_LOG_ENABLED "" OFF) 2 | 3 | if(GCOV_LOG_ENABLED) 4 | SET(GCC_COVERAGE_COMPILE_FLAGS "-fprofile-arcs -ftest-coverage") 5 | SET(GCC_COVERAGE_LINK_FLAGS "-lgcov --coverage") 6 | 7 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}") 8 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") 9 | endif() 10 | -------------------------------------------------------------------------------- /cmake/MakeLibrary.cmake: -------------------------------------------------------------------------------- 1 | function(MakeLibrary LIBRARY_NAME INCLUDE_DIR) 2 | file(GLOB_RECURSE SOURCES 3 | ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp 4 | ${CMAKE_CURRENT_SOURCE_DIR}/*.h 5 | ${CMAKE_CURRENT_SOURCE_DIR}/*.hpp 6 | ${CMAKE_CURRENT_SOURCE_DIR}/*.hxx 7 | ) 8 | 9 | if(LIB_OPT) 10 | if (WIN32) 11 | set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS TRUE) 12 | endif () 13 | 14 | add_library(${LIBRARY_NAME} SHARED ${SOURCES}) 15 | else() 16 | add_library(${LIBRARY_NAME} STATIC ${SOURCES}) 17 | endif() 18 | 19 | target_include_directories(${LIBRARY_NAME} PUBLIC 20 | ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_DIR} 21 | ) 22 | 23 | install(TARGETS ${LIBRARY_NAME}) 24 | install (DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_DIR}/ DESTINATION include/${LIBRARY_NAME} FILES_MATCHING PATTERN "*.h*") 25 | endfunction() 26 | -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(Sample-utils) 2 | 3 | include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/make-sample.cmake) 4 | 5 | # # add_subdirectory(Sample-README) # only used to produce the documentation 6 | 7 | add_subdirectory(Sample-01-big-vector-samples) 8 | add_subdirectory(Sample-02-long-text-process) 9 | add_subdirectory(Sample-03-concurrent-update-read) 10 | add_subdirectory(Sample-04-collisions-detection) 11 | -------------------------------------------------------------------------------- /samples/Sample-01-big-vector-samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | MAKE_SAMPLE() 2 | -------------------------------------------------------------------------------- /samples/Sample-01-big-vector-samples/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | class EmptySamplesVector : public std::runtime_error { 13 | public: 14 | EmptySamplesVector() : std::runtime_error{"No samples in vector"} {} 15 | }; 16 | 17 | std::vector make_samples(std::size_t size) { 18 | if (0 == size) { 19 | throw EmptySamplesVector{}; 20 | } 21 | flw::sample::Sampler sampler(-5, 5); 22 | std::vector samples; 23 | samples.reserve(size); 24 | for (std::size_t k = 0; k < size; ++k) { 25 | samples.push_back(sampler.sample()); 26 | } 27 | return samples; 28 | } 29 | 30 | float compute_std_dev(const std::vector &samples, float mean) { 31 | float variance = 0.f; 32 | float coeff = 1.f / static_cast(samples.size()); 33 | float diff; 34 | for (const auto &sample : samples) { 35 | diff = (sample - mean); 36 | variance += coeff * diff * diff; 37 | } 38 | return sqrtf(variance); 39 | } 40 | 41 | int main() { 42 | ////////////////////////////////////////////////////// 43 | // build a new flow 44 | flw::Flow flow; 45 | flow.setOnNewNodePolicy(flw::HandlerMaker::OnNewNodePolicy::DEFERRED_UPDATE); 46 | 47 | // define the source, which will be the number of samples to draw 48 | auto samplesNumber = flow.makeSource(100000); 49 | 50 | // define the node containing the samples 51 | // in this case we want to keep track of a particular exception that can be 52 | // throwned 53 | bool had_zero_samples = false; 54 | auto samples = flow.makeNodeWithErrorsCB, std::size_t>( 55 | &make_samples, samplesNumber, 56 | flw::ValueCallBacks, EmptySamplesVector>{} 57 | .addOnError( 58 | [&had_zero_samples](const EmptySamplesVector &) { 59 | had_zero_samples = true; 60 | }) 61 | .extract(), 62 | "Samples"); 63 | 64 | // we are going to register 2 callbacks to catch the results from the network 65 | float computedMean; 66 | float computedStdDev; 67 | 68 | // define the node storing the mean of the samples 69 | auto samplesMean = flow.makeNode>( 70 | [](const std::vector &samples) { 71 | float mean = 0.f; 72 | float coeff = 1.f / static_cast(samples.size()); 73 | for (const auto &sample : samples) { 74 | mean += coeff * sample; 75 | } 76 | return mean; 77 | }, 78 | samples, "Mean", [&computedMean](float val) { computedMean = val; }); 79 | 80 | // define the node storing the standard deviation of the samples 81 | auto samplesStdDev = flow.makeNode, float>( 82 | &compute_std_dev, samples, samplesMean, "StdDeviation", 83 | [&computedStdDev](float val) { computedStdDev = val; }); 84 | 85 | // update the flow ... done now as we opted for a 86 | // flw::HandlerMaker::OnNewNodePolicy::DEFERRED_UPDATE 87 | flow.update(); 88 | 89 | std::cout << "Mean: " << computedMean 90 | << " Std deviation: " << computedStdDev << std::endl 91 | << std::endl; 92 | 93 | // take a snapshot of the network and export it as a .dot file 94 | flw::Converter::toFile( 95 | flw::sample::LogDir::get() / "Flow-Sample-01.dot", flow.snapshot()); 96 | // use python graphviz to render exported .dot file 97 | flw::sample::RunScript::runDefaultScript("Flow-Sample-01.dot"); 98 | 99 | ////////////////////////////////////////////////////// 100 | // update the source with a bad input 101 | samplesNumber.update(0); 102 | flow.update(); 103 | 104 | // check that the correct exception was stored 105 | if (had_zero_samples) { 106 | std::cout << std::endl 107 | << std::endl 108 | << "Correctly, a EmptySamplesVector exception was stored" 109 | << std::endl; 110 | } else { 111 | std::cout << std::endl 112 | << std::endl 113 | << "Something went wrong storing the exception" << std::endl; 114 | } 115 | 116 | return EXIT_SUCCESS; 117 | } 118 | -------------------------------------------------------------------------------- /samples/Sample-01-big-vector-samples/README.md: -------------------------------------------------------------------------------- 1 | Basic usage of **DynamicFlow**. Shows how to build a small network able to process a buffer of samples and compute statistics on them. A single source is part of the flow and stored the samples to process. 2 | 3 | The network is rendered with a python script using graphviz (which is expected to have already been installed with **pip**). Notice that the network rendering is blocking and you should close the generated window and press enter to progress and get to the end. 4 | 5 | Notice that a custom exception, `EmptySamplesVector` is registered for one of the node. 6 | 7 | Firstly, a valid source of samples is set and the update of the entire flow is done. 8 | Then, the same update of the flow is tried with a bad empty collection of samples with the only purpose of showing that an `EmptySamplesVector` exception will be correctly stored and will prevent the update of some dependant nodes. 9 | -------------------------------------------------------------------------------- /samples/Sample-01-big-vector-samples/src/Sampler.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | namespace flw::sample { 6 | Sampler::Sampler(const int min, const int max, const long long &seed) 7 | : distribution(std::make_unique(min, max)) { 8 | if (0 == seed) { 9 | this->generator.seed( 10 | std::chrono::system_clock::now().time_since_epoch().count()); 11 | } else { 12 | this->generator.seed(seed); 13 | } 14 | }; 15 | 16 | float Sampler::sample() const { 17 | return (*this->distribution)(this->generator); 18 | }; 19 | } // namespace flw::sample 20 | -------------------------------------------------------------------------------- /samples/Sample-01-big-vector-samples/src/Sampler.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace flw::sample { 7 | class Sampler { 8 | public: 9 | Sampler(const int min, const int max, const long long &seed = 0); 10 | 11 | float sample() const; 12 | 13 | private: 14 | mutable std::default_random_engine generator; 15 | 16 | using Distr = std::uniform_int_distribution<>; 17 | using DistrPtr = std::unique_ptr>; 18 | const DistrPtr distribution; 19 | }; 20 | } // namespace flw::sample 21 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | MAKE_SAMPLE() 2 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | void build_network(flw::Flow &flow); 13 | 14 | static const std::string FIRST_TEXT = "LoremIpsum"; 15 | static const std::string SECOND_TEXT = "Novel"; 16 | 17 | int main() { 18 | // build the flow 19 | flw::Flow flow; 20 | flow.setOnNewNodePolicy(flw::HandlerMaker::OnNewNodePolicy::DEFERRED_UPDATE); 21 | build_network(flow); 22 | 23 | // take a snapshot of the network and export it as a .dot file 24 | flw::Converter::toFile( 25 | flw::sample::LogDir::get() / "Flow-Sample-02.dot", flow.snapshot()); 26 | // use python graphviz to render exported .dot file 27 | flw::sample::RunScript::runDefaultScript("Flow-Sample-02.dot"); 28 | 29 | auto fileNameSource = flow.findSource("fileName"); 30 | 31 | // process the first text 32 | fileNameSource.update(FIRST_TEXT); 33 | flow.update(); 34 | 35 | // now a non existing file ... a call back on the FileNotFound exception was 36 | // registered and should be printed 37 | fileNameSource.update("InexistentFile"); 38 | flow.update(); 39 | 40 | // process the second text 41 | fileNameSource.update(SECOND_TEXT); 42 | flow.update(); 43 | 44 | return EXIT_SUCCESS; 45 | } 46 | 47 | void append_analysis_nodes(flw::Flow &flow) { 48 | auto fileName = flow.makeSource("", "fileName"); 49 | 50 | auto content = flow.makeNodeWithErrorsCB, std::string>( 51 | [](const std::string &file_name) { 52 | return flw::sample::importText(file_name); 53 | }, 54 | fileName, 55 | flw::ValueCallBacks, flw::sample::FileNotFound>{} 56 | .addOnError( 57 | [](const flw::sample::FileNotFound &e) { 58 | std::cout << e.what() << std::endl; 59 | }) 60 | .extract(), 61 | "content"); 62 | 63 | auto linesCounter = flow.makeNode>( 64 | [](const std::list &content) { return content.size(); }, 65 | content, "lines-counter"); 66 | 67 | auto spacesCounter = flow.makeNode>( 68 | [](const std::list &content) { 69 | std::size_t result = 0; 70 | for (const auto &line : content) { 71 | result += flw::sample::countSpaces(line); 72 | } 73 | return result; 74 | }, 75 | content, "spaces-counter"); 76 | 77 | auto wordsByLine = flow.makeNode>, 78 | std::list>( 79 | [](const std::list &content) { 80 | std::vector> result; 81 | result.reserve(content.size()); 82 | for (const auto &line : content) { 83 | result.push_back(flw::sample::parseWords(line)); 84 | } 85 | return result; 86 | }, 87 | content, "words-by-lines"); 88 | 89 | auto wordsFrequencies = flow.makeNode, 90 | std::vector>>( 91 | [](const std::vector> &wordsByLine) { 92 | std::map result; 93 | for (const auto &line : wordsByLine) { 94 | for (const auto &word : line) { 95 | auto it = result.find(word); 96 | if (it == result.end()) { 97 | result.emplace(word, 1); 98 | } else { 99 | ++it->second; 100 | } 101 | } 102 | } 103 | return result; 104 | }, 105 | wordsByLine, "words-frequencies"); 106 | 107 | auto wordsCounter = 108 | flow.makeNode>( 109 | [](const std::map &wordsFrequencies) { 110 | std::size_t result = 0; 111 | for (auto it = wordsFrequencies.begin(); 112 | it != wordsFrequencies.end(); ++it) { 113 | result += it->second; 114 | } 115 | return result; 116 | }, 117 | wordsFrequencies, "words-counter"); 118 | } 119 | 120 | void append_exporter_node(flw::Flow &flow) { 121 | auto fileName = flow.findSource("fileName"); 122 | auto linesCounter = flow.findNode("lines-counter"); 123 | auto spacesCounter = flow.findNode("spaces-counter"); 124 | auto wordsCounter = flow.findNode("words-counter"); 125 | auto wordsFrequencies = 126 | flow.findNode>("words-frequencies"); 127 | 128 | flow.makeNode>( 130 | [](const std::string &fileName, const std::size_t &lines, 131 | const std::size_t &spaces, const std::size_t &words, 132 | const std::map &frequencies) { 133 | std::string outputFile = fileName + "_results.log"; 134 | 135 | auto stream = flw::sample::make_out_stream(outputFile); 136 | 137 | *stream.get() << fileName << " analysis" << std::endl; 138 | 139 | *stream.get() << "<<<------------------>>>" << std::endl; 140 | 141 | *stream.get() << "lines: " << lines << std::endl; 142 | 143 | *stream.get() << "spaces: " << spaces << std::endl; 144 | 145 | *stream.get() << "words: " << words << std::endl; 146 | 147 | *stream.get() << "<<<------------------>>>" << std::endl; 148 | 149 | *stream.get() << "words frequencies: " << std::endl; 150 | for (auto it = frequencies.begin(); it != frequencies.end(); ++it) { 151 | *stream.get() << it->first << " : " << it->second << std::endl; 152 | } 153 | 154 | return outputFile; 155 | }, 156 | fileName, linesCounter, spacesCounter, wordsCounter, wordsFrequencies, "", 157 | [](const std::filesystem::path &outputFile) { 158 | std::cout << " done, results written in " 159 | << std::filesystem::current_path() / outputFile << std::endl; 160 | }); 161 | } 162 | 163 | void build_network(flw::Flow &flow) { 164 | append_analysis_nodes(flow); 165 | append_exporter_node(flow); 166 | } 167 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/README.md: -------------------------------------------------------------------------------- 1 | The sample shows how to build a pipeline processing textual files. 2 | The files should be imported and then some statistics are computed, like the number of words that part of the text and others. 3 | Statistics are then persisted into log file, that you can inspect after running the sample application. 4 | 5 | The network is rendered with a python script using graphviz (which is expected to have already been installed with **pip**). Notice that the network rendering is blocking and you should close the generated window and press enter to progress and get to the end. 6 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/assets/LoremIpsum: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque felis sem, commodo vel feugiat ac, imperdiet feugiat tortor. Nullam in odio vel purus pretium gravida sit amet eget dui. Nulla accumsan mi nisi. 2 | Ut tempus finibus leo et sagittis. Etiam nec magna ante. Duis arcu lorem, tincidunt vitae tincidunt in, suscipit at massa. Fusce feugiat ligula congue sem dapibus, et dignissim lorem scelerisque. 3 | Vivamus eu fringilla urna. Nunc sed dui ullamcorper, ultrices risus vitae, vehicula felis. 4 | 5 | Curabitur in pellentesque justo, sit amet sagittis ligula. Mauris sit amet nisi ut ligula auctor rutrum vitae nec velit. Aliquam congue tortor nec quam pulvinar molestie. Vivamus et urna in erat porta gravida. 6 | Suspendisse eros nunc, imperdiet sed posuere id, suscipit at nunc. Duis id velit enim. Morbi non rhoncus lorem, a semper sem. Mauris vestibulum tempor elementum. Mauris quis nulla a magna mattis congue. 7 | Aenean fermentum volutpat velit vitae rutrum. Praesent ornare massa ac dui tempus, nec euismod eros euismod. Aenean massa tellus, auctor in felis non, eleifend sodales neque. 8 | Curabitur ultricies faucibus purus, non rutrum tortor semper et. Aenean id ligula sapien. Praesent a euismod dolor. Vivamus id eleifend orci, non vestibulum quam. 9 | 10 | Maecenas vitae nisi suscipit, porttitor velit non, commodo urna. Suspendisse nunc felis, dapibus vel tincidunt eget, placerat vel justo. Sed maximus nisl eget consequat faucibus. Duis id imperdiet velit. 11 | Sed odio erat, accumsan quis mattis nec, mattis in sapien. Duis imperdiet id purus ut ullamcorper. Phasellus vestibulum, nunc sed porttitor viverra, felis mauris sodales velit, quis bibendum odio mauris et est. Nullam eleifend auctor nibh. 12 | 13 | Cras placerat varius venenatis. Sed non scelerisque velit. Praesent sit amet quam magna. Morbi quis posuere urna. Mauris dignissim et enim sit amet elementum. Duis accumsan dolor risus, non congue lectus imperdiet sit amet. 14 | Cras tempus in nunc sit amet ornare. Nunc dui lacus, semper ut sollicitudin sit amet, facilisis non ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Mauris luctus commodo tortor, et venenatis eros pharetra eu. 15 | 16 | Quisque tristique mattis nulla ut euismod. Etiam sit amet metus id ante faucibus ultrices ut ut quam. Proin lectus nibh, tempor ac risus quis, molestie placerat felis. Duis porta mauris neque. In et ornare nibh. 17 | Nunc hendrerit augue in bibendum facilisis. Nulla quis leo mi. Maecenas porttitor tellus sit amet nunc placerat ultrices vitae sagittis magna. Morbi massa orci, elementum in risus ac, interdum dapibus leo. 18 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/assets/Novel: -------------------------------------------------------------------------------- 1 | Chapter One 2 | A Stop on the Salt Route 3 | 1000 B.C. 4 | As they rounded a bend in the path that ran beside the river, Lara recognized the silhouette of a fig tree atop a nearby hill. The weather was hot and the days were long. The fig tree was in full leaf, but not yet bearing fruit. 5 | Soon Lara spotted other landmarks—an outcropping of limestone beside the path that had a silhouette like a man’s face, a marshy spot beside the river where the waterfowl were easily startled, a tall tree that looked like a man with his arms upraised. They were drawing near to the place where there was an island in the river. The island was a good spot to make camp. They would sleep on the island tonight. 6 | Lara had been back and forth along the river path many times in her short life. Her people had not created the path—it had always been there, like the river—but their deerskin-shod feet and the wooden wheels of their handcarts kept the path well worn. Lara’s people were salt traders, and their livelihood took them on a continual journey. 7 | At the mouth of the river, the little group of half a dozen intermingled families gathered salt from the great salt beds beside the sea. They groomed and sifted the salt and loaded it into handcarts. When the carts were full, most of the group would stay behind, taking shelter amid rocks and simple lean-tos, while a band of fifteen or so of the heartier members set out on the path that ran alongside the river. 8 | With their precious cargo of salt, the travelers crossed the coastal lowlands and traveled toward the mountains. But Lara’s people never reached the mountaintops; they traveled only as far as the foothills. Many people lived in the forests and grassy meadows of the foothills, gathered in small villages. In return for salt, these people would give Lara’s people dried meat, animal skins, cloth spun from wool, clay pots, needles and scraping tools carved from bone, and little toys made of wood. 9 | Their bartering done, Lara and her people would travel back down the river path to the sea. The cycle would begin again. 10 | It had always been like this. Lara knew no other life. She traveled back and forth, up and down the river path. No single place was home. She liked the seaside, where there was always fish to eat, and the gentle lapping of the waves lulled her to sleep at night. She was less fond of the foothills, where the path grew steep, the nights could be cold, and views of great distances made her dizzy. She felt uneasy in the villages, and was often shy around strangers. The path itself was where she felt most at home. She loved the smell of the river on a hot day, and the croaking of frogs at night. Vines grew amid the lush foliage along the river, with berries that were good to eat. Even on the hottest day, sundown brought a cool breeze off the water, which sighed and sang amid the reeds and tall grasses. 11 | Of all the places along the path, the area they were approaching, with the island in the river, was Lara’s favorite. 12 | The terrain along this stretch of the river was mostly flat, but in the immediate vicinity of the island, the land on the sunrise side was like a rumpled cloth, with hills and ridges and valleys. Among Lara’s people, there was a wooden baby’s crib, suitable for strapping to a cart, that had been passed down for generations. The island was shaped like that crib, longer than it was wide and pointed at the upriver end, where the flow had eroded both banks. The island was like a crib, and the group of hills on the sunrise side of the river were like old women mantled in heavy cloaks gathered to have a look at the baby in the crib—that was how Lara’s father had once described the lay of the land. 13 | Larth spoke like that all the time, conjuring images of giants and monsters in the landscape. He could perceive the spirits, called numina, that dwelled in rocks and trees. Sometimes he could speak to them and hear what they had to say. The river was his oldest friend and told him where the fishing would be best. From whispers in the wind he could foretell the next day’s weather. Because of such skills, Larth was the leader of the group. 14 | “We’re close to the island, aren’t we, Papa?” said Lara. 15 | “How did you know?” 16 | “The hills. First we start to see the hills, off to the right. The hills grow bigger. And just before we come to the island, we can see the silhouette of that fig tree up there, along the crest of that hill.” 17 | “Good girl!” said Larth, proud of his daughter’s memory and powers of observation. He was a strong, handsome man with flecks of gray in his black beard. His wife had borne several children, but all had died very young except Lara, the last, whom his wife had died bearing. Lara was very precious to him. Like her mother, she had golden hair. Now that she had reached the age of childbearing, Lara was beginning to display the fullness of a woman’s hips and breasts. It was Larth’s greatest wish that he might live to see his own grandchildren. Not every man lived that long, but Larth was hopeful. He had been healthy all his life, partly, he believed, because he had always been careful to show respect to the numina he encountered on his journeys. 18 | Respecting the numina was important. The numen of the river could suck a man under and drown him. The numen of a tree could trip a man with its roots, or drop a rotten branch on his head. Rocks could give way underfoot, chuckling with amusement at their own treachery. Even the sky, with a roar of fury, sometimes sent down fingers of fire that could roast a man like a rabbit on a spit, or worse, leave him alive but robbed of his senses. Larth had heard that the earth itself could open and swallow a man; though he had never actually seen such a thing, he nevertheless performed a ritual each morning, asking the earth’s permission before he went striding across it. 19 | “There’s something so special about this place,” said Lara, gazing at the sparkling river to her left and then at the rocky, tree-spotted hills ahead and to her right. “How was it made? Who made it?” 20 | Larth frowned. The question made no sense to him. A place was never made, it simply was. Small features might change over time. Uprooted by a storm, a tree might fall into the river. A boulder might decide to tumble down the hillside. The numina that animated all things went about reshaping the landscape from day to day, but the essential things never changed, and had always existed: the river, the hills, the sky, the sun, the sea, the salt beds at the mouth of the river. 21 | He was trying to think of some way to express these thoughts to Lara, when a deer, drinking at the river, was startled by their approach. The deer bolted up the brushy bank and onto the path. Instead of running to safety, the creature stood and stared at them. As clearly as if the animal had whispered aloud, Larth heard the words “Eat me.” The deer was offering herself. 22 | Larth turned to shout an order, but the most skilled hunter of the group, a youth called Po, was already in motion. Po ran forward, raised the sharpened stick he always carried and hurled it whistling through the air between Larth and Lara. 23 | A heartbeat later, the spear struck the deer’s breast with such force that the creature was knocked to the ground. Unable to rise, she thrashed her neck and flailed her long, slender legs. Po ran past Larth and Lara. When he reached the deer, he pulled the spear free and stabbed the creature again. The deer released a stifled noise, like a gasp, and stopped moving. 24 | There was a cheer from the group. Instead of yet another dinner of fish from the river, tonight there would be venison. 25 | The distance from the riverbank to the island was not great, but at this time of year—early summer—the river was too high to wade across. Lara’s people had long ago made simple rafts of branches lashed together with leather thongs, which they left on the riverbanks, repairing and replacing them as needed. When they last passed this way, there had been three rafts, all in good condition, left on the east bank. Two of the rafts were still there, but one was missing. 26 | “I see it! There—pulled up on the bank of the island, almost hidden among those leaves,” said Po, whose eyes were sharp. “Someone must have used it to cross over.” 27 | “Perhaps they’re still on the island,” said Larth. He did not begrudge others the use of the rafts, and the island was large enough to share. Nonetheless, the situation required caution. He cupped his hands to his mouth and gave a shout. It was not long before a man appeared on the bank of the island. The man waved. 28 | “Do we know him?” said Larth, squinting. 29 | “I don’t think so,” said Po. “He’s young—my age or younger, I’d say. He looks strong.” 30 | “Very strong!” said Lara. Even from this distance, the young stranger’s brawniness was impressive. He wore a short tunic without sleeves, and Lara had never seen such arms on a man. 31 | Po, who was small and wiry, looked at Lara sidelong and frowned. “I’m not sure I like the look of this stranger.” 32 | “Why not?” said Lara. “He’s smiling at us.” 33 | In fact, the young man was smiling at Lara, and Lara alone. 34 | His name was Tarketios. Much more than that, Larth could not tell, for the stranger spoke a language which Larth did not recognize, in which each word seemed as long and convoluted as the man’s name. Understanding the deer had been easier than understanding the strange noises uttered by this man and his two companions! Even so, they seemed friendly, and the three of them presented no threat to the more numerous salt traders. 35 | Tarketios and his two older companions were skilled metalworkers from a region some two hundred miles to the north, where the hills were rich with iron, copper, and lead. They had been on a trading journey to the south and were returning home. Just as the river path carried Larth’s people from the seashore to the hills, so another path, perpendicular to the river, traversed the long coastal plain. Because the island provided an easy place to ford the river, it was here that the two paths intersected. On this occasion, the salt traders and the metal traders happened to arrive at the island on the same day. Now they met for the first time. 36 | The two groups made separate camps at opposite ends of the island. As a gesture of friendship, speaking with his hands, Larth invited Tarketios and the others to share the venison that night. As the hosts and their guests feasted around the roasting fire, Tarketios tried to explain something of his craft. Firelight glittered in Lara’s eyes as she watched Tarketios point at the flames and mime the act of hammering. Firelight danced across the flexing muscles of his arms and shoulders. When he smiled at her, his grin was like a boast. She had never seen teeth so white and so perfect. 37 | Po saw the looks the two exchanged and frowned. Lara’s father saw the same looks and smiled. 38 | The meal was over. The metal traders, after many gestures of gratitude for the venison, withdrew to their camp at the far side of the island. Before he disappeared into the shadows, Tarketios looked over his shoulder and gave Lara a parting grin. 39 | While the others settled down to sleep, Larth stayed awake a while longer, as was his habit. He liked to watch the fire. Like all other things, fire possessed a numen that sometimes communicated with him, showing him visions. As the last of the embers faded into darkness, Larth fell asleep. 40 | Larth blinked. The flames, which had dwindled to almost nothing, suddenly shot up again. Hot air rushed over his face. His eyes were seared by white flames brighter than the sun. 41 | Amid the dazzling brightness, he perceived a thing that levitated above the flames. It was a masculine member, disembodied but nonetheless rampant and upright. It bore wings, like a bird, and hovered in midair. Though it seemed to be made of flesh, it was impervious to the flames. 42 | Larth had seen the winged phallus before, always in such circumstances, when he stared at a fire and entered a dream state. He had even given it a name, or more precisely, the thing had planted its name in his mind: Fascinus. 43 | Fascinus was not like the numina that animated trees, stones, or rivers. Those numina existed without names. Each was bound to the object in which it resided, and there was little to differentiate one from another. When such numina spoke, they could not always be trusted. Sometimes they were friendly, but at other times they were mischievous or even hostile. 44 | Fascinus was different. It was unique. It existed in and of itself, without beginning or end. Clearly, from its form, it had something to do with life and the origin of life, yet it seemed to come from a place beyond this world, slipping for a few moments through a breach opened by the heat of the dancing flames. An appearance by Fascinus was always significant. The winged phallus never appeared without giving Larth an answer to a dilemma that had been troubling him, or planting an important new thought in his mind. The guidance given to him by Fascinus had never led Larth astray. 45 | Elsewhere, in distant lands—Greece, Israel, Egypt—men and women worshiped gods and goddesses. Those people made images of their gods, told stories about them, and worshiped them in temples. Larth had never met such people. He had never even heard of the lands where they lived, and he had never encountered or conceived of a god. The very concept of a deity such as those other men worshiped was unknown to Larth, but the closest thing to a god in his imagination and experience was Fascinus. 46 | With a start, he blinked again. 47 | The flames had died. In place of intolerable brightness there was only the darkness of a warm summer night lit by the faintest sliver of a moon. The air on his face was no longer hot but fresh and cool. 48 | Fascinus had vanished—but not without planting a thought in Larth’s mind. He hurried to the leafy bower beside the river where Lara liked to sleep, thinking to himself, It must be made so, because Fascinus says it must! 49 | He knelt beside her, but there was no need to wake her. She was already awake. 50 | “Papa? What is it?” 51 | “Go to him!” 52 | She did not need to ask for an explanation. It was what she had been yearning to do, lying restless and eager in the dark. 53 | “Are you sure, Papa?” 54 | “Fascinus . . . ,” He did not finish the thought, but she understood. She had never seen Fascinus, but he had told her about it. Many times in the past, Fascinus had given guidance to her father. Now, once again, Fascinus had made its will known. 55 | The darkness did not deter her. She knew every twist and turn of every path on the little island. When she came to the metal trader’s camp, she found Tarketios lying in a leafy nook secluded from the others; she recognized him by his brawny silhouette. He was awake and waiting, just as she had been lying awake, waiting, when her father came to her. 56 | At her approach, Tarketios rose onto his elbows. He spoke her name in a whisper. There was a quiver of something like desperation in his voice; his neediness made her smile. She sighed and lowered herself beside him. By the faint moonlight, she saw that he wore an amulet of some sort, suspended from a strap of leather around his neck. Nestled amid the hair on his chest, the bit of shapeless metal seemed to capture and concentrate the faint moonlight, casting back a radiance brighter than the moon itself. 57 | His arms—the arms she had so admired earlier—reached out and closed around her in a surprisingly gentle embrace. His body was as warm and naked as her own, but much bigger and much harder. She wondered if Fascinus was with them in the darkness, for she seemed to feel the beating of wings between their legs as she was entered by the thing that gave origin to life. 58 | Copyright © 2007 by Steven Saylor. All rights reserved. 59 | 60 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/src/TextIO.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace flw::sample { 5 | namespace { 6 | template 7 | std::unique_ptr make_stream(const std::filesystem::path &filePath) { 8 | auto result = std::make_unique(filePath); 9 | if (!result->is_open()) { 10 | throw FileNotFound{filePath}; 11 | } 12 | return result; 13 | } 14 | } // namespace 15 | 16 | std::unique_ptr make_in_stream(const std::filesystem::path &filePath) { 17 | return make_stream(filePath); 18 | } 19 | 20 | std::unique_ptr make_out_stream(const std::filesystem::path &filePath) { 21 | return make_stream(filePath); 22 | } 23 | 24 | std::list importText(const std::filesystem::path &fileName) { 25 | auto stream = make_in_stream(std::filesystem::path{ASSET_FOLDER} / fileName); 26 | 27 | std::list result; 28 | while (!stream->eof()) { 29 | result.emplace_back(); 30 | getline(*stream, result.back()); 31 | } 32 | 33 | return result; 34 | } 35 | 36 | void exportText(const std::list &content, 37 | const std::filesystem::path &filePath) { 38 | auto stream = make_out_stream(filePath); 39 | for (const auto &line : content) { 40 | *stream << line << std::endl; 41 | } 42 | } 43 | } // namespace flw::sample 44 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/src/TextIO.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace flw::sample { 11 | class FileNotFound : public std::runtime_error { 12 | public: 13 | FileNotFound(const std::filesystem::path &filePath) 14 | : std::runtime_error{filePath.string() + " : is an invalid file name"} {}; 15 | }; 16 | 17 | std::unique_ptr make_in_stream(const std::filesystem::path &filePath); 18 | std::unique_ptr make_out_stream(const std::filesystem::path &filePath); 19 | 20 | std::list importText(const std::filesystem::path &fileName); 21 | 22 | void exportText(const std::list &content, 23 | const std::filesystem::path &filePath); 24 | 25 | } // namespace flw::sample 26 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/src/WordsParser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace flw::sample { 6 | std::size_t countSpaces(const std::string &line) { 7 | return std::count(line.begin(), line.end(), ' '); 8 | } 9 | 10 | std::list parseWords(const std::string &line) { 11 | std::list result; 12 | std::string formingWord; 13 | for (auto c : line) { 14 | if (' ' == c) { 15 | if (!formingWord.empty()) { 16 | result.push_back(formingWord); 17 | } 18 | formingWord.clear(); 19 | } else { 20 | formingWord += c; 21 | } 22 | } 23 | if(!formingWord.empty()) { 24 | result.push_back(formingWord); 25 | } 26 | return result; 27 | } 28 | } // namespace flw::sample 29 | -------------------------------------------------------------------------------- /samples/Sample-02-long-text-process/src/WordsParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace flw::sample { 7 | std::size_t countSpaces(const std::string &line); 8 | 9 | std::list parseWords(const std::string &line); 10 | } // namespace flw::sample -------------------------------------------------------------------------------- /samples/Sample-03-concurrent-update-read/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | MAKE_SAMPLE() 2 | -------------------------------------------------------------------------------- /samples/Sample-03-concurrent-update-read/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | std::atomic_bool life = true; 13 | 14 | void handle(int sig) { 15 | life.store(false, std::memory_order_release); 16 | } 17 | 18 | int main() { 19 | signal(SIGINT, handle); 20 | signal(SIGABRT, handle); 21 | signal(SIGSEGV, handle); 22 | signal(SIGTERM, handle); 23 | 24 | flw::Flow flow; 25 | 26 | std::cout << "Logging sum values at: " << flw::sample::LogDir::get() / "sum.log" << std::endl; 27 | flw::sample::ValueListener sum_listener{flw::sample::LogDir::get() / "sum.log", std::chrono::milliseconds(50)}; 28 | 29 | std::cout << "Logging diff values at: " << flw::sample::LogDir::get() / "diff.log" << std::endl; 30 | flw::sample::ValueListener diff_listener{flw::sample::LogDir::get() / "diff.log", std::chrono::milliseconds(50)}; 31 | 32 | // build a simple flow with 2 int as sources and 2 nodes representing their 33 | // sum and difference 34 | auto sampleA = flow.makeSource(0, "SampleA"); 35 | auto sampleB = flow.makeSource(0, "SampleB"); 36 | auto sum_node = flow.makeNode( 37 | [](int a, int b) { 38 | return a + b; 39 | }, sampleA, sampleB, "", [&sum_listener](int val) { 40 | sum_listener.onNewValue(val); 41 | }); 42 | auto diff_node = flow.makeNode( 43 | [](int a, int b) { 44 | return a - b; 45 | }, sampleA, sampleB, "", [&diff_listener](int val) { 46 | diff_listener.onNewValue(val); 47 | }); 48 | 49 | std::vector threads; 50 | // spawn the thread updating the flow 51 | threads.emplace_back([&flow](std::stop_token token) { 52 | while(!token.stop_requested()) { 53 | flow.update(); 54 | std::this_thread::sleep_for(std::chrono::milliseconds(120)); 55 | } 56 | }); 57 | // spawn the updater of the first source 58 | threads.emplace_back([&sampleA](std::stop_token token){ 59 | while(!token.stop_requested()) { 60 | sampleA.update(rand() % 10 - 5); 61 | } 62 | }); 63 | // spawn the updater of the second source 64 | threads.emplace_back([&sampleB](std::stop_token token){ 65 | while(!token.stop_requested()) { 66 | sampleB.update(rand() % 10 - 5); 67 | } 68 | }); 69 | // spawn the periodic file dumpers 70 | threads.emplace_back(sum_listener.startAndTransfer()); 71 | threads.emplace_back(diff_listener.startAndTransfer()); 72 | 73 | std::cout << "press Ctrl+c to stop" << std::endl; 74 | while(life.load(std::memory_order_acquire)) { 75 | } 76 | std::cout << "stopping ... " << std::endl; 77 | threads.clear(); 78 | 79 | return EXIT_SUCCESS; 80 | } 81 | -------------------------------------------------------------------------------- /samples/Sample-03-concurrent-update-read/README.md: -------------------------------------------------------------------------------- 1 | A simple network with no particular meaning is built with the purpose to show how to thread-safely interact with **DynamicFlow**. 2 | 3 | In essence: 4 | - one thread continously updated the flow 5 | - two different threads continously update two distinct sources 6 | - values of the nodes are monitored and periodically logged to file 7 | 8 | Check the comments in the code for further details. 9 | -------------------------------------------------------------------------------- /samples/Sample-03-concurrent-update-read/src/ValueListener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace flw::sample { 4 | namespace { 5 | struct Timer { 6 | static Timer &get() { 7 | static Timer res; 8 | return res; 9 | } 10 | 11 | std::chrono::nanoseconds now() const { 12 | return std::chrono::duration_cast( 13 | std::chrono::system_clock::now() - start_tp); 14 | } 15 | 16 | private: 17 | Timer() = default; 18 | 19 | std::chrono::system_clock::time_point start_tp = 20 | std::chrono::system_clock::now(); 21 | }; 22 | } // namespace 23 | 24 | std::jthread ValueListener::startAndTransfer() { 25 | return std::jthread{[this](std::stop_token token) { 26 | while (!token.stop_requested()) { 27 | int val = last_value_.load(); 28 | 29 | time_t timestamp; 30 | time(×tamp); 31 | stream_ << std::chrono::duration_cast( 32 | Timer::get().now()) 33 | .count() 34 | << "[ms]: " << val << std::endl; 35 | 36 | std::this_thread::sleep_for(period_); 37 | } 38 | }}; 39 | } 40 | } // namespace flw::sample 41 | -------------------------------------------------------------------------------- /samples/Sample-03-concurrent-update-read/src/ValueListener.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace flw::sample { 10 | struct ValueListener { 11 | ValueListener(const std::filesystem::path& logFile, const std::chrono::milliseconds& period) 12 | : period_{period} 13 | , stream_{logFile} {} 14 | 15 | void onNewValue(int val) { 16 | last_value_.store(val); 17 | } 18 | 19 | std::jthread startAndTransfer(); 20 | 21 | private: 22 | std::chrono::milliseconds period_; 23 | std::ofstream stream_; 24 | std::atomic last_value_; 25 | }; 26 | } // namespace flw::sample 27 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | MAKE_SAMPLE() 2 | 3 | # external library for collision detection 4 | message("fetching gjk-epa") 5 | set(BUILD_GJK_EPA_SAMPLES OFF CACHE BOOL "" FORCE) 6 | include(FetchContent) 7 | FetchContent_Declare( 8 | gjk_epa 9 | GIT_REPOSITORY https://github.com/andreacasalino/Flexible-GJK-and-EPA 10 | GIT_TAG 8166dfb30c3fbaaa22d362651f25210e29a0c715 11 | ) 12 | FetchContent_MakeAvailable(gjk_epa) 13 | 14 | FECTH_JSON_LIB() 15 | 16 | target_link_libraries(DynamicFlow-Sample-04-collisions-detection PUBLIC 17 | GJK-EPA 18 | nlohmann_json::nlohmann_json 19 | ) 20 | 21 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "CollisionDetectionFlow.h" 7 | #include 8 | 9 | int main() { 10 | ////////////////////////////////////////////////////////////// 11 | // build the collision check network 12 | flw::sample::FlowHandler flow_handler; 13 | auto &flow = flow_handler.flow; 14 | 15 | auto &&[triangle_a_orientation, triangle_a_pos] = 16 | flow_handler.addPolygon(flw::sample::Polygon{1.f, 3}, "triangle_a"); 17 | auto &&[triangle_b_orientation, triangle_b_pos] = 18 | flow_handler.addPolygon(flw::sample::Polygon{1.f, 3}, "triangle_b"); 19 | auto &&[square_orientation, square_pos] = 20 | flow_handler.addPolygon(flw::sample::Polygon{1.f, 4}, "square"); 21 | auto &&[circle_orientation, circle_pos] = 22 | flow_handler.addPolygon(flw::sample::Polygon{1.f, 30}, "almost_circle"); 23 | flow_handler.finalizeFlowCreation(); 24 | 25 | // take a snapshot of the network and export it as a .dot file 26 | flw::Converter::toFile( 27 | flw::sample::LogDir::get() / "Flow-Sample-04.dot", flow.snapshot()); 28 | // use python graphviz to render exported .dot file 29 | flw::sample::RunScript::runDefaultScript("Flow-Sample-04.dot"); 30 | 31 | // sets multiple threads for the flow update 32 | flow.setThreads(3); 33 | 34 | ////////////////////////////////////////////////////////////// 35 | // first scenario -> no collisions at all 36 | square_orientation.update(flw::sample::to_rad(45.f)); 37 | triangle_a_pos.update(flw::sample::Point2D{-3.f, 0}); 38 | triangle_b_pos.update(flw::sample::Point2D{3.f, 1.5f}); 39 | triangle_b_orientation.update(flw::sample::to_rad(45.f)); 40 | circle_pos.update(flw::sample::Point2D{0, -3.f}); 41 | // recompute proximity queries 42 | flow.update(); 43 | // show results 44 | flw::sample::RunScript::runScript( 45 | std::filesystem::path{SCRIPT_FOLDER} / "ShowResults.py", "file", 46 | flw::sample::LogDir::get() / "Scenario_1.json"); 47 | 48 | ////////////////////////////////////////////////////////////// 49 | // second scenario -> a pair in collision the other no 50 | triangle_a_pos.update(flw::sample::Point2D{-1.5f, 0}); 51 | circle_pos.update(flw::sample::Point2D{3.f, -0.5f}); 52 | // recompute proximity queries 53 | // 54 | // Notice that some results will be cached from previous scenario as not all 55 | // polygon were moved 56 | flow.update(); 57 | // show results 58 | flw::sample::RunScript::runScript( 59 | std::filesystem::path{SCRIPT_FOLDER} / "ShowResults.py", "file", 60 | flw::sample::LogDir::get() / "Scenario_2.json"); 61 | 62 | return EXIT_SUCCESS; 63 | } 64 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/README.md: -------------------------------------------------------------------------------- 1 | This sample shows how to build a collision check pipeline. In this context, a certain number of convex 2D polygons are registered. As the shapes moves over time, the pipeline purpose is to check for each pair of polygons: 2 | 3 | * the closest pair of points in case they are not in collision 4 | 5 | * the penetration depth otherwise 6 | 7 | The collision checks are done using [**Flexible-GJK-and-EPA**](https://github.com/andreacasalino/Flexible-GJK-and-EPA), which is an external library automatically fetched for you by **CMake** when configuring this sample. The sources of the network are of course the polygon vertices (never changing) as well as their positions/orientations (clearly changing over the time). 8 | Notice that the number of polygons is parametric and the construction of the entire network is done by `FlowHandler`. 9 | 10 | More than one **thread** is used for the network update, as some computations for the pairs of polygons can be done in parallel. 11 | 12 | The network is rendered with a python script using graphviz (which is expected to have already been installed with **pip**). Notice that the network rendering is blocking and you should close the generated window and press enter to progress and get to the end. 13 | 14 | For each scenario in the sample, the polygons are moved to some specific location and the entire network is updated. After the last containing the results is updated, a results are logged into a **.json** file whose content is rendered with an external python script. 15 | What you will see in the opened window is the set of polygons whose color is set to; 16 | 17 | * green in case that polygon is not in collision with any other one 18 | 19 | * red otherwise 20 | 21 | Dashed lines connects the closest points of the pairs not in collsion. 22 | 23 | Basic usage of **DynamicFlow**. Shows how to build a small network able to process a buffer of samples and compute statistics on them. A single source is part of the flow and stored the samples to process. 24 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/script/ShowResults.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import argparse 4 | import math 5 | import json 6 | import matplotlib.pyplot as plt 7 | import matplotlib.path as mpath 8 | import matplotlib.patches as mpatches 9 | 10 | def import_json(name): 11 | with open(name) as json_file: 12 | return json.load(json_file) 13 | 14 | class AxisBox: 15 | def __init__(self): 16 | self.range=[0,0] 17 | 18 | def extend(self, val): 19 | if(self.range[1] < val): 20 | self.range[1] = val 21 | if(self.range[0] > val): 22 | self.range[0] = val 23 | 24 | def get(self): 25 | delta = 0.1* (self.range[1] - self.range[0]) 26 | return [self.range[0] - delta, self.range[1] + delta] 27 | 28 | class BoundingBox: 29 | def __init__(self): 30 | self.interval_x=AxisBox() 31 | self.interval_y=AxisBox() 32 | 33 | def extend(self, point): 34 | self.interval_x.extend(point[0]) 35 | self.interval_y.extend(point[1]) 36 | 37 | def printCorners(self, ax): 38 | range_x = self.interval_x.get() 39 | range_y = self.interval_y.get() 40 | ax.plot([range_x[0]],[range_y[0]], 'r') 41 | ax.plot([range_x[1]],[range_y[1]], 'r') 42 | 43 | limits = BoundingBox() 44 | 45 | def print_line(ax, start, end): 46 | segment_x = [start['x'], end['x']] 47 | segment_y = [start['y'], end['y']] 48 | ax.plot(segment_x, segment_y, '--', c='k', linewidth=0.5) 49 | limits.extend([start['x'], start['y']]) 50 | limits.extend([end['x'], end['y']]) 51 | 52 | def print_patch(ax, vertices, color): 53 | for vertex in vertices: 54 | limits.extend(vertex) 55 | path = mpath.Path(vertices) 56 | patch = mpatches.PathPatch(path, facecolor=color, alpha=1.0) 57 | ax.add_patch(patch) 58 | 59 | if __name__ == '__main__': 60 | parser = argparse.ArgumentParser() 61 | parser.add_argument('--file', required=True) 62 | args = parser.parse_args() 63 | 64 | to_print = import_json(args.file) 65 | 66 | fig, ax = plt.subplots() 67 | fig.suptitle(args.file, fontsize=14) 68 | for shape_name in to_print['Shapes']: 69 | shape = to_print['Shapes'][shape_name] 70 | print_patch(ax, shape['vertices'], shape['color']) 71 | # TODO print name of polygons close to them in the figure 72 | for line in to_print['Lines']: 73 | print_line(ax, line['diffA'], line['diffB']) 74 | 75 | limits.printCorners(ax) 76 | 77 | ax.axis('equal') 78 | plt.show() 79 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/src/CollisionDetectionFlow.cpp: -------------------------------------------------------------------------------- 1 | #include "CollisionDetectionFlow.h" 2 | #include "ResultsAccumulator.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace flw::sample { 12 | FlowHandler::PolygonHandler FlowHandler::addPolygon(Polygon &&polygon, 13 | const std::string &name) { 14 | { 15 | if (table_.contains(name)) { 16 | throw Error::make<0>("A polygon named ", name, 17 | " is already part of the network"); 18 | } 19 | } 20 | 21 | auto angle = flow.makeSource(0, name + "-angle"); 22 | auto center = flow.makeSource(Point2D{0, 0}, name + "-center"); 23 | 24 | auto polygon_source = 25 | flow.makeSource(std::forward(polygon), name); 26 | 27 | auto polygon_transformation = 28 | flow.makeNode( 29 | [](const float &angle, const Point2D ¢er) { 30 | return flx::shape::Transformation{ 31 | hull::Coordinate{center[0], center[1]}, 32 | flx::shape::RotationXYZ{0, 0, angle}}; 33 | }, 34 | angle, center, name + "-roto-traslation"); 35 | 36 | auto polygon_with_transformation = 37 | flow.makeNode( 39 | [](const Polygon &polygon, const flx::shape::Transformation &trsf) { 40 | return flx::shape::TransformDecorator{ 41 | std::make_unique(polygon), trsf}; 42 | }, 43 | polygon_source, polygon_transformation, name + "-transformed"); 44 | 45 | polygons_.emplace_front( 46 | PolygonWithPosition{name, polygon_source, polygon_with_transformation}); 47 | table_.emplace(polygons_.front().label.data(), polygons_.begin()); 48 | return PolygonHandler{angle, center}; 49 | } 50 | 51 | namespace { 52 | void print(const nlohmann::json &subject) { 53 | static std::size_t PRINT_COUNTER = 0; 54 | std::ostringstream filename_complete; 55 | filename_complete << "Scenario" << '_' << ++PRINT_COUNTER << ".json"; 56 | std::filesystem::path path_tot = LogDir::get() / filename_complete.str(); 57 | std::ofstream stream(path_tot); 58 | if (!stream.is_open()) { 59 | throw Error::make<' '>(path_tot, ": invalid log path"); 60 | } 61 | stream << subject.dump(); 62 | } 63 | } // namespace 64 | 65 | void FlowHandler::finalizeFlowCreation() { 66 | if (polygons_.empty()) { 67 | throw Error{"At least one polygon should be provided"}; 68 | } 69 | 70 | struct Check { 71 | Handler query; 72 | std::string name_a; 73 | HandlerSource source_a; 74 | std::string name_b; 75 | HandlerSource source_b; 76 | }; 77 | std::vector checks; 78 | for (auto a = polygons_.begin(); a != polygons_.end(); ++a) { 79 | auto b = a; 80 | ++b; 81 | for (; b != polygons_.end(); ++b) { 82 | std::ostringstream label; 83 | label << a->label << "-vs-" << b->label << "-gjk-result"; 84 | auto gjk_result = 85 | flow.makeNode( 87 | flx::get_closest_points_or_penetration_info, a->decorator, 88 | b->decorator, label.str()); 89 | checks.emplace_back( 90 | Check{gjk_result, a->label, a->shape, b->label, b->shape}); 91 | } 92 | } 93 | 94 | using Handler = Handler; 95 | std::optional accumulator; 96 | for (const auto &polygon : polygons_) { 97 | const auto &name = polygon.label; 98 | if (!accumulator.has_value()) { 99 | accumulator.emplace( 100 | flow.makeNode( 101 | [name](const flx::shape::TransformDecorator &res) { 102 | ResultsAccumulator result; 103 | result.add(name, res); 104 | return result; 105 | }, 106 | polygon.decorator, "JSON-" + name)); 107 | } else { 108 | auto &prev = accumulator.value(); 109 | accumulator.emplace( 110 | flow.makeNode( 112 | [name](const flx::shape::TransformDecorator &res, 113 | const ResultsAccumulator &prev) { 114 | ResultsAccumulator result{prev}; 115 | result.add(name, res); 116 | return result; 117 | }, 118 | polygon.decorator, prev, "JSON-" + name)); 119 | } 120 | } 121 | for (const auto &check : checks) { 122 | const auto &name_a = check.name_a; 123 | const auto &name_b = check.name_b; 124 | std::ostringstream label; 125 | label << "JSON-" << name_a << "-vs-" << name_b << "-gjk-result"; 126 | flw::ValueCallBacks cb; 127 | if (&checks.back() == &check) { 128 | // register dump callback to last result 129 | cb.addOnValue( 130 | [](const ResultsAccumulator &results) { print(results.dump()); }); 131 | } 132 | auto &prev = accumulator.value(); 133 | accumulator.emplace( 134 | flow.makeNodeWithErrorsCB( 136 | [name_a, name_b](const flx::QueryResult &res, 137 | const ResultsAccumulator &prev) { 138 | ResultsAccumulator result{prev}; 139 | result.add(name_a, name_b, res); 140 | return result; 141 | }, 142 | check.query, prev, std::move(cb), label.str())); 143 | } 144 | } 145 | } // namespace flw::sample 146 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/src/CollisionDetectionFlow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Polygon.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace flw::sample { 15 | class FlowHandler { 16 | public: 17 | Flow flow; 18 | 19 | FlowHandler() { 20 | flow.setOnNewNodePolicy(HandlerMaker::OnNewNodePolicy::DEFERRED_UPDATE); 21 | } 22 | 23 | struct PolygonHandler { 24 | HandlerSource angle; 25 | HandlerSource center; 26 | }; 27 | PolygonHandler addPolygon(Polygon &&polygon, const std::string &name); 28 | 29 | void finalizeFlowCreation(); 30 | 31 | private: 32 | struct PolygonWithPosition { 33 | std::string label; 34 | HandlerSource shape; 35 | Handler decorator; 36 | }; 37 | using Container = std::list; 38 | Container polygons_; 39 | std::unordered_map table_; 40 | }; 41 | } // namespace flw::sample 42 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/src/Polygon.cpp: -------------------------------------------------------------------------------- 1 | #include "Polygon.h" 2 | 3 | #include 4 | 5 | namespace flw::sample { 6 | namespace { 7 | static constexpr float PI_GREEK = 3.14159f; 8 | static constexpr float PI_GREEK_2 = 2.f * PI_GREEK; 9 | } // namespace 10 | 11 | float to_rad(float angle) { return angle * PI_GREEK / 180.f; } 12 | 13 | namespace { 14 | std::shared_ptr make_vertices(float ray, std::size_t vertices) { 15 | Points result; 16 | result.reserve(vertices * 2); 17 | const float angle_delta = PI_GREEK_2 / static_cast(vertices); 18 | float angle = 0; 19 | for (std::size_t k = 0; k < vertices; ++k, angle += angle_delta) { 20 | float x = ray * cosf(angle); 21 | float y = ray * sinf(angle); 22 | result.emplace_back(Point3D{x, y, 0.1f}); 23 | result.emplace_back(Point3D{x, y, -0.1f}); 24 | } 25 | return std::make_shared(std::move(result)); 26 | } 27 | 28 | float product(const Points::const_iterator &element, 29 | const hull::Coordinate &direction) { 30 | return element->at(0) * direction.x + element->at(1) * direction.y + 31 | element->at(2) * direction.z; 32 | } 33 | 34 | hull::Coordinate to_coordinate(const Points::const_iterator &to_convert) { 35 | return hull::Coordinate{to_convert->at(0), to_convert->at(1), 36 | to_convert->at(2)}; 37 | } 38 | } // namespace 39 | 40 | Polygon::Polygon(float ray, std::size_t vertices) 41 | : Polygon{make_vertices(ray, vertices)} {} 42 | 43 | Polygon::Polygon(std::shared_ptr vertices) 44 | : detail::PointsStorer{vertices}, 45 | flx::shape::PointCloud{ 46 | vertices_->begin(), vertices_->end(), product, to_coordinate} {} 47 | 48 | std::vector Polygon::getPoints() const { 49 | std::vector result; 50 | for (std::size_t k = 0; k < vertices_->size(); k += 2) { 51 | const auto &vertex = vertices_->at(k); 52 | result.push_back(Point2D{vertex[0], vertex[1]}); 53 | } 54 | return result; 55 | } 56 | } // namespace flw::sample 57 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/src/Polygon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace flw::sample { 10 | float to_rad(float angle); 11 | 12 | using Point3D = std::array; 13 | using Points = std::vector; 14 | 15 | using Point2D = std::array; 16 | 17 | namespace detail { 18 | class PointsStorer { 19 | public: 20 | virtual ~PointsStorer() = default; 21 | 22 | protected: 23 | PointsStorer(std::shared_ptr vertices) : vertices_(vertices) {} 24 | 25 | std::shared_ptr vertices_; 26 | }; 27 | } // namespace detail 28 | 29 | class Polygon : private detail::PointsStorer, 30 | public flx::shape::PointCloud { 31 | public: 32 | Polygon(float ray, std::size_t vertices); 33 | 34 | std::vector getPoints() const; 35 | 36 | private: 37 | Polygon(std::shared_ptr vertices); 38 | }; 39 | } // namespace flw::sample 40 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/src/ResultsAccumulator.cpp: -------------------------------------------------------------------------------- 1 | #include "ResultsAccumulator.h" 2 | 3 | #include 4 | 5 | namespace flw::sample { 6 | ResultsAccumulator::ResultsAccumulator() { 7 | results_ = std::make_shared(); 8 | } 9 | 10 | namespace { 11 | void to_json(nlohmann::json &recipient, const hull::Coordinate &subject) { 12 | recipient["x"] = subject.x; 13 | recipient["y"] = subject.y; 14 | } 15 | 16 | void to_json(nlohmann::json &recipient, const Point3D &point) { 17 | recipient["x"] = point[0]; 18 | recipient["y"] = point[1]; 19 | } 20 | } // namespace 21 | 22 | void ResultsAccumulator::add(const std::string &name, 23 | const flx::shape::TransformDecorator &subject) { 24 | auto &recipient = results_->serialized_polygons[name]; 25 | const auto *as_polygon = dynamic_cast(&subject.getShape()); 26 | const auto trsf = subject.getTransformation(); 27 | auto points = as_polygon->getPoints(); 28 | // apply the rototraslation 29 | const auto &rot = subject.getTransformation().getRotation(); 30 | const auto &trasl = subject.getTransformation().getTraslation(); 31 | for (auto &point : points) { 32 | float x = rot[0][0] * point[0] + rot[0][1] * point[1] + trasl.x; 33 | float y = rot[1][0] * point[0] + rot[1][1] * point[1] + trasl.y; 34 | point[0] = x; 35 | point[1] = y; 36 | } 37 | nlohmann::to_json(recipient, points); 38 | } 39 | 40 | void ResultsAccumulator::add(const std::string &nameA, const std::string &nameB, 41 | const flx::QueryResult &subject) { 42 | auto &recipient = 43 | results_->serialized_closest_pairs 44 | .emplace_back(Results::ClosestPair{nameA, nameB, nlohmann::json{}}) 45 | .serializedPair; 46 | 47 | if (subject.is_closest_pair_or_penetration_info) { 48 | to_json(recipient["diffA"], subject.result.point_in_shape_a); 49 | to_json(recipient["diffB"], subject.result.point_in_shape_b); 50 | } 51 | } 52 | 53 | namespace { 54 | using Color = std::array; 55 | static const Color RED = {1.f, 0.f, 0.f}; 56 | static const Color GREEN = {0.f, 1.f, 0.f}; 57 | } // namespace 58 | 59 | nlohmann::json ResultsAccumulator::dump() const { 60 | // apply red colors to polygons in collision and green to others 61 | // + 62 | // extract the closest pairs 63 | 64 | nlohmann::json result; 65 | auto &shapes = result["Shapes"]; 66 | for (const auto &[name, serialization] : results_->serialized_polygons) { 67 | auto &shape = shapes[name]; 68 | shape["vertices"] = serialization; 69 | shape["color"] = GREEN; 70 | } 71 | auto &lines = result["Lines"]; 72 | lines = nlohmann::json::array(); 73 | for (const auto &pair : results_->serialized_closest_pairs) { 74 | if (pair.serializedPair.is_null()) { 75 | // collision: set to red the polygons color 76 | shapes[pair.nameA]["color"] = RED; 77 | shapes[pair.nameB]["color"] = RED; 78 | } else { 79 | // no collision: put json of the closest pair 80 | auto &line = lines.emplace_back(); 81 | line = pair.serializedPair; 82 | } 83 | } 84 | return result; 85 | } 86 | } // namespace flw::sample 87 | -------------------------------------------------------------------------------- /samples/Sample-04-collisions-detection/src/ResultsAccumulator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace flw::sample { 13 | class ResultsAccumulator { 14 | public: 15 | ResultsAccumulator(); 16 | 17 | struct Results { 18 | std::unordered_map serialized_polygons; 19 | 20 | struct ClosestPair { 21 | std::string nameA; 22 | std::string nameB; 23 | nlohmann::json serializedPair; // null represents a collision 24 | }; 25 | std::vector serialized_closest_pairs; 26 | }; 27 | 28 | void add(const std::string &name, 29 | const flx::shape::TransformDecorator &subject); 30 | 31 | void add(const std::string &nameA, const std::string &nameB, 32 | const flx::QueryResult &subject); 33 | 34 | nlohmann::json dump() const; 35 | 36 | protected: 37 | std::shared_ptr results_; 38 | }; 39 | } // namespace flw::sample 40 | -------------------------------------------------------------------------------- /samples/Sample-README/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | MAKE_SAMPLE() 2 | -------------------------------------------------------------------------------- /samples/Sample-README/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | int main() { 6 | // Build a new empty flow 7 | flw::Flow flow; 8 | 9 | // Define the sources nodes, passing the initial values 10 | auto source_1 = flow.makeSource(576, "Source-1"); 11 | auto source_2 = flow.makeSource("Some value", "Source-2"); 12 | 13 | // Define a node combining the 2 sources 14 | // The intial value for the node will be computed right after built, as the default policy of the network 15 | // is OnNewNodePolicy::IMMEDIATE_UPDATE 16 | auto node = flow.makeNode( 17 | // here you pass or define the lambda used to processing the values of the ancestors 18 | [](const int &source_1, const std::string &source_2) { 19 | // use sources values to get the new node value 20 | std::string result = source_2 + std::to_string(source_1); 21 | return result; 22 | }, 23 | // the ancestors 24 | source_1, source_2, 25 | // label to assing to the node (is actually optional ... needed if you want to retrieve the node later from the network) 26 | "Node", 27 | // we want in this case to also register a call back, triggered every time a new value is computed for the node 28 | [](const std::string &new_value) { 29 | // do something fancy with new_value ... 30 | }); 31 | 32 | // It may happen that an exception is thrown when executing the labda passed to a node, when trying 33 | // to update the value stored in that node. 34 | // You can register a call back for each specific exception type that can be thrown. 35 | // In this way, the call back of the exception is given the exception without loosing its type. 36 | class CustomException1 : public std::runtime_error { 37 | public: 38 | CustomException1() : std::runtime_error{"Bla bla"} {}; 39 | }; 40 | class CustomException2 : public std::runtime_error { 41 | public: 42 | CustomException2() : std::runtime_error{"Bla bla"} {}; 43 | }; 44 | flow.makeNodeWithErrorsCB( 45 | [](const std::string &source_2) { 46 | std::string result; 47 | // ops ... an exception is thrown 48 | throw CustomException1{}; 49 | return result; 50 | }, 51 | source_2, 52 | flw::ValueCallBacks{} 53 | .addOnValue([](const std::string& val) { 54 | // do something with the value 55 | }) 56 | .addOnError([](const CustomException1& e) { 57 | // inspect e and trigger the proper reaction 58 | }) 59 | .addOnError([](const CustomException2& e) { 60 | // inspect e and trigger the proper reaction 61 | }) 62 | .extract()); 63 | 64 | // Now is the time to update one of the source (we could update multiple ones or them all if needed). 65 | source_2.update("Some other value"); 66 | // Update those nodes in the flow requiring a recomputation 67 | flow.update(); 68 | 69 | // You can also decide to update the flow, using multiple threads. 70 | // This is actually recommended only for very big network. 71 | flow.setThreads(3); 72 | // 3 threads will be used by the next update as 3 was passed to setThreads(...) 73 | flow.update(); 74 | 75 | return EXIT_SUCCESS; 76 | } 77 | -------------------------------------------------------------------------------- /samples/Sample-utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.h ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) 2 | 3 | set(LIB_NAME DynamicFlow-Sample-utils) 4 | 5 | add_library(${LIB_NAME} ${SOURCES}) 6 | target_include_directories(${LIB_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/) 7 | 8 | find_package(Python3 REQUIRED COMPONENTS Interpreter Development) 9 | target_compile_definitions(${LIB_NAME} PUBLIC 10 | -DPYTHON_CMD="${Python3_EXECUTABLE}" 11 | -DLOG_DIR="${CMAKE_CURRENT_BINARY_DIR}/log" 12 | -DDEFAULT_SCRIPT="${CMAKE_CURRENT_SOURCE_DIR}/ShowGraph.py" 13 | ) 14 | target_link_libraries(${LIB_NAME} PUBLIC DynamicFlow) 15 | -------------------------------------------------------------------------------- /samples/Sample-utils/RunScript.cpp: -------------------------------------------------------------------------------- 1 | #include "RunScript.h" 2 | 3 | #include 4 | 5 | namespace flw::sample { 6 | LogDir::LogDir() : path_{LOG_DIR} { 7 | if (std::filesystem::exists(path_)) { 8 | std::filesystem::remove_all(path_); 9 | } 10 | std::filesystem::create_directories(path_); 11 | } 12 | 13 | const std::filesystem::path &LogDir::get() { 14 | static LogDir guard; 15 | return guard.path_; 16 | } 17 | 18 | void RunScript::runDefaultScript(const std::string &dotFileName) { 19 | static std::filesystem::path script{DEFAULT_SCRIPT}; 20 | runScript(script, "filename", LogDir::get() / dotFileName); 21 | } 22 | 23 | void RunScript::run() { 24 | auto cmd_str = cmd_.str(); 25 | if (std::system(cmd_str.c_str())) { 26 | throw Error::make<' '>("Unable to run:", cmd_str); 27 | } 28 | } 29 | } // namespace flw::sample 30 | -------------------------------------------------------------------------------- /samples/Sample-utils/RunScript.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace flw::sample { 10 | struct LogDir { 11 | static const std::filesystem::path &get(); 12 | 13 | private: 14 | LogDir(); 15 | 16 | std::filesystem::path path_; 17 | }; 18 | 19 | struct RunScript { 20 | template 21 | static void runScript(const std::filesystem::path &location, ARGS &&...args) { 22 | RunScript tmp{}; 23 | tmp.cmd_ << PYTHON_CMD; 24 | tmp.cmd_ << ' '; 25 | tmp.cmd_ << location; 26 | tmp.addArg_(args...); 27 | tmp.run(); 28 | } 29 | 30 | static void runDefaultScript(const std::string &dotFileName); 31 | 32 | private: 33 | template 34 | void addArg_(const std::string_view &name, Front &&front_val, 35 | ARGS &&...args) { 36 | cmd_ << ' '; 37 | cmd_ << "--"; 38 | cmd_ << name; 39 | cmd_ << ' '; 40 | cmd_ << front_val; 41 | if constexpr (0 < sizeof...(ARGS)) { 42 | addArg_(args...); 43 | } 44 | } 45 | 46 | RunScript() = default; 47 | 48 | void run(); 49 | 50 | std::stringstream cmd_; 51 | }; 52 | 53 | } // namespace flw::sample 54 | -------------------------------------------------------------------------------- /samples/Sample-utils/ShowGraph.py: -------------------------------------------------------------------------------- 1 | import os, argparse 2 | 3 | def print_graph(path): 4 | with open(path, 'r') as stream: 5 | data = stream.read() 6 | 7 | try: 8 | import graphviz 9 | _, fileNameBase = os.path.split(path) 10 | dotGraph = graphviz.Source(data) 11 | dotGraph.render('Rendered/' + fileNameBase, view=True).replace('\\', '/') 12 | 13 | except ImportError: 14 | print("==================================================================================================================================") 15 | print("===== !!!!!! graphviz was not found on your system. Install it with 'pip install graphviz' or paste the below content into https://dreampuf.github.io/GraphvizOnline/") 16 | print(data) 17 | print("==================================================================================================================================") 18 | 19 | input("Press Enter to continue...") 20 | 21 | if __name__ == '__main__': 22 | parser = argparse.ArgumentParser() 23 | parser.add_argument('--filename', required=True) 24 | args = parser.parse_args() 25 | 26 | print_graph(args.filename) 27 | -------------------------------------------------------------------------------- /samples/cmake/make-sample.cmake: -------------------------------------------------------------------------------- 1 | function(MAKE_SAMPLE) 2 | get_filename_component(SAMPLE_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) 3 | 4 | set(COMPLETE_NAME DynamicFlow-${SAMPLE_NAME}) 5 | 6 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src) 7 | file(GLOB SAMPLE_SRC_FILES 8 | ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp 9 | ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h 10 | ${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp 11 | ) 12 | add_executable(${COMPLETE_NAME} ${SAMPLE_SRC_FILES}) 13 | else() 14 | add_executable(${COMPLETE_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/Main.cpp) 15 | endif() 16 | 17 | target_link_libraries(${COMPLETE_NAME} PUBLIC 18 | DynamicFlow 19 | DynamicFlow-Sample-utils 20 | ) 21 | 22 | target_include_directories(${COMPLETE_NAME} PUBLIC 23 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ 24 | ) 25 | 26 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/assets) 27 | target_compile_definitions(${COMPLETE_NAME} 28 | PUBLIC 29 | -DASSET_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}/assets" 30 | ) 31 | endif() 32 | 33 | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/script) 34 | target_compile_definitions(${COMPLETE_NAME} 35 | PUBLIC 36 | -DSCRIPT_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}/script" 37 | ) 38 | endif() 39 | endfunction() 40 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(PROJECT_SHORTNAME "DynamicFlow") 2 | 3 | MakeLibrary(${PROJECT_SHORTNAME} header) 4 | 5 | find_package(Threads REQUIRED) 6 | find_package(OpenMP REQUIRED) 7 | target_link_libraries(${PROJECT_SHORTNAME} 8 | PUBLIC 9 | ${CMAKE_THREAD_LIBS_INIT} 10 | PRIVATE 11 | OpenMP::OpenMP_CXX 12 | ) 13 | 14 | option(DYNAMIC_FLOW_ENABLE_JSON_EXPORT "Enable export to json for DynamicFlow" OFF) 15 | if(DYNAMIC_FLOW_ENABLE_JSON_EXPORT) 16 | FECTH_JSON_LIB() 17 | target_link_libraries(${PROJECT_SHORTNAME} PUBLIC nlohmann_json::nlohmann_json) 18 | target_compile_definitions(${PROJECT_SHORTNAME} PUBLIC DYNAMIC_FLOW_ENABLE_JSON_EXPORT) 19 | endif() 20 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/CallBack.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | namespace flw { 13 | 14 | template class CallBack { 15 | public: 16 | CallBack() = default; 17 | 18 | CallBack(const CallBack&) = delete; 19 | CallBack& operator=(const CallBack&) = delete; 20 | 21 | CallBack(CallBack&&) = default; 22 | CallBack& operator=(CallBack&&) = default; 23 | 24 | template void add(Pred &&pred) { 25 | if (cb_) { 26 | cb_ = [prev = std::move(cb_), pred = std::forward(pred)](const T& val) { 27 | pred(val); 28 | prev(val); 29 | }; 30 | } else { 31 | cb_ = std::forward(pred); 32 | } 33 | } 34 | 35 | void operator()(const T &ref) const { 36 | if (cb_) { 37 | cb_(ref); 38 | } 39 | } 40 | 41 | private: 42 | std::function cb_; 43 | }; 44 | 45 | } // namespace flw 46 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Error.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace flw { 15 | namespace detail { 16 | template 17 | void merge_(std::stringstream &stream, const First &first, 18 | const Rest &...args) { 19 | stream << first; 20 | if constexpr (sizeof...(Rest) != 0) { 21 | if constexpr (0 < Sep) { 22 | stream << Sep; 23 | } 24 | merge_(stream, args...); 25 | } 26 | } 27 | 28 | template std::string merge(const ARGS &...args) { 29 | std::stringstream stream; 30 | merge_(stream, args...); 31 | return stream.str(); 32 | } 33 | } // namespace detail 34 | 35 | class Error : public std::runtime_error { 36 | public: 37 | explicit Error(const std::string &what); 38 | 39 | template 40 | static Error make(const Args &...args) { 41 | return Error(detail::merge(args...)); 42 | } 43 | }; 44 | 45 | } // namespace flw 46 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Network.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace flw { 19 | class HandlerMaker; 20 | class HandlerFinder; 21 | class FlowBase; 22 | class UpdateRequiredAware; 23 | 24 | template class Handler { 25 | friend class HandlerMaker; 26 | friend class HandlerFinder; 27 | 28 | public: 29 | Handler(const Handler &) = default; 30 | Handler &operator=(const Handler &) = default; 31 | 32 | Handler(Handler &&) = default; 33 | Handler &operator=(Handler &&) = default; 34 | 35 | protected: 36 | Handler(FlowBase &flow, NodeBasePtr node) : flow_ref_{flow}, node_{node} {} 37 | 38 | private: 39 | FlowBase &flow_ref_; 40 | NodeBasePtr node_; 41 | }; 42 | 43 | template class HandlerSource : public Handler { 44 | friend class HandlerMaker; 45 | friend class HandlerFinder; 46 | 47 | public: 48 | void update(T value); 49 | 50 | private: 51 | HandlerSource(UpdateRequiredAware &flow, std::shared_ptr> source) 52 | : Handler{flow, source}, update_ref_{flow}, source_{source} {} 53 | 54 | std::shared_ptr> source_; 55 | UpdateRequiredAware &update_ref_; 56 | }; 57 | 58 | class FlowBase { 59 | public: 60 | virtual ~FlowBase() = default; 61 | 62 | FlowBase(const FlowBase &) = delete; 63 | FlowBase &operator=(const FlowBase &) = delete; 64 | 65 | FlowBase(FlowBase &&) = delete; 66 | FlowBase &operator=(FlowBase &&) = delete; 67 | 68 | /** 69 | * @return the number of sources + nodes stored in this network 70 | */ 71 | std::size_t size() const { 72 | std::scoped_lock guard(flow_lock_); 73 | return sources_.size() + nodes_.size(); 74 | } 75 | /** 76 | * @return the number of sources stored in this network 77 | */ 78 | std::size_t sources_size() const { 79 | std::scoped_lock guard(flow_lock_); 80 | return sources_.size(); 81 | } 82 | /** 83 | * @return the number of nodes stored in this network 84 | */ 85 | std::size_t nodes_size() const { 86 | std::scoped_lock guard(flow_lock_); 87 | return nodes_.size(); 88 | } 89 | 90 | protected: 91 | FlowBase() = default; 92 | 93 | mutable std::mutex flow_lock_; 94 | 95 | std::unordered_map labeled_elements_; 96 | 97 | std::vector sources_; 98 | std::unordered_map sources_to_labels_; 99 | 100 | std::vector nodes_; 101 | std::unordered_map nodes_to_labels_; 102 | }; 103 | 104 | class UpdateRequiredAware : public virtual FlowBase { 105 | template friend class HandlerSource; 106 | 107 | protected: 108 | void propagateSourceUpdate(const FlowElement &source); 109 | 110 | std::unordered_set updatePending_; 111 | }; 112 | 113 | class HandlerFinder : public virtual UpdateRequiredAware { 114 | public: 115 | /** 116 | * @return An handler wrapping an already generated source, if this one was 117 | * created with the passed label and has the specified type. 118 | */ 119 | template 120 | HandlerSource findSource(const std::string &label) const { 121 | return find_(label); 122 | } 123 | 124 | /** 125 | * @return An handler wrapping an already generated node, if this one was 126 | * created with the passed label and has the specified type. 127 | */ 128 | template Handler findNode(const std::string &label) const { 129 | return find_(label); 130 | } 131 | 132 | private: 133 | template class HandlerU> 134 | HandlerU find_(const std::string &label) const; 135 | }; 136 | 137 | class HandlerMaker : public virtual UpdateRequiredAware { 138 | public: 139 | template 140 | HandlerSource makeSource(T initial_value, const std::string &label = ""); 141 | 142 | template 143 | Handler makeNode( 144 | Pred &&lambda, const Handler &...deps, const std::string &label = "", 145 | std::function valueCB = std::function{}, 146 | std::function errCB = 147 | std::function{}); 148 | 149 | template 150 | Handler makeNodeWithErrorsCB( 151 | Pred &&lambda, const Handler &...deps, 152 | ValueCallBacks cb = ValueCallBacks{}, 153 | const std::string &label = ""); 154 | 155 | enum class OnNewNodePolicy { IMMEDIATE_UPDATE, DEFERRED_UPDATE }; 156 | auto onNewNodePolicy() const { return policy.load(); } 157 | void setOnNewNodePolicy(OnNewNodePolicy p) { policy.store(p); } 158 | 159 | private: 160 | template 162 | void packDeps(TupleT &recipient, const Handler &dep_front, 163 | const Handler &...dep_rest); 164 | 165 | std::atomic policy{OnNewNodePolicy::IMMEDIATE_UPDATE}; 166 | }; 167 | 168 | enum class FlowStatus { UPDATE_NOT_REQUIRED, UPDATE_REQUIRED, UPDATING }; 169 | 170 | class Updater : public virtual UpdateRequiredAware { 171 | public: 172 | FlowStatus status() const { 173 | if (updating_.load(std::memory_order_acquire)) { 174 | return FlowStatus::UPDATING; 175 | } 176 | std::scoped_lock guard(flow_lock_); 177 | return updatePending_.empty() ? FlowStatus::UPDATE_NOT_REQUIRED 178 | : FlowStatus::UPDATE_REQUIRED; 179 | } 180 | 181 | /** 182 | * @brief triggers the update of all the nodes in the network. 183 | * Changes should happen as a consequence of having changed before any 184 | * source(s) value(s). 185 | */ 186 | void update(); 187 | 188 | /** 189 | * @brief sets the number of threads to use for the next network update, i.e. 190 | * when update() will be called again. 191 | */ 192 | void setThreads(std::size_t threads) { 193 | if (threads == 0) { 194 | throw Error{"Specify at least one thread for the update"}; 195 | } 196 | threads_.store(threads, std::memory_order_release); 197 | } 198 | 199 | private: 200 | std::unordered_set 201 | updateSingle(std::vector open); 202 | 203 | std::unordered_set 204 | updateMulti(std::unordered_set open, 205 | std::size_t threads_to_use); 206 | 207 | std::atomic threads_ = 1; 208 | std::atomic_bool updating_{false}; 209 | }; 210 | 211 | struct ValueSnapshot { 212 | bool has_value; 213 | std::size_t id; 214 | std::size_t generation; 215 | std::string label; 216 | std::string value_serialization; 217 | // the Ids of the node whose result depend on this one 218 | std::vector dependants; 219 | }; 220 | using FlowSnapshot = std::vector; 221 | 222 | class StructureExporter : public virtual FlowBase { 223 | public: 224 | /** 225 | * @return a serialization of the entire network 226 | */ 227 | FlowSnapshot snapshot() const; 228 | }; 229 | 230 | class Flow final : public HandlerFinder, 231 | public HandlerMaker, 232 | public Updater, 233 | public StructureExporter { 234 | public: 235 | Flow() = default; 236 | }; 237 | 238 | } // namespace flw 239 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Network.hxx: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | namespace flw { 13 | template class HandlerU> 14 | HandlerU HandlerFinder::find_(const std::string &label) const { 15 | FlowElementPtr ptr; 16 | { 17 | std::scoped_lock guard(flow_lock_); 18 | auto it = labeled_elements_.find(label); 19 | if (it == labeled_elements_.end()) { 20 | if constexpr (std::is_same_v, HandlerSource>) { 21 | throw Error::make<' '>("Unable to find source named:", label); 22 | } else { 23 | throw Error::make<' '>("Unable to find node named:", label); 24 | } 25 | } 26 | ptr = it->second; 27 | } 28 | NodeBasePtr res = std::dynamic_pointer_cast>(ptr); 29 | if (!res) { 30 | if constexpr (std::is_same_v, HandlerSource>) { 31 | throw Error::make<' '>("Unable to find source named:", label, 32 | "with the requested type"); 33 | } else { 34 | throw Error::make<' '>("Unable to find node named:", label, 35 | "with the requested type"); 36 | } 37 | } 38 | 39 | UpdateRequiredAware &ref = *const_cast(this); 40 | if constexpr (std::is_same_v, HandlerSource>) { 41 | return HandlerSource{ref, std::dynamic_pointer_cast>(res)}; 42 | } else { 43 | return Handler{ref, res}; 44 | } 45 | } 46 | 47 | template 48 | HandlerSource HandlerMaker::makeSource(T initial_value, 49 | const std::string &label) { 50 | auto src = std::make_shared>(std::move(initial_value)); 51 | std::scoped_lock guard(flow_lock_); 52 | if (!label.empty() && 53 | labeled_elements_.find(label) != labeled_elements_.end()) { 54 | throw Error::make<' '>("Label `", label, "` was already used"); 55 | } 56 | sources_.emplace_back(src); 57 | if (!label.empty()) { 58 | labeled_elements_.emplace(label, src); 59 | sources_to_labels_.emplace(src.get(), label); 60 | } 61 | return HandlerSource{std::ref(*this), src}; 62 | } 63 | 64 | template 65 | Handler 66 | HandlerMaker::makeNode(Pred &&lambda, const Handler &...deps, 67 | const std::string &label, 68 | std::function valueCB, 69 | std::function errCB) { 70 | ValueCallBacks cb; 71 | cb.addOnValue(std::move(valueCB)); 72 | cb.template addOnError(std::move(errCB)); 73 | return this->makeNodeWithErrorsCB(std::forward(lambda), 74 | deps..., std::move(cb), label); 75 | } 76 | 77 | template 78 | Handler HandlerMaker::makeNodeWithErrorsCB(Pred &&lambda, 79 | const Handler &...deps, 80 | ValueCallBacks cb, 81 | const std::string &label) { 82 | if (!label.empty() && 83 | labeled_elements_.find(label) != labeled_elements_.end()) { 84 | throw Error::make<' '>("Label `", label, "` was already used"); 85 | } 86 | std::tuple...> deps_node; 87 | packDeps<0, std::tuple...>, As...>(deps_node, deps...); 88 | auto node = 89 | Node::make(std::forward(lambda), deps_node, 90 | std::forward>(cb)); 91 | std::scoped_lock guard(flow_lock_); 92 | nodes_.emplace_back(node); 93 | if (!label.empty()) { 94 | labeled_elements_.emplace(label, node); 95 | nodes_to_labels_.emplace(node.get(), label); 96 | } 97 | if (policy.load() == OnNewNodePolicy::IMMEDIATE_UPDATE && 98 | node->updatePossible()) { 99 | node->update(); 100 | } else { 101 | this->updatePending_.emplace(node.get()); 102 | } 103 | return Handler{*this, node}; 104 | } 105 | 106 | template 108 | void HandlerMaker::packDeps(TupleT &recipient, const Handler &dep_front, 109 | const Handler &...dep_rest) { 110 | if (this != &dep_front.flow_ref_) { 111 | throw Error{"All the dependencies must come from the same graph"}; 112 | } 113 | std::get(recipient) = dep_front.node_; 114 | if constexpr (0 < sizeof...(Arest)) { 115 | packDeps(recipient, dep_rest...); 116 | } 117 | } 118 | 119 | template void HandlerSource::update(T value) { 120 | std::scoped_lock guard(update_ref_.flow_lock_); 121 | if (!source_->update(std::move(value))) { 122 | return; 123 | } 124 | update_ref_.propagateSourceUpdate(*this->source_); 125 | } 126 | } // namespace flw 127 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/NetworkSerialization.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #ifdef DYNAMIC_FLOW_ENABLE_JSON_EXPORT 13 | #include 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | namespace flw { 21 | 22 | enum class Serialization { DOT, JSON }; 23 | 24 | template struct Converter {}; 25 | 26 | // dot is a language for describing graph, which is also used by graphviz, 27 | // refer to: https://graphviz.org/doc/info/lang.html 28 | // 29 | // dot files can be also converted to svg, png or pdf. The easiest way to do 30 | // this is to use Python graphviz, refe to: 31 | // https:// pypi.org/project/graphviz/ 32 | // 33 | // alterantively, you can copy paste the dot file content into this 34 | // online application: https://dreampuf.github.io/GraphvizOnline/ 35 | // that will render the graph for you 36 | template <> struct Converter { 37 | static void toStream(std::ostream &recipient, const FlowSnapshot &structure); 38 | 39 | static std::string toString(const FlowSnapshot &structure) { 40 | std::stringstream res; 41 | toStream(res, structure); 42 | return res.str(); 43 | } 44 | 45 | static void toFile(const std::filesystem::path &filename, 46 | const FlowSnapshot &structure); 47 | }; 48 | 49 | #ifdef DYNAMIC_FLOW_ENABLE_JSON_EXPORT 50 | 51 | template <> struct Converter { 52 | static void toJson(nlohmann::json &recipient, const FlowSnapshot &structure); 53 | 54 | static void toJsonFile(const std::filesystem::path &filename, const FlowSnapshot &structure); 55 | }; 56 | 57 | #endif 58 | } // namespace flw 59 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Node.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | namespace flw { 16 | class FlowElementResettable; 17 | using FlowElementResettablePtr = std::shared_ptr; 18 | 19 | class FlowElement { 20 | public: 21 | virtual ~FlowElement() = default; 22 | 23 | struct SerializationInfo { 24 | std::size_t generation; 25 | 26 | /** 27 | * @brief if a value is stored and can be converted to a string, the 28 | * converted version of the value is returned. Otherwise, an empty string is 29 | * returned. 30 | */ 31 | std::string value_serialized; 32 | }; 33 | virtual SerializationInfo serializationInfo() const = 0; 34 | 35 | const auto &dependants() const { return dependants_; } 36 | 37 | virtual bool hasValue() const = 0; 38 | 39 | protected: 40 | FlowElement() = default; 41 | 42 | static void link(FlowElement &parent, FlowElementResettablePtr dependant) { 43 | parent.dependants_.push_back(dependant); 44 | } 45 | 46 | private: 47 | std::vector dependants_; 48 | }; 49 | using FlowElementPtr = std::shared_ptr; 50 | 51 | class Resettable { 52 | public: 53 | virtual ~Resettable() = default; 54 | 55 | virtual void reset() = 0; 56 | 57 | const auto &dependencies() const { return dependencies_; } 58 | bool updatePossible() const; 59 | 60 | virtual void update() = 0; 61 | 62 | protected: 63 | std::vector dependencies_; 64 | }; 65 | 66 | class FlowElementResettable : virtual public FlowElement, public Resettable { 67 | public: 68 | FlowElementResettable() = default; 69 | }; 70 | 71 | template class NodeBase : virtual public FlowElement { 72 | public: 73 | SerializationInfo serializationInfo() const final; 74 | 75 | bool hasValue() const final { return value_->getValue(); } 76 | 77 | auto value() const { return value_; } 78 | 79 | protected: 80 | NodeBase(ValueBasePtr val) : value_{val} {} 81 | 82 | private: 83 | ValueBasePtr value_; 84 | }; 85 | template using NodeBasePtr = std::shared_ptr>; 86 | 87 | template 88 | class Node : public NodeBase, public FlowElementResettable { 89 | static_assert( 90 | 0 < sizeof...(As), 91 | "nodes should have at least one dependency ... otherwise are sources!!!"); 92 | 93 | public: 94 | using Lambda = std::function; 95 | using Ancestors = std::tuple...>; 96 | 97 | template 98 | static std::shared_ptr> 99 | make(Lambda &&lam, const std::tuple...> &deps, 100 | ValueCallBacks &&cb); 101 | 102 | void reset() final { this->valueT_->reset(); } 103 | 104 | void update() final; 105 | 106 | private: 107 | template 108 | Node(Lambda &&lam, const std::tuple...> &deps, 109 | ValueCallBacks &&cb); 110 | 111 | template 112 | static void link(std::shared_ptr> subject, 113 | const std::tuple...> &deps); 114 | 115 | std::shared_ptr> valueT_; 116 | Lambda lambda_; 117 | Ancestors ancestors_; 118 | }; 119 | 120 | template class Source : public NodeBase { 121 | public: 122 | Source(T &&initial_value) 123 | : NodeBase{ 124 | std::make_shared>(std::forward(initial_value))} { 125 | source_ = std::dynamic_pointer_cast>(this->value()); 126 | } 127 | 128 | bool update(T &&val); 129 | 130 | private: 131 | std::shared_ptr> source_; 132 | }; 133 | } // namespace flw 134 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Node.hxx: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace flw { 16 | template 17 | FlowElement::SerializationInfo NodeBase::serializationInfo() const { 18 | SerializationInfo res{value_->getGeneration(), "null"}; 19 | if (const T *ptr = value_->getValue(); ptr) { 20 | if constexpr (CanBeStringConvertible) { 21 | res.value_serialized = std::to_string(*ptr); 22 | } else if constexpr (HasStreamOperator) { 23 | std::stringstream buffer; 24 | buffer << *ptr; 25 | res.value_serialized = buffer.str(); 26 | } else { 27 | res.value_serialized = "non-serializable"; 28 | } 29 | } 30 | return res; 31 | } 32 | 33 | template 34 | template 35 | void Node::link(std::shared_ptr> subject, 36 | const std::tuple...> &deps) { 37 | if constexpr (Index < sizeof...(As)) { 38 | auto &parent_ref = *std::get(deps); 39 | FlowElement::link(parent_ref, subject); 40 | std::get(subject->ancestors_) = parent_ref.value(); 41 | link(subject, deps); 42 | } 43 | } 44 | 45 | template 46 | template 47 | Node::Node(Lambda &&lam, const std::tuple...> &deps, 48 | ValueCallBacks &&cb) 49 | : NodeBase{std::make_shared>( 50 | std::forward>(cb))}, 51 | lambda_{std::forward(lam)} { 52 | valueT_ = std::dynamic_pointer_cast>(this->value()); 53 | } 54 | 55 | namespace detail { 56 | template 57 | void linkDependencies(std::vector &recipient, 58 | const AncestorsT &ancestors) { 59 | if constexpr (Index < std::tuple_size::value) { 60 | auto ptr = std::get(ancestors); 61 | if (auto ptr_cast = std::dynamic_pointer_cast(ptr); 62 | ptr_cast) { 63 | recipient.emplace_back(ptr_cast); 64 | } 65 | linkDependencies(recipient, ancestors); 66 | } 67 | } 68 | } // namespace detail 69 | 70 | template 71 | template 72 | std::shared_ptr> 73 | Node::make(Lambda &&lam, const std::tuple...> &deps, 74 | ValueCallBacks &&cb) { 75 | std::shared_ptr> res; 76 | res.reset( 77 | new Node{std::forward(lam), deps, 78 | std::forward>(cb)}); 79 | detail::linkDependencies<0>(res->dependencies_, deps); 80 | link<0>(res, deps); 81 | return res; 82 | } 83 | 84 | namespace detail { 85 | template 87 | T gen_val(const Ancestors &ancestors, const Lambda &lam, const Args &...args) { 88 | if constexpr (Index < std::tuple_size::value) { 89 | auto &ref = std::get(ancestors); 90 | return gen_val(ancestors, lam, args..., *ref->getValue()); 91 | } else { 92 | return lam(args...); 93 | } 94 | } 95 | } // namespace detail 96 | 97 | template void Node::update() { 98 | this->valueT_->update( 99 | [this]() { return detail::gen_val<0, T>(ancestors_, lambda_); }); 100 | } 101 | 102 | template bool Source::update(T &&val) { 103 | if constexpr (HasComparisonOperator) { 104 | if (*source_->getValue() == val) { 105 | return false; 106 | } 107 | } 108 | source_->update(std::forward(val)); 109 | return true; 110 | } 111 | } // namespace flw 112 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/TypeTraits.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace flw { 16 | template 17 | concept HasComparisonOperator = requires(const T &a, const T &b) { 18 | { a == b } -> std::convertible_to; 19 | }; 20 | 21 | template 22 | concept HasStreamOperator = requires(std::ostream &recipient, 23 | const T &subject) { 24 | { recipient << subject } -> std::convertible_to; 25 | }; 26 | 27 | template 28 | concept CanBeStringConvertible = requires(const T &subject) { 29 | { std::to_string(subject) } -> std::convertible_to; 30 | }; 31 | } // namespace flw 32 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Value.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace flw { 19 | template class ValueBase { 20 | public: 21 | virtual ~ValueBase() = default; 22 | 23 | ValueBase() = default; 24 | 25 | std::size_t getGeneration() const { return generation_; } 26 | 27 | const T *getValue() const { 28 | return value_.has_value() ? &value_.value() : nullptr; 29 | } 30 | 31 | protected: 32 | std::size_t generation_ = 0; 33 | std::optional value_; 34 | }; 35 | template using ValueBasePtr = std::shared_ptr>; 36 | 37 | template class ValueSource : public ValueBase { 38 | public: 39 | ValueSource(T &&val_initial) { this->update(std::forward(val_initial)); } 40 | 41 | void update(T &&val) { 42 | ++this->generation_; 43 | this->value_.emplace(std::forward(val)); 44 | } 45 | }; 46 | 47 | template class ValueCallBacks { 48 | public: 49 | ValueCallBacks() = default; 50 | 51 | /** 52 | * @brief You can't access directly the value stored in this object. What 53 | * you can do, is to register with this method a call back that is called 54 | * passing the a new generated value whenever one is generated. 55 | */ 56 | template ValueCallBacks &addOnValue(Pred &&pred) { 57 | onValue_.add(std::forward(pred)); 58 | return *this; 59 | } 60 | 61 | /** 62 | * @brief Similar to onNewValueCallBack(...), but for registering a call back 63 | * called everytime a particular exception is throwned. 64 | */ 65 | template 66 | ValueCallBacks &addOnError(Pred &&pred) { 67 | auto &ref = std::get>(onError_); 68 | ref.add(std::forward(pred)); 69 | return *this; 70 | } 71 | 72 | ValueCallBacks extract() { return std::move(*this); } 73 | 74 | protected: 75 | void onValue(const T &val) { onValue_(val); } 76 | 77 | template void onError(const ErrorT &e) { 78 | auto &ref = std::get>(onError_); 79 | ref(e); 80 | } 81 | 82 | private: 83 | CallBack onValue_; 84 | std::tuple..., CallBack> onError_; 85 | }; 86 | 87 | template class ValueT : public ValueBase { 88 | public: 89 | void reset() { this->value_.reset(); } 90 | 91 | virtual void update(std::function pred) = 0; 92 | }; 93 | 94 | template 95 | class Value : public ValueT, public ValueCallBacks { 96 | public: 97 | Value(ValueCallBacks cb) 98 | : ValueCallBacks{std::move(cb)} {} 99 | 100 | void update(std::function pred) final; 101 | 102 | private: 103 | template 104 | void update_(const std::function &pred); 105 | }; 106 | 107 | } // namespace flw 108 | -------------------------------------------------------------------------------- /src/header/DynamicFlow/Value.hxx: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | namespace flw { 14 | template 15 | void Value::update(std::function pred) { 16 | this->value_.reset(); 17 | try { 18 | if constexpr (sizeof...(ErrorsT) == 0) { 19 | this->value_.emplace(pred()); 20 | ++this->generation_; 21 | this->onValue(this->value_.value()); 22 | } else { 23 | this->update_(pred); 24 | } 25 | } catch (const std::exception &e) { 26 | this->template onError(e); 27 | } 28 | } 29 | 30 | template 31 | template 32 | void Value::update_(const std::function &pred) { 33 | try { 34 | if constexpr (sizeof...(ErrorsRest) == 0) { 35 | this->value_.emplace(pred()); 36 | ++this->generation_; 37 | this->onValue(this->value_.value()); 38 | } else { 39 | this->update_(pred); 40 | } 41 | } catch (const ErrorFirst &e) { 42 | this->template onError(e); 43 | } 44 | } 45 | } // namespace flw 46 | -------------------------------------------------------------------------------- /src/src/Error.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #include 9 | 10 | namespace flw { 11 | Error::Error(const std::string &what) : std::runtime_error(what) {} 12 | } // namespace flw 13 | -------------------------------------------------------------------------------- /src/src/Network.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace flw { 9 | FlowSnapshot StructureExporter::snapshot() const { 10 | FlowSnapshot res; 11 | std::unordered_map mapping; 12 | 13 | { 14 | std::scoped_lock guard(flow_lock_); 15 | 16 | auto forEachNode = [&](const auto &collection) { 17 | for (const auto &element : collection) { 18 | std::size_t id = res.size(); 19 | std::string label; 20 | if constexpr (std::is_same_v) { 22 | if (auto it = sources_to_labels_.find(element.get()); 23 | it != sources_to_labels_.end()) { 24 | label = it->second; 25 | } 26 | } else { 27 | if (auto it = nodes_to_labels_.find(element.get()); 28 | it != nodes_to_labels_.end()) { 29 | label = it->second; 30 | } 31 | } 32 | auto &&[generation, serialization] = element->serializationInfo(); 33 | bool has_val = serialization != "null"; 34 | res.emplace_back(ValueSnapshot{ 35 | has_val, 36 | id, 37 | generation, 38 | std::move(label), 39 | std::move(serialization), 40 | }); 41 | mapping.emplace(element.get(), id); 42 | } 43 | }; 44 | forEachNode(sources_); 45 | forEachNode(nodes_); 46 | } 47 | 48 | auto link = [&](ValueSnapshot &recipient, const FlowElement &el) { 49 | for (const auto &dep : el.dependants()) { 50 | std::size_t dep_id = mapping.at(dep.get()); 51 | recipient.dependants.push_back(dep_id); 52 | } 53 | }; 54 | 55 | std::size_t index = 0; 56 | for (const auto &src : sources_) { 57 | link(res[index++], *src); 58 | } 59 | for (const auto &src : nodes_) { 60 | link(res[index++], *src); 61 | } 62 | 63 | return res; 64 | } 65 | 66 | void UpdateRequiredAware::propagateSourceUpdate(const FlowElement &source) { 67 | std::unordered_set level, next; 68 | for (const auto &f : source.dependants()) { 69 | level.emplace(f.get()); 70 | } 71 | // BFS to extend the update pending set 72 | while (!level.empty()) { 73 | updatePending_.insert(level.begin(), level.end()); 74 | for (auto *n : level) { 75 | n->reset(); 76 | } 77 | next.clear(); 78 | for (auto *prev : level) { 79 | for (const auto &f : prev->dependants()) { 80 | if (updatePending_.find(f.get()) == updatePending_.end()) { 81 | next.emplace(f.get()); 82 | } 83 | } 84 | } 85 | level = std::move(next); 86 | } 87 | } 88 | 89 | std::unordered_set 90 | Updater::updateSingle(std::vector open) { 91 | std::vector next; 92 | while (!open.empty()) { 93 | next.clear(); 94 | for (auto *n : open) { 95 | if (n->updatePossible()) { 96 | n->update(); 97 | } else { 98 | next.push_back(n); 99 | } 100 | } 101 | if (next.size() == open.size()) { 102 | break; 103 | } 104 | std::swap(open, next); 105 | } 106 | return std::unordered_set{open.begin(), open.end()}; 107 | } 108 | 109 | std::unordered_set 110 | Updater::updateMulti(std::unordered_set open_set, 111 | std::size_t threads_to_use) { 112 | std::vector ready_to_process; 113 | std::atomic_bool life{true}; 114 | #pragma omp parallel num_threads(static_cast (threads_to_use)) 115 | { 116 | auto th_id = static_cast(omp_get_thread_num()); 117 | auto process = [&]() { 118 | for (std::size_t k = th_id; k < ready_to_process.size(); k += threads_to_use) { 119 | ready_to_process[k]->update(); 120 | } 121 | }; 122 | 123 | if(th_id == 0) { 124 | while(true) { 125 | std::size_t open_size_snap = open_set.size(); 126 | auto ready_rng = open_set | std::views::filter([&open_set](FlowElementResettable* node) { 127 | const auto &deps = node->dependencies(); 128 | return std::all_of( 129 | deps.begin(), deps.end(), [&open_set](const FlowElementResettablePtr& d) { 130 | return !open_set.contains(d.get()); 131 | }); 132 | }); 133 | ready_to_process = {ready_rng.begin(), ready_rng.end()}; 134 | for(auto* node : ready_to_process) { 135 | open_set.erase(node); 136 | } 137 | if(open_set.size() == open_size_snap || open_set.size() == open_size_snap) { 138 | life.store(false, std::memory_order_acquire); 139 | break; 140 | } 141 | #pragma omp barrier 142 | process(); 143 | #pragma omp barrier 144 | } 145 | #pragma omp barrier 146 | } 147 | else { 148 | while(true) { 149 | #pragma omp barrier 150 | if(!life.load(std::memory_order_acquire)) { 151 | break; 152 | } 153 | process(); 154 | #pragma omp barrier 155 | } 156 | } 157 | } 158 | 159 | return std::move(open_set); 160 | } 161 | 162 | void Updater::update() { 163 | std::scoped_lock guard(flow_lock_); 164 | if (updatePending_.empty()) { 165 | return; 166 | } 167 | 168 | updating_.store(true, std::memory_order_acquire); 169 | std::size_t threads_to_use = threads_.load(std::memory_order_acquire); 170 | if (threads_to_use == 1) { 171 | updatePending_ = updateSingle(std::vector{ 172 | updatePending_.begin(), updatePending_.end()}); 173 | } else { 174 | updatePending_ = updateMulti(std::move(updatePending_), threads_to_use); 175 | } 176 | updating_.store(false, std::memory_order_release); 177 | } 178 | } // namespace flw 179 | -------------------------------------------------------------------------------- /src/src/NetworkSerialization.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | namespace flw { 7 | namespace { 8 | std::unique_ptr 9 | make_stream(const std::filesystem::path &filename) { 10 | auto res = std::make_unique(filename); 11 | if (!res->is_open()) { 12 | throw Error::make<' '>(filename, "is not a valid file name"); 13 | } 14 | return res; 15 | } 16 | } // namespace 17 | 18 | void Converter::toFile( 19 | const std::filesystem::path &filename, const FlowSnapshot &structure) { 20 | auto stream = make_stream(filename); 21 | toStream(*stream, structure); 22 | } 23 | 24 | namespace { 25 | struct Format { 26 | template 27 | static void apply(std::ostream &recipient, std::string_view frmt, 28 | const ARGS &...args) { 29 | Format tmp{recipient, frmt}; 30 | tmp.apply_(args...); 31 | recipient << tmp.rest; 32 | } 33 | 34 | private: 35 | Format(std::ostream &rec, std::string_view frmt) 36 | : recipient{rec}, rest{frmt} {} 37 | 38 | template 39 | void apply_(const ARGF &front, const ARGS &...args) { 40 | auto next = rest.find("{}"); 41 | if (next == std::string::npos) { 42 | throw Error{"Invalid format"}; 43 | } 44 | recipient << std::string_view{rest.data(), next}; 45 | recipient << front; 46 | rest = std::string_view{rest.data() + next + 2}; 47 | 48 | if constexpr (0 < sizeof...(ARGS)) { 49 | apply_(args...); 50 | } 51 | } 52 | 53 | std::ostream &recipient; 54 | std::string_view rest; 55 | }; 56 | 57 | std::string_view node_status(bool engaged) { 58 | return engaged ? R"(FILLED)" 59 | : R"(UNSET)"; 60 | } 61 | 62 | const std::string& make_label_name(std::string& label, const ValueSnapshot& el) { 63 | label.clear(); 64 | if (el.label.empty()) { 65 | label += "Un-named-"; 66 | label += std::to_string(el.id); 67 | } else { 68 | label = el.label; 69 | } 70 | return label; 71 | } 72 | } // namespace 73 | 74 | void Converter::toStream(std::ostream &recipient, 75 | const FlowSnapshot &structure) { 76 | recipient << "digraph DynamicFlow {\n"; 77 | recipient << R"(node [shape=plaintext fontname="Sans serif" fontsize="8"];)" 78 | << "\n\n"; 79 | std::string label; 80 | for (const auto &node : structure) { 81 | make_label_name(label, node); 82 | 83 | Format::apply(recipient, R"({} [ label=< 84 | 85 | 86 | 87 | 88 | 89 |
{}
epoch: {}
status: {}
value: {}
>];)", 90 | node.id, label, node.generation, node_status(node.has_value), 91 | node.value_serialization); 92 | 93 | recipient << '\n'; 94 | for (const auto &dep_id : node.dependants) { 95 | Format::apply(recipient, "{} -> {};\n", node.id, dep_id); 96 | } 97 | recipient << '\n'; 98 | } 99 | recipient << "}\n"; 100 | } 101 | 102 | #ifdef DYNAMIC_FLOW_ENABLE_JSON_EXPORT 103 | void Converter::toJson(nlohmann::json &recipient, const FlowSnapshot &structure) { 104 | std::unordered_map> dependencies_map; 105 | for(const auto& element : structure) { 106 | for(auto d : element.dependants) { 107 | dependencies_map[d].emplace_back(element.id); 108 | } 109 | } 110 | recipient = nlohmann::json::array(); 111 | std::string label; 112 | for(const auto& element : structure) { 113 | auto& added = recipient.emplace_back(); 114 | added["has_value"] = element.has_value; 115 | added["id"] = element.id; 116 | added["generation"] = element.generation; 117 | added["label"] = make_label_name(label, element); 118 | added["dependants_ids"] = element.dependants; 119 | 120 | auto it = dependencies_map.find(element.id); 121 | if(it == dependencies_map.end()) { 122 | static const std::vector place_hoder; 123 | added["dependencies_ids"] = place_hoder; 124 | } 125 | else { 126 | added["dependencies_ids"] = dependencies_map.at(element.id); 127 | } 128 | } 129 | } 130 | 131 | void Converter::toJsonFile(const std::filesystem::path &filename, const FlowSnapshot &structure) { 132 | auto stream = make_stream(filename); 133 | nlohmann::json res; 134 | toJson(res, structure); 135 | *stream << res.dump(1); 136 | } 137 | #endif 138 | 139 | } // namespace flw 140 | -------------------------------------------------------------------------------- /src/src/Node.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace flw { 4 | bool Resettable::updatePossible() const { 5 | return std::all_of( 6 | dependencies_.begin(), dependencies_.end(), 7 | [](const FlowElementResettablePtr &ptr) { return ptr->hasValue(); }); 8 | } 9 | } // namespace flw 10 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (WIN32) 2 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 3 | endif (WIN32) 4 | include(FetchContent) 5 | FetchContent_Declare( 6 | googletest 7 | GIT_REPOSITORY https://github.com/google/googletest.git 8 | GIT_TAG release-1.11.0 9 | ) 10 | FetchContent_MakeAvailable(googletest) 11 | 12 | set(TEST_NAME DynamicFlowTests) 13 | 14 | file(GLOB TEST_SRC ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/*.h) 15 | 16 | add_executable(${TEST_NAME} ${TEST_SRC}) 17 | target_link_libraries(${TEST_NAME} 18 | PUBLIC 19 | gtest 20 | gtest_main 21 | DynamicFlow 22 | ) 23 | 24 | # target_compile_definitions(${TEST_NAME} 25 | # PUBLIC 26 | # -D TEST_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}/" 27 | # # -D SAMPLE_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}/../../Samples/" 28 | # ) 29 | 30 | install(TARGETS ${TEST_NAME}) 31 | -------------------------------------------------------------------------------- /tests/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace flw::test { 9 | class ErrorTest : public std::runtime_error { 10 | public: 11 | using std::runtime_error::runtime_error; 12 | }; 13 | 14 | template 15 | class IncrementGuard { 16 | public: 17 | IncrementGuard(Subject &subject) 18 | : to_check(subject), initial_size(subject.size()){}; 19 | 20 | ~IncrementGuard() { 21 | EXPECT_EQ(to_check.size(), initial_size + ExpectedIncrement); 22 | } 23 | 24 | private: 25 | Subject &to_check; 26 | const std::size_t initial_size; 27 | }; 28 | 29 | template using UnchangedGuard = IncrementGuard<0, Subject>; 30 | 31 | template 32 | struct ComposerLambda { 33 | ComposerLambda() = default; 34 | 35 | template 36 | std::string operator()(const ARGS& ... args) const { 37 | return detail::merge<0>(args...); 38 | } 39 | }; 40 | 41 | template<> 42 | struct ComposerLambda { 43 | ComposerLambda() = default; 44 | 45 | template 46 | std::string operator()(const ARGS& ... args) const { 47 | if(flag_) { 48 | flag_ = false; 49 | throw ErrorTest{"foo"}; 50 | } 51 | return detail::merge<0>(args...); 52 | } 53 | 54 | private: 55 | mutable bool flag_{true}; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /tests/Test00-callback.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #include 9 | 10 | #include 11 | 12 | struct CallBackTest : ::testing::Test { 13 | void add() { 14 | cb.add([this](const std::string& wrd) { 15 | elements.push_back(wrd); 16 | }); 17 | } 18 | 19 | std::vector elements; 20 | flw::CallBack cb; 21 | }; 22 | 23 | TEST_F(CallBackTest, empty) { 24 | cb("foo"); 25 | ASSERT_TRUE(elements.empty()); 26 | } 27 | 28 | TEST_F(CallBackTest, single_predicate) { 29 | add(); 30 | 31 | cb("foo"); 32 | ASSERT_EQ(elements, std::vector{"foo"}); 33 | } 34 | 35 | TEST_F(CallBackTest, multiple_predicate) { 36 | add(); 37 | add(); 38 | add(); 39 | 40 | cb("foo"); 41 | std::vector expected{"foo", "foo", "foo"}; 42 | ASSERT_EQ(elements, expected); 43 | } 44 | -------------------------------------------------------------------------------- /tests/Test00-type-traits.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #include 9 | 10 | #include 11 | 12 | namespace flw::test { 13 | class ComparableByMethod { 14 | public: 15 | ComparableByMethod() = default; 16 | 17 | bool operator==(const ComparableByMethod &) const { return true; } 18 | }; 19 | 20 | class ComparableByFunction { 21 | public: 22 | ComparableByFunction() = default; 23 | }; 24 | 25 | bool operator==(const ComparableByFunction &a, const ComparableByFunction &b) { 26 | return true; 27 | } 28 | 29 | class UnComparable { 30 | public: 31 | UnComparable() = default; 32 | }; 33 | 34 | template bool hasComparisonOperator() { 35 | if constexpr (flw::HasComparisonOperator) { 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | } 41 | 42 | TEST(TypeTraitsTest, HasComparisonOperator) { 43 | EXPECT_TRUE(hasComparisonOperator()); 44 | EXPECT_TRUE(hasComparisonOperator()); 45 | EXPECT_TRUE(hasComparisonOperator()); 46 | 47 | EXPECT_FALSE(hasComparisonOperator()); 48 | } 49 | 50 | class PrintableWithStream { 51 | public: 52 | PrintableWithStream() = default; 53 | }; 54 | 55 | std::ostream &operator<<(std::ostream &s, const PrintableWithStream &) { 56 | return s; 57 | } 58 | 59 | class UnPrintable { 60 | public: 61 | UnPrintable() = default; 62 | }; 63 | 64 | template bool hasStreamOperator() { 65 | if constexpr (flw::HasStreamOperator) { 66 | return true; 67 | } else { 68 | return false; 69 | } 70 | } 71 | 72 | TEST(TypeTraitsTest, HasStreamOperator) { 73 | EXPECT_TRUE(hasStreamOperator()); 74 | EXPECT_TRUE(hasStreamOperator()); 75 | 76 | EXPECT_FALSE(hasStreamOperator()); 77 | } 78 | 79 | template bool canBeStringConvertible() { 80 | if constexpr (flw::CanBeStringConvertible) { 81 | return true; 82 | } else { 83 | return false; 84 | } 85 | } 86 | 87 | TEST(TypeTraitsTest, CanBeStringConvertible) { 88 | EXPECT_TRUE(canBeStringConvertible()); 89 | 90 | EXPECT_FALSE(canBeStringConvertible()); 91 | } 92 | } // namespace flw::test 93 | -------------------------------------------------------------------------------- /tests/Test01-value.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #include 9 | 10 | #include 11 | 12 | TEST(ValueSourceTest, ctor) { 13 | flw::ValueSource src{"hello world"}; 14 | 15 | const auto *ptr = src.getValue(); 16 | ASSERT_TRUE(ptr); 17 | EXPECT_EQ(*ptr, "hello world"); 18 | } 19 | 20 | TEST(ValueSourceTest, update) { 21 | flw::ValueSource src{"hello world"}; 22 | src.update("another hello"); 23 | 24 | const auto *ptr = src.getValue(); 25 | ASSERT_TRUE(ptr); 26 | EXPECT_EQ(*ptr, "another hello"); 27 | } 28 | 29 | struct ErrorA : std::runtime_error { 30 | using std::runtime_error::runtime_error; 31 | }; 32 | 33 | struct ErrorB : std::runtime_error { 34 | using std::runtime_error::runtime_error; 35 | }; 36 | 37 | namespace std { 38 | bool operator==(const std::exception &a, const std::exception &b) { 39 | return a.what() == b.what(); 40 | } 41 | } // namespace std 42 | 43 | struct ValueCallBacksTest : ::testing::Test, 44 | flw::ValueCallBacks { 45 | std::tuple, std::vector, std::vector, 46 | std::vector> 47 | values; 48 | 49 | void clear() { 50 | std::get<0>(values).clear(); 51 | std::get<1>(values).clear(); 52 | std::get<2>(values).clear(); 53 | std::get<3>(values).clear(); 54 | } 55 | 56 | bool empty() const { 57 | return std::get<0>(values).empty() && std::get<1>(values).empty() && 58 | std::get<2>(values).empty() && std::get<3>(values).empty(); 59 | } 60 | 61 | template void addCB() { 62 | if constexpr (std::is_same_v) { 63 | this->addOnValue([this](const std::string &str) { 64 | std::get>(values).emplace_back(str); 65 | }); 66 | } else if constexpr (std::is_same_v) { 67 | this->addOnError([this](const std::exception &e) { 68 | std::get>(values).emplace_back( 69 | e.what()); 70 | }); 71 | } else { 72 | this->addOnError([this](const T &e) { 73 | std::get>(values).emplace_back(e.what()); 74 | }); 75 | } 76 | } 77 | 78 | template std::vector errorMessages() const { 79 | std::vector res; 80 | for (const auto &e : std::get>(values)) { 81 | res.emplace_back(e.what()); 82 | } 83 | return res; 84 | } 85 | }; 86 | 87 | TEST_F(ValueCallBacksTest, empty) { 88 | this->onValue(""); 89 | ASSERT_TRUE(this->empty()); 90 | 91 | this->onError(ErrorA{""}); 92 | ASSERT_TRUE(this->empty()); 93 | 94 | this->onError(ErrorB{""}); 95 | ASSERT_TRUE(this->empty()); 96 | 97 | this->onError(std::runtime_error{""}); 98 | ASSERT_TRUE(this->empty()); 99 | } 100 | 101 | TEST_F(ValueCallBacksTest, onValueCB_onValue) { 102 | this->addCB(); 103 | 104 | this->onValue("foo"); 105 | ASSERT_TRUE(std::get>(values).empty()); 106 | ASSERT_TRUE(std::get>(values).empty()); 107 | ASSERT_TRUE(std::get>(values).empty()); 108 | 109 | ASSERT_EQ(std::get>(values), 110 | std::vector{"foo"}); 111 | } 112 | 113 | TEST_F(ValueCallBacksTest, onValueCB_onError) { 114 | this->addCB(); 115 | 116 | this->onError(ErrorA{""}); 117 | ASSERT_TRUE(this->empty()); 118 | 119 | this->clear(); 120 | this->onError(std::runtime_error{""}); 121 | ASSERT_TRUE(this->empty()); 122 | } 123 | 124 | TEST_F(ValueCallBacksTest, onErrorCB_onValue) { 125 | this->addCB(); 126 | 127 | this->onValue("foo"); 128 | ASSERT_TRUE(this->empty()); 129 | } 130 | 131 | TEST_F(ValueCallBacksTest, onErrorCB_onError) { 132 | this->addCB(); 133 | 134 | this->onError(ErrorA{"foo"}); 135 | ASSERT_TRUE(std::get>(values).empty()); 136 | ASSERT_TRUE(std::get>(values).empty()); 137 | ASSERT_TRUE(std::get>(values).empty()); 138 | ASSERT_EQ(errorMessages(), std::vector{"foo"}); 139 | 140 | this->clear(); 141 | this->onError(ErrorB{""}); 142 | ASSERT_TRUE(this->empty()); 143 | 144 | this->clear(); 145 | this->onError(std::runtime_error{""}); 146 | ASSERT_TRUE(this->empty()); 147 | } 148 | 149 | template struct ValueAllCallBacksTest : ValueCallBacksTest { 150 | void SetUp() override { 151 | this->addCB(); 152 | this->addCB(); 153 | this->addCB(); 154 | this->addCB(); 155 | } 156 | }; 157 | 158 | using ValueAllCallBacksTypes = 159 | ::testing::Types; 160 | TYPED_TEST_CASE(ValueAllCallBacksTest, ValueAllCallBacksTypes); 161 | 162 | TYPED_TEST(ValueAllCallBacksTest, check) { 163 | if constexpr (std::is_same_v) { 164 | this->onValue("foo"); 165 | } else if constexpr (std::is_same_v) { 166 | this->template onError(TypeParam{"foo"}); 167 | } else { 168 | this->template onError(TypeParam{"foo"}); 169 | } 170 | 171 | if constexpr (std::is_same_v) { 172 | ASSERT_EQ(std::get>(this->values), 173 | std::vector{"foo"}); 174 | } else { 175 | ASSERT_EQ(this->template errorMessages(), 176 | std::vector{"foo"}); 177 | } 178 | } 179 | 180 | struct ValueTestBase : ::testing::Test { 181 | void SetUp() override { 182 | flw::ValueCallBacks cb; 183 | cb.addOnValue([&store = this->store](const std::string &str) { 184 | store.template emplace(str); 185 | }); 186 | cb.template addOnError([&store = this->store](const ErrorA &e) { 187 | store.template emplace(e.what()); 188 | }); 189 | cb.template addOnError([&store = this->store](const ErrorB &e) { 190 | store.template emplace(e.what()); 191 | }); 192 | cb.template addOnError( 193 | [&store = this->store](const std::exception &e) { 194 | store.template emplace(e.what()); 195 | }); 196 | val.emplace(std::move(cb)); 197 | } 198 | 199 | std::variant store; 200 | std::optional> val; 201 | }; 202 | 203 | TEST_F(ValueTestBase, onValue) { 204 | val->update([]() { return "hello world"; }); 205 | ASSERT_TRUE(std::holds_alternative(store)); 206 | ASSERT_EQ(std::get(store), "hello world"); 207 | } 208 | 209 | template std::string throw_exc() { 210 | throw ErrorT{"foo"}; 211 | return ""; 212 | } 213 | 214 | TEST_F(ValueTestBase, onError) { 215 | val->update(std::bind(&throw_exc)); 216 | ASSERT_TRUE(std::holds_alternative(store)); 217 | ASSERT_EQ(std::string{std::get(store).what()}, "foo"); 218 | } 219 | 220 | struct ErrorC : std::runtime_error { 221 | using std::runtime_error::runtime_error; 222 | }; 223 | 224 | TEST_F(ValueTestBase, onErrorGeneric) { 225 | val->update(std::bind(&throw_exc)); 226 | ASSERT_TRUE(std::holds_alternative(store)); 227 | ASSERT_EQ(std::string{std::get(store).what()}, "foo"); 228 | } 229 | -------------------------------------------------------------------------------- /tests/Test02-node.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #include 9 | 10 | #include 11 | #include "Common.h" 12 | 13 | #include 14 | 15 | 16 | template struct NodeTest : ::testing::Test { 17 | using CB = flw::ValueCallBacks; 18 | 19 | template void setUp(As &&...initialValues) { 20 | this->setUp_(std::forward(initialValues)..., CB{}); 21 | } 22 | 23 | template 24 | void setUp(As &&...initialValues, onSucess &&succ, onException &&err) { 25 | CB cb; 26 | cb.addOnValue(std::forward(succ)); 27 | cb.addOnError(std::forward(err)); 28 | this->setUp_(std::forward(initialValues)..., std::move(cb)); 29 | } 30 | 31 | std::tuple...> sources; 32 | std::shared_ptr> node; 33 | 34 | private: 35 | template 36 | void setUpSource_(Afront &&front, Arest &&...rest) { 37 | auto &ref = std::get(sources); 38 | ref = std::make_shared>(std::forward(front)); 39 | if constexpr (0 < sizeof...(Arest)) { 40 | setUpSource_(std::forward(rest)...); 41 | } 42 | } 43 | 44 | template void setUp_(As &&...initialValues, CB &&cb) { 45 | this->setUpSource_<0, As...>(std::forward(initialValues)...); 46 | node = flw::Node::make( 47 | [](const As &...vals) { 48 | if constexpr (Throw) { 49 | throw flw::test::ErrorTest{""}; 50 | } 51 | return flw::detail::merge<0>(vals...); 52 | }, 53 | sources, std::forward(cb)); 54 | } 55 | }; 56 | 57 | struct NodeTest_OneSource : NodeTest { 58 | void SetUp() override { this->setUp(int{306}); } 59 | }; 60 | 61 | TEST_F(NodeTest_OneSource, update) { 62 | node->update(); 63 | ASSERT_TRUE(node->value()->getValue()); 64 | EXPECT_EQ(*node->value()->getValue(), "306"); 65 | } 66 | 67 | TEST_F(NodeTest_OneSource, reset) { 68 | node->update(); 69 | node->reset(); 70 | ASSERT_FALSE(node->value()->getValue()); 71 | } 72 | 73 | struct NodeTest_MultipleSources : NodeTest { 74 | void SetUp() override { 75 | this->setUp(int{306}, "hello world", int{603}); 76 | } 77 | }; 78 | 79 | TEST_F(NodeTest_MultipleSources, update) { 80 | node->update(); 81 | ASSERT_TRUE(node->value()->getValue()); 82 | EXPECT_EQ(*node->value()->getValue(), "306hello world603"); 83 | } 84 | 85 | struct NodeTest_MultipleSources_withCB : NodeTest { 86 | template void doSetUp() { 87 | this->setUp( 88 | int{306}, "hello world", int{603}, 89 | [this](const std::string &str) { val.emplace(str); }, 90 | [this](const flw::test::ErrorTest &) { val.emplace(""); }); 91 | } 92 | 93 | struct None {}; 94 | std::variant val; 95 | }; 96 | 97 | TEST_F(NodeTest_MultipleSources_withCB, update_with_cb_sucess) { 98 | this->doSetUp(); 99 | 100 | node->update(); 101 | ASSERT_TRUE(node->value()->getValue()); 102 | EXPECT_EQ(*node->value()->getValue(), "306hello world603"); 103 | ASSERT_TRUE(std::holds_alternative(this->val)); 104 | } 105 | 106 | TEST_F(NodeTest_MultipleSources_withCB, update_with_cb_throw) { 107 | this->doSetUp(); 108 | 109 | node->update(); 110 | ASSERT_FALSE(node->value()->getValue()); 111 | ASSERT_TRUE(std::holds_alternative(this->val)); 112 | } 113 | -------------------------------------------------------------------------------- /tests/Test03-network-handlers.cpp: -------------------------------------------------------------------------------- 1 | // /** 2 | // * Author: Andrea Casalino 3 | // * Created: 10.09.2021 4 | // * 5 | // * report any bug to andrecasa91@gmail.com. 6 | // **/ 7 | #include 8 | 9 | #include "Common.h" 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | struct HandlerMakerTest : public flw::HandlerMaker, public ::testing::Test { 16 | template 17 | flw::HandlerSource makeTestSource(T &&val, const std::string &label = "") { 18 | flw::test::IncrementGuard<1, std::vector> 19 | guard_sources{this->sources_}; 20 | flw::test::UnchangedGuard> 21 | guard_nodes{this->nodes_}; 22 | return this->makeSource(std::forward(val), label); 23 | } 24 | 25 | template 26 | flw::Handler makeTestNode(const flw::Handler &...deps, 27 | const std::string &label = "") { 28 | flw::test::UnchangedGuard> guard_sources{ 29 | this->sources_}; 30 | flw::test::IncrementGuard<1, std::vector> 31 | guard_nodes{this->nodes_}; 32 | 33 | std::shared_ptr ptr = std::make_shared(); 34 | values.emplace_back(ptr); 35 | 36 | return this->makeNode( 37 | [](const As &...args) { return flw::detail::merge<0>(args...); }, 38 | deps..., label, [ptr = ptr](const std::string &str) { *ptr = str; }); 39 | } 40 | 41 | std::vector> values; 42 | }; 43 | 44 | TEST_F(HandlerMakerTest, OneDependency_OneLevel_creation) { 45 | auto source = this->makeTestSource(456); 46 | auto node = this->makeTestNode(source); 47 | 48 | EXPECT_EQ(*values.front(), "456"); 49 | EXPECT_TRUE(this->updatePending_.empty()); 50 | } 51 | 52 | TEST_F(HandlerMakerTest, FourDependency_OneLevel_creation) { 53 | auto source1 = this->makeTestSource(155); 54 | auto source2 = this->makeTestSource("foo"); 55 | auto source3 = this->makeTestSource(250); 56 | auto source4 = this->makeTestSource("bla"); 57 | 58 | auto node = this->makeTestNode( 59 | source1, source2, source3, source4); 60 | 61 | EXPECT_EQ(*values.front(), "155foo250bla"); 62 | EXPECT_TRUE(this->updatePending_.empty()); 63 | } 64 | 65 | TEST_F(HandlerMakerTest, TwoDependencies_TwoLevel_creation) { 66 | auto source_int = this->makeTestSource(155); 67 | auto source_string = this->makeTestSource("foo"); 68 | 69 | auto node1 = this->makeTestNode(source_int, source_string); 70 | 71 | EXPECT_EQ(*values.front(), "155foo"); 72 | EXPECT_TRUE(this->updatePending_.empty()); 73 | 74 | auto node2 = 75 | this->makeTestNode(node1, source_string); 76 | EXPECT_EQ(*values.back(), "155foofoo"); 77 | EXPECT_TRUE(this->updatePending_.empty()); 78 | } 79 | 80 | TEST_F(HandlerMakerTest, SourceLabeled_creation) { 81 | const std::string source0_name = "source0"; 82 | auto source0 = this->makeTestSource(0, source0_name); 83 | 84 | const std::string source1_name = "source1"; 85 | auto source1 = this->makeTestSource(1, source1_name); 86 | 87 | std::set keys, expected{"source0", "source1"}; 88 | for (const auto &[k, _] : this->labeled_elements_) { 89 | keys.emplace(k); 90 | } 91 | EXPECT_EQ(keys, expected); 92 | EXPECT_EQ(this->sources_to_labels_.size(), 2); 93 | EXPECT_EQ(this->nodes_to_labels_.size(), 0); 94 | } 95 | 96 | TEST_F(HandlerMakerTest, NodeLabeled_creation) { 97 | auto source = this->makeTestSource(0); 98 | 99 | const std::string node_name = "node"; 100 | auto node0 = this->makeTestNode(source, node_name); 101 | 102 | std::set keys, expected{"node"}; 103 | for (const auto &[k, _] : this->labeled_elements_) { 104 | keys.emplace(k); 105 | } 106 | EXPECT_EQ(keys, expected); 107 | EXPECT_EQ(this->sources_to_labels_.size(), 0); 108 | EXPECT_EQ(this->nodes_to_labels_.size(), 1); 109 | } 110 | -------------------------------------------------------------------------------- /tests/Test04-network-update.cpp: -------------------------------------------------------------------------------- 1 | // /** 2 | // * Author: Andrea Casalino 3 | // * Created: 10.09.2021 4 | // * 5 | // * report any bug to andrecasa91@gmail.com. 6 | // **/ 7 | #include 8 | 9 | #include "Common.h" 10 | #include 11 | 12 | #include 13 | 14 | struct UpdateTest : ::testing::Test { 15 | flw::Flow flow; 16 | 17 | struct Info { 18 | std::string value; 19 | std::size_t epoch = 0; 20 | }; 21 | std::vector values; 22 | 23 | void set(std::size_t index, const std::string &val) { 24 | values[index].value = val; 25 | ++values[index].epoch; 26 | } 27 | 28 | const auto &at(std::size_t index) const { return values.at(index); } 29 | 30 | template 31 | flw::Handler makeNode(const flw::Handler &s) { 32 | values.emplace_back(); 33 | return flow.makeNode( 34 | [lam = flw::test::ComposerLambda{}](const std::string &val) { 35 | return lam(val); 36 | }, 37 | s, "", 38 | std::bind(&UpdateTest::set, std::ref(*this), values.size() - 1, 39 | std::placeholders::_1)); 40 | } 41 | 42 | template 43 | flw::Handler makeNode(const flw::Handler &s0, 44 | const flw::Handler &s1) { 45 | values.emplace_back(); 46 | return flow.makeNode( 47 | [lam = flw::test::ComposerLambda{}]( 48 | const std::string &a, const std::string &b) { return lam(a, b); }, 49 | s0, s1, "", 50 | std::bind(&UpdateTest::set, std::ref(*this), values.size() - 1, 51 | std::placeholders::_1)); 52 | } 53 | }; 54 | 55 | TEST_F(UpdateTest, one_source_one_node_update) { 56 | auto source = flow.makeSource("Hello"); 57 | 58 | auto node = this->makeNode(source); 59 | 60 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 61 | EXPECT_EQ(at(0).value, "Hello"); 62 | EXPECT_EQ(at(0).epoch, 1); 63 | } 64 | 65 | TEST_F(UpdateTest, one_source_one_node_update_not_needed_as_same_val) { 66 | auto source = flow.makeSource("Hello"); 67 | 68 | auto node = this->makeNode(source); 69 | 70 | source.update("Hello"); 71 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 72 | EXPECT_EQ(at(0).value, "Hello"); 73 | EXPECT_EQ(at(0).epoch, 1); 74 | } 75 | 76 | TEST_F(UpdateTest, one_source_one_node_update_deferred) { 77 | auto source = flow.makeSource("Hello"); 78 | 79 | flow.setOnNewNodePolicy(flw::HandlerMaker::OnNewNodePolicy::DEFERRED_UPDATE); 80 | auto node = this->makeNode(source); 81 | 82 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_REQUIRED); 83 | 84 | flow.update(); 85 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 86 | EXPECT_EQ(at(0).value, "Hello"); 87 | EXPECT_EQ(at(0).epoch, 1); 88 | } 89 | 90 | TEST_F(UpdateTest, two_source_one_node_update) { 91 | auto source1 = flow.makeSource("Hello"); 92 | auto source2 = flow.makeSource("World"); 93 | 94 | auto node = this->makeNode(source1, source2); 95 | 96 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 97 | EXPECT_EQ(at(0).value, "HelloWorld"); 98 | EXPECT_EQ(at(0).epoch, 1); 99 | 100 | { 101 | SCOPED_TRACE("Update source 1"); 102 | 103 | source1.update("Ciao"); 104 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_REQUIRED); 105 | 106 | flow.update(); 107 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 108 | EXPECT_EQ(at(0).value, "CiaoWorld"); 109 | EXPECT_EQ(at(0).epoch, 2); 110 | } 111 | 112 | { 113 | SCOPED_TRACE("Update source 2"); 114 | 115 | source2.update("Mondo"); 116 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_REQUIRED); 117 | 118 | flow.update(); 119 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 120 | EXPECT_EQ(at(0).value, "CiaoMondo"); 121 | EXPECT_EQ(at(0).epoch, 3); 122 | } 123 | } 124 | 125 | TEST_F(UpdateTest, two_source_one_node_update_not_needed_as_same_val) { 126 | auto source1 = flow.makeSource("Hello"); 127 | auto source2 = flow.makeSource("World"); 128 | 129 | auto node = this->makeNode(source1, source2); 130 | 131 | source1.update("Hello"); 132 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 133 | EXPECT_EQ(at(0).value, "HelloWorld"); 134 | EXPECT_EQ(at(0).epoch, 1); 135 | 136 | source2.update("World"); 137 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 138 | EXPECT_EQ(at(0).value, "HelloWorld"); 139 | EXPECT_EQ(at(0).epoch, 1); 140 | } 141 | 142 | TEST_F(UpdateTest, two_source_two_node_joint_update) { 143 | auto source1 = flow.makeSource("Hello"); 144 | auto source2 = flow.makeSource("World"); 145 | 146 | auto node1 = this->makeNode(source1, source2); 147 | auto node2 = this->makeNode(source1, source2); 148 | 149 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 150 | for (std::size_t k = 0; k < 2; ++k) { 151 | EXPECT_EQ(at(k).value, "HelloWorld"); 152 | EXPECT_EQ(at(k).epoch, 1); 153 | } 154 | } 155 | 156 | TEST_F(UpdateTest, two_source_two_node_and_one_node_update) { 157 | auto source1 = flow.makeSource("Hello"); 158 | auto source2 = flow.makeSource("World"); 159 | 160 | auto node1 = this->makeNode(source1, source2); 161 | auto node2 = this->makeNode(source1, source2); 162 | 163 | auto node3 = this->makeNode(node1, node2); 164 | 165 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 166 | EXPECT_EQ(at(0).value, "HelloWorld"); 167 | EXPECT_EQ(at(0).epoch, 1); 168 | EXPECT_EQ(at(1).value, "HelloWorld"); 169 | EXPECT_EQ(at(1).epoch, 1); 170 | EXPECT_EQ(at(2).value, "HelloWorldHelloWorld"); 171 | EXPECT_EQ(at(2).epoch, 1); 172 | } 173 | 174 | TEST_F(UpdateTest, one_source_one_node_then_fork_update_with_exception) { 175 | auto source = flow.makeSource("Hello"); 176 | 177 | auto node_1 = this->makeNode(source); // first time exception is thrown 178 | 179 | auto node_2_1 = this->makeNode(node_1); 180 | auto node_2_2 = this->makeNode(node_1); 181 | 182 | for (auto &[_, epoch] : values) { 183 | EXPECT_EQ(at(0).epoch, 0); 184 | } 185 | 186 | source.update("Helloo"); 187 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_REQUIRED); 188 | 189 | flow.update(); 190 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 191 | for (auto &[val, epoch] : values) { 192 | EXPECT_EQ(at(0).value, "Helloo"); 193 | EXPECT_EQ(at(0).epoch, 1); 194 | } 195 | } 196 | 197 | TEST_F(UpdateTest, one_source_then_fork_then_sink_update_with_exception) { 198 | auto source = flow.makeSource("Hello"); 199 | 200 | auto node_1_1 = 201 | this->makeNode(source); // first time exception is thrown 202 | auto node_1_2 = 203 | this->makeNode(source); // first time exception is thrown 204 | 205 | auto node_2 = this->makeNode(node_1_1, node_1_2); 206 | 207 | EXPECT_EQ(at(0).epoch, 0); 208 | EXPECT_EQ(at(1).value, "Hello"); 209 | EXPECT_EQ(at(1).epoch, 1); 210 | EXPECT_EQ(at(2).epoch, 0); 211 | 212 | source.update("Helloo"); 213 | 214 | flow.update(); 215 | ASSERT_EQ(flow.status(), flw::FlowStatus::UPDATE_NOT_REQUIRED); 216 | EXPECT_EQ(at(0).epoch, 1); 217 | EXPECT_EQ(at(0).value, "Helloo"); 218 | EXPECT_EQ(at(1).epoch, 2); 219 | EXPECT_EQ(at(1).value, "Helloo"); 220 | EXPECT_EQ(at(2).epoch, 1); 221 | EXPECT_EQ(at(2).value, "HellooHelloo"); 222 | } 223 | -------------------------------------------------------------------------------- /tests/Test05-network-update-lazyness.cpp: -------------------------------------------------------------------------------- 1 | // /** 2 | // * Author: Andrea Casalino 3 | // * Created: 10.09.2021 4 | // * 5 | // * report any bug to andrecasa91@gmail.com. 6 | // **/ 7 | 8 | #include 9 | 10 | #include 11 | 12 | struct Summer { 13 | int operator()(int val) { return val; } 14 | 15 | int operator()(int a, int b) { return a + b; } 16 | }; 17 | 18 | class LazynessTest : public ::testing::Test, 19 | public flw::HandlerMaker, 20 | public flw::Updater { 21 | public: 22 | LazynessTest() = default; 23 | 24 | struct Source { 25 | Source(flw::HandlerSource &&s) 26 | : source{std::forward>(s)} {} 27 | 28 | int val = 0; 29 | flw::HandlerSource source; 30 | }; 31 | std::vector sources; 32 | 33 | struct NodeValue { 34 | int val; 35 | std::size_t epochs = 0; 36 | }; 37 | NodeValue M0_val; 38 | NodeValue M1_val; 39 | NodeValue D0_val; 40 | NodeValue D1_val; 41 | NodeValue F0_val; 42 | 43 | template 44 | auto makeNode_(NodeValue &rec, const flw::Handler &...deps) { 45 | return this->makeNode( 46 | [](const As &...vals) { 47 | static Summer s; 48 | return s(vals...); 49 | }, 50 | deps..., "", 51 | [&rec](int val) { 52 | rec.val = val; 53 | ++rec.epochs; 54 | }); 55 | } 56 | 57 | template void updateSource(int val) { 58 | sources[Index].val = val; 59 | sources[Index].source.update(val); 60 | } 61 | 62 | void SetUp() override { 63 | auto cloner = [](const auto &in) { return in; }; 64 | auto combiner = [](const auto &in1, const auto &in2) { return in1 + in2; }; 65 | 66 | sources.emplace_back(this->makeSource(0)); 67 | sources.emplace_back(this->makeSource(0)); 68 | sources.emplace_back(this->makeSource(0)); 69 | sources.emplace_back(this->makeSource(0)); 70 | 71 | auto M0 = 72 | this->makeNode_(M0_val, sources[0].source, sources[1].source); 73 | auto M1 = 74 | this->makeNode_(M1_val, sources[2].source, sources[3].source); 75 | 76 | auto D0 = this->makeNode_(D0_val, M0); 77 | auto D1 = this->makeNode_(D1_val, M0, M1); 78 | 79 | auto F0 = this->makeNode_(F0_val, D0, D1); 80 | 81 | this->checkValues(); 82 | } 83 | 84 | void checkValues() { 85 | EXPECT_EQ(sources[0].val + sources[1].val, M0_val.val); 86 | EXPECT_EQ(sources[2].val + sources[3].val, M1_val.val); 87 | 88 | EXPECT_EQ(M0_val.val, D0_val.val); 89 | EXPECT_EQ(M0_val.val + M1_val.val, D1_val.val); 90 | 91 | EXPECT_EQ(D0_val.val + D1_val.val, F0_val.val); 92 | } 93 | }; 94 | 95 | TEST_F(LazynessTest, case_0_same_source_values) { 96 | this->updateSource<0>(0); 97 | this->updateSource<1>(0); 98 | this->updateSource<2>(0); 99 | this->updateSource<3>(0); 100 | 101 | this->update(); 102 | 103 | // no update expected as the value is still the same 104 | EXPECT_EQ(M0_val.epochs, 1); 105 | EXPECT_EQ(M1_val.epochs, 1); 106 | EXPECT_EQ(D0_val.epochs, 1); 107 | EXPECT_EQ(D1_val.epochs, 1); 108 | EXPECT_EQ(F0_val.epochs, 1); 109 | } 110 | 111 | TEST_F(LazynessTest, case_1_0_only_one_of_the_source) { 112 | this->updateSource<0>(1); 113 | 114 | this->update(); 115 | 116 | this->checkValues(); 117 | 118 | EXPECT_EQ(M0_val.val, 1); 119 | EXPECT_EQ(M0_val.epochs, 2); 120 | EXPECT_EQ(M1_val.val, 0); 121 | EXPECT_EQ(M1_val.epochs, 1); 122 | 123 | EXPECT_EQ(D0_val.val, 1); 124 | EXPECT_EQ(D0_val.epochs, 2); 125 | EXPECT_EQ(D1_val.val, 1); 126 | EXPECT_EQ(D1_val.epochs, 2); 127 | 128 | EXPECT_EQ(F0_val.val, 2); 129 | EXPECT_EQ(F0_val.epochs, 2); 130 | } 131 | 132 | TEST_F(LazynessTest, case_1_1_only_one_of_the_source) { 133 | this->updateSource<2>(1); 134 | 135 | this->update(); 136 | 137 | this->checkValues(); 138 | 139 | EXPECT_EQ(M0_val.val, 0); 140 | EXPECT_EQ(M0_val.epochs, 1); 141 | EXPECT_EQ(M1_val.val, 1); 142 | EXPECT_EQ(M1_val.epochs, 2); 143 | 144 | EXPECT_EQ(D0_val.val, 0); 145 | EXPECT_EQ(D0_val.epochs, 1); 146 | EXPECT_EQ(D1_val.val, 1); 147 | EXPECT_EQ(D1_val.epochs, 2); 148 | 149 | EXPECT_EQ(F0_val.val, 1); 150 | EXPECT_EQ(F0_val.epochs, 2); 151 | } 152 | 153 | TEST_F(LazynessTest, case_2_multiple_sources) { 154 | this->updateSource<0>(1); 155 | this->updateSource<2>(2); 156 | 157 | this->update(); 158 | this->checkValues(); 159 | 160 | EXPECT_EQ(M0_val.val, 1); 161 | EXPECT_EQ(M0_val.epochs, 2); 162 | EXPECT_EQ(M1_val.val, 2); 163 | EXPECT_EQ(M1_val.epochs, 2); 164 | 165 | EXPECT_EQ(D0_val.val, 1); 166 | EXPECT_EQ(D0_val.epochs, 2); 167 | EXPECT_EQ(D1_val.val, 3); 168 | EXPECT_EQ(D1_val.epochs, 2); 169 | 170 | EXPECT_EQ(F0_val.val, 4); 171 | EXPECT_EQ(F0_val.epochs, 2); 172 | } 173 | -------------------------------------------------------------------------------- /tests/Test05-network-update-lazyness.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 29 | 35 | 36 | 44 | 50 | 51 | 59 | 65 | 66 | 74 | 80 | 81 | 89 | 95 | 96 | 104 | 110 | 111 | 119 | 125 | 126 | 134 | 140 | 141 | 149 | 155 | 156 | 157 | 175 | 177 | 178 | 180 | image/svg+xml 181 | 183 | 184 | 185 | 186 | 187 | 192 | 198 | S1 210 | 216 | S0 228 | 234 | S3 246 | 252 | S2 264 | 270 | M0 282 | 288 | M1 300 | 306 | D0 318 | 324 | D1 336 | 342 | F0 354 | 359 | 364 | 370 | 376 | 382 | 388 | 394 | 400 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /tests/Test06-network-thread-safety.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Author: Andrea Casalino 3 | * Created: 10.09.2021 4 | * 5 | * report any bug to andrecasa91@gmail.com. 6 | **/ 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | TEST(ThreadSafetyTest, node_creation_while_updating_flow) { 16 | flw::Flow flow; 17 | flow.setOnNewNodePolicy(flw::HandlerMaker::OnNewNodePolicy::DEFERRED_UPDATE); 18 | 19 | auto source = flow.makeSource(0); 20 | auto node = flow.makeNode( 21 | [](int input) { 22 | std::this_thread::sleep_for(std::chrono::seconds(2)); 23 | return input; 24 | }, 25 | source); 26 | 27 | bool node2_was_updated = false; 28 | 29 | { 30 | SCOPED_TRACE("Adding an extra node while updating the flow"); 31 | 32 | std::atomic_bool spawned = false; 33 | std::jthread th([&]() { 34 | spawned.store(true, std::memory_order_acquire); 35 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 36 | flow.makeNode( 37 | [](int input) { return input; }, node, "", 38 | [&node2_was_updated](int) { node2_was_updated = true; }); 39 | }); 40 | while (!spawned.load(std::memory_order_acquire)) { 41 | } 42 | flow.update(); 43 | } 44 | 45 | ASSERT_FALSE(node2_was_updated); 46 | 47 | flow.update(); 48 | ASSERT_TRUE(node2_was_updated); 49 | } 50 | 51 | namespace flw::test { 52 | struct UnComparable { 53 | int val = 0; 54 | 55 | bool operator==(const UnComparable &) const = delete; 56 | }; 57 | 58 | class MultiThreadedUpdateFixture : public ::testing::TestWithParam, 59 | public flw::HandlerFinder, 60 | public flw::HandlerMaker, 61 | public flw::Updater { 62 | protected: 63 | using Source = HandlerSource; 64 | std::vector sources; 65 | std::vector> nodes_values; 66 | 67 | public: 68 | static constexpr std::size_t THREADS = 69 | 2; // cannot be done with more than 2 threads on the CI but can be changed 70 | // to do local tests 71 | 72 | template 73 | void makeNextLevel(std::vector> &res, 74 | const std::vector &prev_layer) { 75 | std::size_t index_a = nodes_values.size(); 76 | nodes_values.emplace_back().resize(prev_layer.size() - 1); 77 | res.clear(); 78 | res.reserve(prev_layer.size() - 1); 79 | for (std::size_t index_b = 0; index_b < prev_layer.size() - 1; ++index_b) { 80 | res.emplace_back(this->makeNode( 81 | [](const UnComparable &in1, const UnComparable &in2) { 82 | std::this_thread::sleep_for(std::chrono::milliseconds(50)); 83 | return UnComparable{in1.val + in2.val}; 84 | }, 85 | prev_layer[index_b], prev_layer.back(), "", 86 | [a = index_a, b = index_b, this](const UnComparable &v) { 87 | nodes_values[a][b] = v.val; 88 | })); 89 | } 90 | } 91 | 92 | void SetUp() override { 93 | std::size_t layers = GetParam(); 94 | this->setOnNewNodePolicy(OnNewNodePolicy::DEFERRED_UPDATE); 95 | 96 | for (std::size_t s = 0; s < (layers + 1); ++s) { 97 | sources.push_back(this->makeSource(UnComparable{1})); 98 | } 99 | 100 | // first layer 101 | std::vector> layer; 102 | makeNextLevel(layer, sources); 103 | // other layers 104 | std::vector> next; 105 | for (std::size_t l = 1; l < layers; ++l) { 106 | makeNextLevel(next, layer); 107 | std::swap(next, layer); 108 | } 109 | } 110 | 111 | bool checkValues() const { 112 | int expected_val = 2; 113 | return std::all_of(nodes_values.begin(), nodes_values.end(), 114 | [&](const std::vector &layer) { 115 | bool res = std::all_of( 116 | layer.begin(), layer.end(), 117 | [&](int val) { return val == expected_val; }); 118 | expected_val *= 2; 119 | return res; 120 | }); 121 | } 122 | 123 | std::chrono::nanoseconds update(std::size_t threads) { 124 | this->setThreads(threads); 125 | 126 | for (auto &src : sources) { 127 | src.update(UnComparable{1}); 128 | } 129 | 130 | auto tic = std::chrono::high_resolution_clock::now(); 131 | this->flw::Updater::update(); 132 | return std::chrono::duration_cast( 133 | std::chrono::high_resolution_clock::now() - tic); 134 | } 135 | }; 136 | 137 | TEST_P(MultiThreadedUpdateFixture, compare_durations) { 138 | auto serial_time = this->update(1); 139 | ASSERT_TRUE(checkValues()); 140 | std::cout << "Serial version: " 141 | << std::chrono::duration_cast( 142 | serial_time) 143 | .count() 144 | << " [ms]" << std::endl; 145 | 146 | auto parallel_time = this->update(THREADS); 147 | ASSERT_TRUE(checkValues()); 148 | std::cout << "Multithreaded version: " 149 | << std::chrono::duration_cast( 150 | parallel_time) 151 | .count() 152 | << " [ms]" << std::endl; 153 | 154 | EXPECT_GE(serial_time, parallel_time); 155 | } 156 | 157 | INSTANTIATE_TEST_CASE_P(MultiThreadedUpdateTests, MultiThreadedUpdateFixture, 158 | ::testing::Values(5, 7, 10)); 159 | } // namespace flw::test 160 | --------------------------------------------------------------------------------