From 26b52a3402fa3cf4558d630d716e4604ffeaf4a5 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 7 Sep 2022 14:51:18 +0200 Subject: [PATCH 001/239] [externals] Add Dataflow external stduuid --- doc/basics/dependencies.md | 5 ++++ external/CMakeLists.txt | 2 ++ external/Core/CMakeLists.txt | 6 ++--- external/Dataflow/CMakeLists.txt | 36 +++++++++++++++++++++++++ external/Dataflow/package.cmake | 4 +++ external/Dataflow/patches/stduuid.patch | 24 +++++++++++++++++ 6 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 external/Dataflow/CMakeLists.txt create mode 100644 external/Dataflow/package.cmake create mode 100644 external/Dataflow/patches/stduuid.patch diff --git a/doc/basics/dependencies.md b/doc/basics/dependencies.md index ab706eebad4..73031d687cc 100644 --- a/doc/basics/dependencies.md +++ b/doc/basics/dependencies.md @@ -7,6 +7,7 @@ Radium relies on several external libraries to load files or to represent some d * [Engine] glm, globjects, glbindings, tinyEXR * [IO] Assimp * [Gui] Qt Core, Qt Widgets and Qt OpenGL v5.5+ (5.14 at least, Qt6 support is experimental), PowerSlider +* [Dataflow] stduuid * [doc] Doxygen-awesome-css * stb_image @@ -100,6 +101,7 @@ set(tinyEXR_DIR "/path/to/external/install/share/tinyEXR/cmake/" CACHE PATH "My set(assimp_DIR "/path/to/external/install/lib/cmake/assimp-5.0/" CACHE PATH "My assimp location") set(tinyply_DIR "/path/to/external/install/lib/cmake/tinyply/" CACHE PATH "My tinyply location") set(PowerSlider_DIR "/path/to/external/install/lib/cmake/PowerSlider/" CACHE PATH "My PowerSlider location") +set(stduuid_DIR "${RADIUM_DEP_PREFIX}/lib/cmake/stduuid/" CACHE PATH "My stduuid") set(RADIUM_IO_ASSIMP ON CACHE BOOL "Radium uses assimp io") set(RADIUM_IO_TINYPLY ON CACHE BOOL "Radium uses tinyply io") ~~~ @@ -122,6 +124,7 @@ To this end, just provide the corresponding '*_DIR' to cmake at configuration ti Currently supported (note that these paths must refer to the installation directory of the corresponding library): +* `stduuid_DIR` * `assimp_DIR` * `tinyply_DIR` * `PowerSlider_DIR` @@ -137,6 +140,8 @@ Currently supported (note that these paths must refer to the installation direct Radium is compiled and tested with specific version of dependencies, as given in the external's folder CMakeLists.txt and state here for the record +* stduuid: https://github.com/mariusbancila/stduuid, [3afe7193facd5d674de709fccc44d5055e144d7a], + * with options `-DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON` * assimp: https://github.com/assimp/assimp.git, [tags/v5.0.1], * with options `-DASSIMP_BUILD_ASSIMP_TOOLS=False -DASSIMP_BUILD_SAMPLES=False -DASSIMP_BUILD_TESTS=False -DIGNORE_GIT_HASH=True -DASSIMP_NO_EXPORT=True` * tinyply: https://github.com/ddiakopoulos/tinyply.git, [tags/2.3.2], diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index a09f9af4099..7b06fe131fe 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -101,6 +101,8 @@ else() add_subdirectory(IO) # add Gui dependencies add_subdirectory(Gui) + # add Dataflow dependencies + add_subdirectory(Dataflow) # generate radium-option.cmake configuration file file(REMOVE "${CMAKE_INSTALL_PREFIX}/radium-options.cmake") diff --git a/external/Core/CMakeLists.txt b/external/Core/CMakeLists.txt index 06fd9589787..21ce5678226 100644 --- a/external/Core/CMakeLists.txt +++ b/external/Core/CMakeLists.txt @@ -19,7 +19,7 @@ add_custom_target(CoreExternals ALL) if(NOT DEFINED Eigen3_DIR) check_externals_prerequisite() - status_message("" "eigen3" "remote git") + status_message("[CoreExternal]" "eigen3" "remote git") ExternalProject_Add( Eigen3 @@ -41,7 +41,7 @@ endif() if(NOT DEFINED OpenMesh_DIR) check_externals_prerequisite() - status_message("" "OpenMesh" "remote git") + status_message("[CoreExternal]" "OpenMesh" "remote git") # I found some problems when generating xcode project for OpenMesh: the libXXX.dylib link to # libXXX.Major.minor.dylib (eg libOpenMesh.2.1.dylib) is not generated and the script failed. # Need to generate this link manually. TODO, find why only OpenMesh is problematic @@ -63,7 +63,7 @@ endif() if(NOT DEFINED cpplocate_DIR OR NOT cpplocate_DIR) check_externals_prerequisite() - status_message("" "cpplocate" "remote git") + status_message("[CoreExternal]" "cpplocate" "remote git") ExternalProject_Add( cpplocate GIT_REPOSITORY https://github.com/cginternals/cpplocate.git diff --git a/external/Dataflow/CMakeLists.txt b/external/Dataflow/CMakeLists.txt new file mode 100644 index 00000000000..3820aff385c --- /dev/null +++ b/external/Dataflow/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.6) + +project(radiumdataflow-external VERSION 1.0.0) + +include(ExternalProject) +include(ExternalInclude) + +# force installing by default all the external projects +set_property(DIRECTORY PROPERTY EP_STEP_TARGETS install) + +# Add fPIC for all dependencies +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +string(REPLACE ";" "" indent_string "${CMAKE_MESSAGE_INDENT}") +set(indent_string "${indent_string}--") + +add_custom_target(DataflowExternals ALL) + +if(NOT DEFINED stduuid_DIR) + check_externals_prerequisite() + status_message("[DataflowExternal]" "stduuid" "remote git") + ExternalProject_Add( + stduuid + GIT_TAG 3afe7193facd5d674de709fccc44d5055e144d7a + GIT_REPOSITORY https://github.com/mariusbancila/stduuid + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + PATCH_COMMAND git reset --hard && git apply -v --ignore-whitespace + "${CMAKE_CURRENT_LIST_DIR}/patches/stduuid.patch" + INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" + CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} -DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON + "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" + ) + add_dependencies(DataflowExternals stduuid) +else() + status_message("" "stduuid" ${stduuid_DIR}) +endif() diff --git a/external/Dataflow/package.cmake b/external/Dataflow/package.cmake new file mode 100644 index 00000000000..61807b50091 --- /dev/null +++ b/external/Dataflow/package.cmake @@ -0,0 +1,4 @@ +if(NOT DEFINED stduuid_DIR) + set(stduuid_sub_DIR lib/cmake/stduuid CACHE INTERNAL "") + set(stduuid_DIR ${CMAKE_INSTALL_PREFIX}/${stduuid_sub_DIR}) +endif() diff --git a/external/Dataflow/patches/stduuid.patch b/external/Dataflow/patches/stduuid.patch new file mode 100644 index 00000000000..4022183e398 --- /dev/null +++ b/external/Dataflow/patches/stduuid.patch @@ -0,0 +1,24 @@ +diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in +index 7217b72..6b099bc 100644 +--- a/cmake/Config.cmake.in ++++ b/cmake/Config.cmake.in +@@ -1,3 +1,7 @@ ++include(FindPackageHandleStandardArgs) ++set(${CMAKE_FIND_PACKAGE_NAME}_CONFIG ${CMAKE_CURRENT_LIST_FILE}) ++find_package_handle_standard_args(@PROJECT_NAME@ CONFIG_MODE) ++ + @PACKAGE_INIT@ + + include(CMakeFindDependencyMacro) +@@ -9,6 +13,7 @@ if (@UUID_SYSTEM_GENERATOR@) + endif () + endif () + +-include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") +- +-check_required_components(@PROJECT_NAME@) +\ No newline at end of file ++if(NOT TARGET @PROJECT_NAME@) ++ include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") ++endif() ++check_required_components(@PROJECT_NAME@) From c0f285a7b640ae65970fd9b0dc6eed4344a8df09 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 7 Sep 2022 16:51:04 +0200 Subject: [PATCH 002/239] [core] add random point sets --- src/Core/Random/RandomPointSet.cpp | 56 +++++++++++ src/Core/Random/RandomPointSet.hpp | 152 +++++++++++++++++++++++++++++ src/Core/Random/RandomPointSet.inl | 32 ++++++ src/Core/filelist.cmake | 2 + 4 files changed, 242 insertions(+) create mode 100644 src/Core/Random/RandomPointSet.cpp create mode 100644 src/Core/Random/RandomPointSet.hpp create mode 100644 src/Core/Random/RandomPointSet.inl diff --git a/src/Core/Random/RandomPointSet.cpp b/src/Core/Random/RandomPointSet.cpp new file mode 100644 index 00000000000..2797bfc6162 --- /dev/null +++ b/src/Core/Random/RandomPointSet.cpp @@ -0,0 +1,56 @@ +#include + +namespace Ra { +namespace Core { +namespace Random { + +FibonacciSequence::FibonacciSequence( size_t number ) : n { std::max( size_t( 5 ), number ) } {}; + +size_t FibonacciSequence::range() { + return n; +} +Scalar FibonacciSequence::operator()( size_t i ) { + return Scalar( i ) / phi; +} + +Scalar VanDerCorputSequence::operator()( unsigned int bits ) { + bits = ( bits << 16u ) | ( bits >> 16u ); + bits = ( ( bits & 0x55555555u ) << 1u ) | ( ( bits & 0xAAAAAAAAu ) >> 1u ); + bits = ( ( bits & 0x33333333u ) << 2u ) | ( ( bits & 0xCCCCCCCCu ) >> 2u ); + bits = ( ( bits & 0x0F0F0F0Fu ) << 4u ) | ( ( bits & 0xF0F0F0F0u ) >> 4u ); + bits = ( ( bits & 0x00FF00FFu ) << 8u ) | ( ( bits & 0xFF00FF00u ) >> 8u ); + return Scalar( float( bits ) * 2.3283064365386963e-10 ); // / 0x100000000 +} + +FibonacciPointSet::FibonacciPointSet( size_t n ) : seq( n ) {}; + +size_t FibonacciPointSet::range() { + return seq.range(); +} +Ra::Core::Vector2 FibonacciPointSet::operator()( size_t i ) { + return { seq( i ), Scalar( i ) / Scalar( range() ) }; +} + +HammersleyPointSet::HammersleyPointSet( size_t number ) : n( number ) {}; + +size_t HammersleyPointSet::range() { + return n; +} + +Ra::Core::Vector2 HammersleyPointSet::operator()( size_t i ) { + return { Scalar( i ) / Scalar( range() ), seq( i ) }; +} + +MersenneTwisterPointSet::MersenneTwisterPointSet( size_t number ) : + gen( 0 ), seq( 0., 1. ), n( number ) {}; + +size_t MersenneTwisterPointSet::range() { + return n; +} +Ra::Core::Vector2 MersenneTwisterPointSet::operator()( size_t ) { + return { seq( gen ), seq( gen ) }; +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/Random/RandomPointSet.hpp b/src/Core/Random/RandomPointSet.hpp new file mode 100644 index 00000000000..ffed0ae8e4e --- /dev/null +++ b/src/Core/Random/RandomPointSet.hpp @@ -0,0 +1,152 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Core { + +/** \brief Random point set utilities. + * + * This namespace contains functions, classes and utilities for 1D, 2D and 3D random point sets + * management. + * + */ +namespace Random { + +Scalar constexpr sqrtNewtonRaphsonhelper( Scalar x, Scalar curr, Scalar prev ) { + return curr == prev ? curr : sqrtNewtonRaphsonhelper( x, 0.5_ra * ( curr + x / curr ), curr ); +} +Scalar constexpr ct_sqrt( Scalar x ) { + return sqrtNewtonRaphsonhelper( x, x, 0_ra ); +} + +/** \brief Implements the fibonacci sequence + * i --> i/phi + * where phi = (1 + sqrt(5)) / 2 + */ +class RA_CORE_API FibonacciSequence +{ + + static constexpr Scalar phi = ( 1_ra + ct_sqrt( 5_ra ) ) / 2_ra; + size_t n; + + public: + explicit FibonacciSequence( size_t number ); + // copyable + FibonacciSequence( const FibonacciSequence& ) = default; + FibonacciSequence& operator=( const FibonacciSequence& ) = default; + // movable + FibonacciSequence( FibonacciSequence&& ) = default; + FibonacciSequence& operator=( FibonacciSequence&& ) = default; + virtual ~FibonacciSequence() = default; + + size_t range(); + Scalar operator()( size_t i ); +}; + +/** \brief 1D Van der Corput sequence + * only implemented for 32bits floats (converted out to Scalar) + */ +struct RA_CORE_API VanDerCorputSequence { + Scalar operator()( unsigned int bits ); +}; + +/** \brief Implements the 2D fibonacci Point set + * points follow the FibonacciSequence + * (i, N) => [i / phi, i / N] + */ +class RA_CORE_API FibonacciPointSet +{ + FibonacciSequence seq; + + public: + explicit FibonacciPointSet( size_t n ); + // copyable + FibonacciPointSet( const FibonacciPointSet& ) = default; + FibonacciPointSet& operator=( const FibonacciPointSet& ) = default; + // movable + FibonacciPointSet( FibonacciPointSet&& ) = default; + FibonacciPointSet& operator=( FibonacciPointSet&& ) = default; + virtual ~FibonacciPointSet() = default; + + size_t range(); + Ra::Core::Vector2 operator()( size_t i ); +}; + +/** \brief 2D Hammersley point set + * + */ +class RA_CORE_API HammersleyPointSet +{ + VanDerCorputSequence seq; + size_t n; + + public: + explicit HammersleyPointSet( size_t number ); + // copyable + HammersleyPointSet( const HammersleyPointSet& ) = default; + HammersleyPointSet& operator=( const HammersleyPointSet& ) = default; + // movable + HammersleyPointSet( HammersleyPointSet&& ) = default; + HammersleyPointSet& operator=( HammersleyPointSet&& ) = default; + virtual ~HammersleyPointSet() = default; + + size_t range(); + Ra::Core::Vector2 operator()( size_t i ); +}; + +/** \brief 2D Random point set + */ +class RA_CORE_API MersenneTwisterPointSet +{ + std::mt19937 gen; + std::uniform_real_distribution<> seq; + size_t n; + + public: + explicit MersenneTwisterPointSet( size_t number ); + // copyable + MersenneTwisterPointSet( const MersenneTwisterPointSet& ) = default; + MersenneTwisterPointSet& operator=( const MersenneTwisterPointSet& ) = default; + // movable + MersenneTwisterPointSet( MersenneTwisterPointSet&& ) = default; + MersenneTwisterPointSet& operator=( MersenneTwisterPointSet&& ) = default; + virtual ~MersenneTwisterPointSet() = default; + + size_t range(); + Ra::Core::Vector2 operator()( size_t ); +}; + +// https://observablehq.com/@mbostock/spherical-fibonacci-lattice +// http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html +/** \brief Map a [0, 1)^2 point set on the unit sphere + * \tparam PointSet the random point set type to map on sphere + */ +template +class SphericalPointSet +{ + PointSet p; + Ra::Core::Vector3 projectOnSphere( const Ra::Core::Vector2&& pt ); + + public: + explicit SphericalPointSet( size_t n ); + // copyable + SphericalPointSet( const SphericalPointSet& ) = default; + SphericalPointSet& operator=( const SphericalPointSet& ) = default; + // movable + SphericalPointSet( SphericalPointSet&& ) = default; + SphericalPointSet& operator=( SphericalPointSet&& ) = default; + virtual ~SphericalPointSet() = default; + + size_t range(); + Ra::Core::Vector3 operator()( size_t i ); +}; + +} // namespace Random +} // namespace Core +} // namespace Ra + +#include diff --git a/src/Core/Random/RandomPointSet.inl b/src/Core/Random/RandomPointSet.inl new file mode 100644 index 00000000000..525cbbfc329 --- /dev/null +++ b/src/Core/Random/RandomPointSet.inl @@ -0,0 +1,32 @@ +#pragma once +#include + +namespace Ra { +namespace Core { +namespace Random { + +template +Ra::Core::Vector3 SphericalPointSet::projectOnSphere( const Ra::Core::Vector2&& pt ) { + Scalar theta = std::acos( 2 * pt[1] - 1 ); // 0 <= tetha <= pi + Scalar phi = 2_ra * Scalar( M_PI ) * pt[0]; + return { std::sin( theta ) * std::cos( phi ), + std::sin( theta ) * std::sin( phi ), + std::cos( theta ) }; +} + +template +SphericalPointSet::SphericalPointSet( size_t n ) : p( n ) {} + +template +size_t SphericalPointSet::range() { + return p.range(); +} + +template +Ra::Core::Vector3 SphericalPointSet::operator()( size_t i ) { + return projectOnSphere( p( i ) ); +} + +} // namespace Random +} // namespace Core +} // namespace Ra diff --git a/src/Core/filelist.cmake b/src/Core/filelist.cmake index 7aabbe1b640..4760bf220f9 100644 --- a/src/Core/filelist.cmake +++ b/src/Core/filelist.cmake @@ -35,6 +35,7 @@ set(core_sources Geometry/TriangleMesh.cpp Geometry/Volume.cpp Geometry/deprecated/TopologicalMesh.cpp + Random/RandomPointSet.cpp Resources/Resources.cpp Tasks/TaskQueue.cpp Utils/Attribs.cpp @@ -107,6 +108,7 @@ set(core_headers Math/Math.hpp Math/Quadric.hpp RaCore.hpp + Random/RandomPointSet.hpp Resources/Resources.hpp Tasks/Task.hpp Tasks/TaskQueue.hpp From 5a4d94abea38bed84f26a202b74bf9882eadcc7b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 7 Sep 2022 16:51:31 +0200 Subject: [PATCH 003/239] [unittests][core] add tests on random point sets --- tests/unittest/CMakeLists.txt | 1 + tests/unittest/Core/random.cpp | 95 ++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 tests/unittest/Core/random.cpp diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index aa26cd009d9..496468e1f74 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -22,6 +22,7 @@ set(test_src Core/obb.cpp Core/observer.cpp Core/polyline.cpp + Core/random.cpp Core/raycast.cpp Core/resources.cpp Core/string.cpp diff --git a/tests/unittest/Core/random.cpp b/tests/unittest/Core/random.cpp new file mode 100644 index 00000000000..27383ea05b3 --- /dev/null +++ b/tests/unittest/Core/random.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include +#include + +#include +using namespace Ra::Core::Random; + +TEST_CASE( "Core/Random/RandomPointSet", "[Core][Core/Random][PointSet]" ) { + SECTION( "Fibonacci sequence" ) { + std::array fib_verif { 0_ra, + 0.61803400516510009765625_ra, + 1.2360680103302001953125_ra, + 1.8541018962860107421875_ra, + 2.472136020660400390625_ra }; + FibonacciSequence fib { 2 }; + // Our fibonacci sequence is only defined for more than 5 points + REQUIRE( fib.range() == 5 ); + for ( size_t i = 0; i < 5; ++i ) { + REQUIRE( isApprox( fib( i ), fib_verif[i] ) ); + } + } + + SECTION( "VanDerCorput sequence" ) { + std::array vdc_verif { 0_ra, 0.5_ra, 0.25_ra, 0.75_ra, 0.125_ra }; + VanDerCorputSequence vdc; + for ( size_t i = 0; i < 5; ++i ) { + REQUIRE( isApprox( vdc( i ), vdc_verif[i] ) ); + } + } + + SECTION( "Fibonacci point set" ) { + std::array, 5> fibseq_verif { + std::pair { 0_ra, 0_ra / 5_ra }, + { 0.61803400516510009765625_ra, 1_ra / 5_ra }, + { 1.2360680103302001953125_ra, 2_ra / 5_ra }, + { 1.8541018962860107421875_ra, 3_ra / 5_ra }, + { 2.472136020660400390625_ra, 4_ra / 5_ra } }; + FibonacciPointSet fibs { 5 }; + for ( size_t i = 0; i < 5; ++i ) { + auto v = fibs( i ); + REQUIRE( isApprox( v[0], fibseq_verif[i].first ) ); + REQUIRE( isApprox( v[1], fibseq_verif[i].second ) ); + } + } + + SECTION( "Hammersley point set" ) { + std::array, 5> seq_verif { + std::pair { 0_ra, 0_ra / 5_ra }, + { 1_ra / 5_ra, 0.5_ra }, + { 2_ra / 5_ra, 0.25_ra }, + { 3_ra / 5_ra, 0.75_ra }, + { 4_ra / 5_ra, 0.125_ra } }; + HammersleyPointSet seq { 5 }; + for ( size_t i = 0; i < 5; ++i ) { + auto v = seq( i ); + REQUIRE( isApprox( v[0], seq_verif[i].first ) ); + REQUIRE( isApprox( v[1], seq_verif[i].second ) ); + } + } + + // todo, verify if the sequence is always the same (it should) on any systems/run + SECTION( "MersenneTwister point set" ) { + std::array, 5> seq_verif { + std::pair { 0.59284460544586181640625_ra, + 0.844265758991241455078125_ra }, + { 0.857945621013641357421875_ra, 0.847251713275909423828125_ra }, + { 0.623563706874847412109375_ra, 0.384381711483001708984375_ra }, + { 0.2975346148014068603515625_ra, 0.056712977588176727294921875_ra }, + { 0.2726562917232513427734375_ra, 0.477665126323699951171875_ra } }; + MersenneTwisterPointSet seq { 5 }; + std::cout << std::setprecision( 32 ); + for ( size_t i = 0; i < 5; ++i ) { + auto v = seq( i ); + REQUIRE( isApprox( v[0], seq_verif[i].first ) ); + REQUIRE( isApprox( v[1], seq_verif[i].second ) ); + } + } + + SECTION( "SphericalPointSet point set (Hammersley)" ) { + std::array, 5> seq_verif { + std::pair { 1.5099580252808664226904511451721e-07_ra, 0_ra }, + { 0.3090169727802276611328125_ra, 0.951056540012359619140625_ra }, + { -0.700629293918609619140625_ra, 0.50903689861297607421875_ra }, + { -0.700629055500030517578125_ra, -0.509037196636199951171875_ra }, + { 0.204395592212677001953125_ra, -0.62906467914581298828125_ra } }; + SphericalPointSet seq { 5 }; + for ( size_t i = 0; i < 5; ++i ) { + auto v = seq( i ); + REQUIRE( isApprox( v[0], seq_verif[i].first ) ); + REQUIRE( isApprox( v[1], seq_verif[i].second ) ); + } + } +} From c91d8d49274175b15adde83c1e2d4124536e7fa7 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 2 Oct 2022 18:40:07 +0200 Subject: [PATCH 004/239] [core] improve type utilities : add type traits utility and fix type demangling on windows --- src/Core/Utils/TypesUtils.hpp | 26 ++++++++++++++ tests/unittest/CMakeLists.txt | 2 +- .../Core/{demangle.cpp => typeutils.cpp} | 36 +++++++++++-------- 3 files changed, 49 insertions(+), 15 deletions(-) rename tests/unittest/Core/{demangle.cpp => typeutils.cpp} (61%) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index ac3d962d9e3..fc20cbe6cae 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -25,6 +25,32 @@ const char* demangleType() noexcept; template const char* demangleType( const T& ) noexcept; +// Check if a type is a container with access to its element type and number +// adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable +namespace detail { + +using std::begin; +using std::end; + +template +auto is_container_impl( int ) + -> decltype( begin( std::declval() ) != + end( std::declval() ), // begin/end and operator != + void(), // Handle evil operator , + std::declval().empty(), + std::declval().size(), + ++std::declval() ) )&>(), // operator ++ + void( *begin( std::declval() ) ), // operator* + std::true_type {} ); + +template +std::false_type is_container_impl( ... ); + +} // namespace detail + +template +using is_container = decltype( detail::is_container_impl( 0 ) ); + // TypeList taken and adapted from // https://github.com/AcademySoftwareFoundation/openvdb/blob/master/openvdb/openvdb/TypeList.h // Only took small part of TypeList utilities diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index 496468e1f74..24b0e020cf8 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -12,7 +12,7 @@ set(test_src Core/camera.cpp Core/color.cpp Core/containers.cpp - Core/demangle.cpp + Core/typeutils.cpp Core/distance.cpp Core/enumconverter.cpp Core/geometryData.cpp diff --git a/tests/unittest/Core/demangle.cpp b/tests/unittest/Core/typeutils.cpp similarity index 61% rename from tests/unittest/Core/demangle.cpp rename to tests/unittest/Core/typeutils.cpp index da3cc5c2073..f68a0922bce 100644 --- a/tests/unittest/Core/demangle.cpp +++ b/tests/unittest/Core/typeutils.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include @@ -9,19 +12,15 @@ namespace TypeTests { struct TypeName_struct {}; } // namespace TypeTests -TEST_CASE( "Core/Utils/TypesUtils", "[Core][Core/Utils][TypesUtils]" ) { +TEST_CASE( "Core/Utils/TypesUtils", "[Core][Utils][TypesUtils]" ) { SECTION( "Demangle from typename" ) { using Ra::Core::Utils::demangleType; REQUIRE( std::string( demangleType() ) == "int" ); REQUIRE( std::string( demangleType() ) == "float" ); REQUIRE( std::string( demangleType() ) == "unsigned int" ); - // TODO, verify type demangling on windows -#ifndef _WIN32 REQUIRE( std::string( demangleType() ) == "unsigned long" ); -#else - REQUIRE( std::string( demangleType() ) == "unsigned __int64" ); -#endif + auto demangledName = std::string( demangleType>() ); REQUIRE( demangledName == "std::vector>" ); @@ -40,18 +39,27 @@ TEST_CASE( "Core/Utils/TypesUtils", "[Core][Core/Utils][TypesUtils]" ) { REQUIRE( std::string( demangleType( i ) ) == "int" ); REQUIRE( std::string( demangleType( f ) ) == "float" ); REQUIRE( std::string( demangleType( u ) ) == "unsigned int" ); - // TODO, verify type demangling on windows -#ifndef _WIN32 REQUIRE( std::string( demangleType( s ) ) == "unsigned long" ); -#else - REQUIRE( std::string( demangleType( s ) ) == "unsigned __int64" ); -#endif + +#ifndef _WIN32 + // this segfault on windows due to out_of_bound exception. why ??? std::vector v; auto demangledName = std::string( demangleType( v ) ); REQUIRE( demangledName == "std::vector>" ); - +#endif TypeTests::TypeName_struct tns; - demangledName = std::string( demangleType( tns ) ); - REQUIRE( demangledName == "TypeTests::TypeName_struct" ); + auto demangledNameFromStruct = std::string( demangleType( tns ) ); + REQUIRE( demangledNameFromStruct == "TypeTests::TypeName_struct" ); + } + + SECTION( "Type traits" ) { + using namespace Ra::Core::Utils; + REQUIRE( is_container::value == false ); + REQUIRE( is_container::value == false ); + REQUIRE( is_container::value == false ); + REQUIRE( is_container>::value == true ); + REQUIRE( is_container>::value == true ); + REQUIRE( is_container>::value == true ); + REQUIRE( is_container>::value == true ); } } From aee2bfdd2824b6bc78e59fa75057ab8098e84db3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 8 Sep 2022 00:13:22 +0200 Subject: [PATCH 005/239] [cmake] Allows configuring INTERFACE library (e.g. header only) --- cmake/RadiumSetupFunctions.cmake | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmake/RadiumSetupFunctions.cmake b/cmake/RadiumSetupFunctions.cmake index 9c4c861422e..bf5362d6b7a 100644 --- a/cmake/RadiumSetupFunctions.cmake +++ b/cmake/RadiumSetupFunctions.cmake @@ -966,18 +966,27 @@ function(configure_radium_library) # add the cmake command to install target add_custom_install_target(${ARGS_TARGET}) + get_target_property(TargetType ${ARGS_TARGET} TYPE) + if(TargetType STREQUAL INTERFACE_LIBRARY) + set(PropertyQualifier INTERFACE) + else() + set(PropertyQualifier PUBLIC) + target_compile_definitions(${ARGS_TARGET} PRIVATE ${ARGS_TARGET}_EXPORTS) + endif() + if(CMAKE_BUILD_TYPE STREQUAL "Debug") - target_compile_definitions(${ARGS_TARGET} PUBLIC _DEBUG) + target_compile_definitions(${ARGS_TARGET} ${PropertyQualifier} _DEBUG) if(MSVC OR MSVC_IDE) install(FILES $ DESTINATION bin) endif() endif() - target_compile_features(${ARGS_TARGET} PUBLIC cxx_std_17) + + target_compile_features(${ARGS_TARGET} ${PropertyQualifier} cxx_std_17) if(OPENMP_FOUND) - target_link_libraries(${ARGS_TARGET} PUBLIC OpenMP::OpenMP_CXX) + target_link_libraries(${ARGS_TARGET} ${PropertyQualifier} OpenMP::OpenMP_CXX) endif(OPENMP_FOUND) - target_compile_definitions(${ARGS_TARGET} PRIVATE ${ARGS_TARGET}_EXPORTS) - target_include_directories(${ARGS_TARGET} PUBLIC $) + target_include_directories(${ARGS_TARGET} ${PropertyQualifier} $) + add_library(${ARGS_NAMESPACE}::${ARGS_TARGET} ALIAS ${ARGS_TARGET}) install( From 5a9fd16e88b41500efa001ca7e2d5713b3bda18a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 8 Sep 2022 00:14:38 +0200 Subject: [PATCH 006/239] [scripts] generation of filelist is allows filelist per subdirectory --- scripts/generateFilelistForModule.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/generateFilelistForModule.sh b/scripts/generateFilelistForModule.sh index 9448c419fac..2e3caa7336f 100755 --- a/scripts/generateFilelistForModule.sh +++ b/scripts/generateFilelistForModule.sh @@ -7,12 +7,16 @@ fi BASE=$1 LOWBASE=$(echo "$BASE" | tr '[:upper:]' '[:lower:]') +LOWBASE=$(echo "$LOWBASE" | tr '/' '_') OUTPUT="../src/${BASE}/filelist.cmake" echo "generate [${BASE}] filelist" echo "-- input files from [../src/${BASE}]" echo "-- output vars prefix [${LOWBASE}]" echo "-- output file is [${OUTPUT}]" +DELIM='/' +SLASHES=$( awk -F"$DELIM" '{print NF-1}' <<<"${BASE}" ) +(( SLASHES = 4 + SLASHES )) rm -f "${OUTPUT}" @@ -32,7 +36,7 @@ function genList(){ if [ ! -z "$L" ] then echo "set(${LOWBASE}_${suffix}" >> "${OUTPUT}" - echo "${L}" | cut -f 4- -d/ | grep -v pch.hpp | sort | xargs -n1 echo " " >> "${OUTPUT}" + echo "${L}" | cut -f $SLASHES- -d/ | grep -v pch.hpp | sort | xargs -n1 echo " " >> "${OUTPUT}" echo ")" >> "${OUTPUT}" echo "" >> "${OUTPUT}" fi From 8ee90d8452730bf362624367f90eb82db0a5b079 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 8 Sep 2022 00:15:38 +0200 Subject: [PATCH 007/239] [dataflow-core] first commit of dataflow component --- CMakeLists.txt | 2 + src/CMakeLists.txt | 2 +- src/Dataflow/CMakeLists.txt | 39 + src/Dataflow/Config.cmake.in | 44 ++ src/Dataflow/Core/CMakeLists.txt | 44 ++ src/Dataflow/Core/Config.cmake.in | 30 + src/Dataflow/Core/DataflowGraph.cpp | 725 ++++++++++++++++++ src/Dataflow/Core/DataflowGraph.hpp | 183 +++++ src/Dataflow/Core/DataflowGraph.inl | 35 + src/Dataflow/Core/EditableParameter.hpp | 34 + src/Dataflow/Core/EditableParameter.inl | 26 + src/Dataflow/Core/Enumerator.hpp | 34 + src/Dataflow/Core/Enumerator.inl | 62 ++ src/Dataflow/Core/Node.cpp | 109 +++ src/Dataflow/Core/Node.hpp | 211 +++++ src/Dataflow/Core/Node.inl | 183 +++++ src/Dataflow/Core/NodeFactory.cpp | 131 ++++ src/Dataflow/Core/NodeFactory.hpp | 225 ++++++ src/Dataflow/Core/NodeFactory.inl | 81 ++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 9 + .../Core/Nodes/Filters/FilterNode.hpp | 66 ++ .../Core/Nodes/Filters/FilterNode.inl | 80 ++ src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 43 ++ src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 59 ++ .../Core/Nodes/Sources/CoreDataSources.hpp | 87 +++ .../Core/Nodes/Sources/CoreDataSources.inl | 99 +++ .../Nodes/Sources/SingleDataSourceNode.hpp | 85 ++ .../Nodes/Sources/SingleDataSourceNode.inl | 95 +++ src/Dataflow/Core/Port.hpp | 170 ++++ src/Dataflow/Core/Port.inl | 208 +++++ src/Dataflow/Core/filelist.cmake | 34 + src/Dataflow/Core/pch.hpp | 1 + src/Dataflow/RaDataflow.hpp | 11 + 33 files changed, 3246 insertions(+), 1 deletion(-) create mode 100644 src/Dataflow/CMakeLists.txt create mode 100644 src/Dataflow/Config.cmake.in create mode 100644 src/Dataflow/Core/CMakeLists.txt create mode 100644 src/Dataflow/Core/Config.cmake.in create mode 100644 src/Dataflow/Core/DataflowGraph.cpp create mode 100644 src/Dataflow/Core/DataflowGraph.hpp create mode 100644 src/Dataflow/Core/DataflowGraph.inl create mode 100644 src/Dataflow/Core/EditableParameter.hpp create mode 100644 src/Dataflow/Core/EditableParameter.inl create mode 100644 src/Dataflow/Core/Enumerator.hpp create mode 100644 src/Dataflow/Core/Enumerator.inl create mode 100644 src/Dataflow/Core/Node.cpp create mode 100644 src/Dataflow/Core/Node.hpp create mode 100644 src/Dataflow/Core/Node.inl create mode 100644 src/Dataflow/Core/NodeFactory.cpp create mode 100644 src/Dataflow/Core/NodeFactory.hpp create mode 100644 src/Dataflow/Core/NodeFactory.inl create mode 100644 src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp create mode 100644 src/Dataflow/Core/Nodes/Filters/FilterNode.hpp create mode 100644 src/Dataflow/Core/Nodes/Filters/FilterNode.inl create mode 100644 src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp create mode 100644 src/Dataflow/Core/Nodes/Sinks/SinkNode.inl create mode 100644 src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp create mode 100644 src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl create mode 100644 src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp create mode 100644 src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl create mode 100644 src/Dataflow/Core/Port.hpp create mode 100644 src/Dataflow/Core/Port.inl create mode 100644 src/Dataflow/Core/filelist.cmake create mode 100644 src/Dataflow/Core/pch.hpp create mode 100644 src/Dataflow/RaDataflow.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d80b1bddacb..2f544ce7bf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,7 @@ option(RADIUM_GENERATE_LIB_ENGINE "Include Radium::Engine in CMake project." ON) option(RADIUM_GENERATE_LIB_GUI "Include Radium::Gui in CMake project." ON) option(RADIUM_GENERATE_LIB_PLUGINBASE "Include Radium::PluginBase in CMake project." ON) option(RADIUM_GENERATE_LIB_HEADLESS "Include Radium::Headless in CMake project." ON) +option(RADIUM_GENERATE_LIB_DATAFLOW "Include Radium::Dataflow* in CMake project." ON) option( RADIUM_UPDATE_VERSION "Update version file each time the project is compiled (update compilation time in version.cpp)." @@ -290,6 +291,7 @@ message_setting("RADIUM_GENERATE_LIB_CORE") message_setting("RADIUM_GENERATE_LIB_ENGINE") message_setting("RADIUM_GENERATE_LIB_GUI") message_setting("RADIUM_GENERATE_LIB_HEADLESS") +message_setting("RADIUM_GENERATE_LIB_DATAFLOW") message_setting("RADIUM_GENERATE_LIB_IO") message_setting("RADIUM_GENERATE_LIB_PLUGINBASE") string(REPLACE ";" " " COMPONENTS_LIST "${RADIUM_COMPONENTS}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ff89b416a7..50b71e8094a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -31,7 +31,7 @@ set(CONFIG_PACKAGE_LOCATION lib/cmake/Radium) # ----------------------------------------------------------------------------------- # Include sources and declare components -foreach(lib Core Engine IO PluginBase Gui Headless) +foreach(lib Core Engine IO PluginBase Gui Headless Dataflow) string(TOUPPER ${lib} upcase_lib) if(RADIUM_GENERATE_LIB_${upcase_lib}) add_subdirectory(${lib}) diff --git a/src/Dataflow/CMakeLists.txt b/src/Dataflow/CMakeLists.txt new file mode 100644 index 00000000000..508b3ec96f6 --- /dev/null +++ b/src/Dataflow/CMakeLists.txt @@ -0,0 +1,39 @@ +set(ra_dataflow_target Dataflow) +list(APPEND CMAKE_MESSAGE_INDENT "[${ra_dataflow_target}] ") + +project(${ra_dataflow_target} LANGUAGES CXX VERSION ${Radium_VERSION}) + +set(dataflow_headers RaDataflow.hpp) +add_library(${ra_dataflow_target} INTERFACE) + +if(RADIUM_GENERATE_LIB_CORE) + add_subdirectory(Core) + add_dependencies(${ra_dataflow_target} DataflowCore) + target_link_libraries(${ra_dataflow_target} INTERFACE DataflowCore) +endif() + +if(OFF) + if(RADIUM_GENERATE_LIB_ENGINE) + add_subdirectory(Rendering) + add_dependencies(${ra_dataflow_target} DataflowRendering) + target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) + endif() + + if(RADIUM_GENERATE_LIB_GUI) + add_subdirectory(QtGui) + add_dependencies(${ra_dataflow_target} DataflowQtGui) + target_link_libraries(${ra_dataflow_target} INTERFACE DataflowQtGui) + endif() +endif() + +message(STATUS "Configuring library ${ra_dataflow_target} with standard settings") +target_include_directories( + ${ra_dataflow_target} INTERFACE $ + $ +) +configure_radium_library( + TARGET ${ra_dataflow_target} COMPONENT + PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in FILES "${dataflow_headers}" +) +set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflow_target} PARENT_SCOPE) +set(RADIUM_MISSING_COMPONENTS ${RADIUM_MISSING_COMPONENTS} PARENT_SCOPE) diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in new file mode 100644 index 00000000000..da59da4882e --- /dev/null +++ b/src/Dataflow/Config.cmake.in @@ -0,0 +1,44 @@ +# -------------- Configuration of the Radium Dataflow targets and definitions ----------------------- +# Setup Dataflow and check for dependencies +if (Dataflow_FOUND AND NOT TARGET Dataflow) + set(Configure_Dataflow ON) + # verify dependencies + if(NOT DataflowCore_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake") + set(DataflowCore_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowCore not found") + set(Configure_Dataflow OFF) + endif() + endif() + + if (OFF) + if(@RADIUM_GENERATE_LIB_ENGINE@ AND NOT Engine_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake") + set(Engine_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency Engine not found") + set(Configure_Dataflow OFF) + endif() + endif() + if(@RADIUM_GENERATE_LIB_GUI@ AND NOT Gui_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake") + set(Engine_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency Gui not found") + set(Configure_Dataflow OFF) + endif() + endif() + endif() +endif() + +# configure Dataflow component +if(Configure_Dataflow) + include("${CMAKE_CURRENT_LIST_DIR}/DataflowTargets.cmake") +endif() diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt new file mode 100644 index 00000000000..be728741155 --- /dev/null +++ b/src/Dataflow/Core/CMakeLists.txt @@ -0,0 +1,44 @@ +set(ra_dataflowcore_target DataflowCore) +list(APPEND CMAKE_MESSAGE_INDENT "[${ra_dataflowcore_target}] ") + +project(${ra_dataflowcore_target} LANGUAGES CXX VERSION ${Radium_VERSION}) + +include(filelist.cmake) + +add_library( + ${ra_dataflowcore_target} SHARED ${dataflow_core_sources} ${dataflow_core_headers} + ${dataflow_core_inlines} +) + +# LocalDependencies from parent scope +populate_local_dependencies(NAME "stduuid_DIR") +find_package(stduuid REQUIRED NO_DEFAULT_PATH) + +# This one should be extracted directly from external project properties. +target_compile_definitions(${ra_dataflowcore_target} PUBLIC THIS_A_REMINDER) + +target_compile_options(${ra_dataflowcore_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) + +add_dependencies(${ra_dataflowcore_target} Core) +target_link_libraries(${ra_dataflowcore_target} PUBLIC Core stduuid) + +message(STATUS "Configuring library ${ra_dataflowcore_target} with standard settings") +configure_radium_target(${ra_dataflowcore_target}) +# configure the library only. The package is a sub-package and should be configured independently +configure_radium_library( + TARGET ${ra_dataflowcore_target} COMPONENT TARGET_DIR "Dataflow/Core" + FILES "${dataflow_core_headers};${dataflow_core_inlines}" +) +# Generate cmake package +configure_radium_package( + NAME ${ra_dataflowcore_target} PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in + PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowcore_target}" NAME_PREFIX "Radium" +) + +set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowcore_target} PARENT_SCOPE) + +if(RADIUM_ENABLE_PCH) + target_precompile_headers(${ra_dataflowcore_target} PRIVATE pch.hpp) +endif() + +list(REMOVE_AT CMAKE_MESSAGE_INDENT -1) diff --git a/src/Dataflow/Core/Config.cmake.in b/src/Dataflow/Core/Config.cmake.in new file mode 100644 index 00000000000..9d8b0df9996 --- /dev/null +++ b/src/Dataflow/Core/Config.cmake.in @@ -0,0 +1,30 @@ +# -------------- Configuration of the Radium DataflowCore targets and definitions ----------------------- +# Setup Engine and check for dependencies + +if (DataflowCore_FOUND AND NOT TARGET DataflowCore) + set(Configure_DataflowCore ON) + # verify dependencies + if(NOT Core_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake") + set(Core_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Engine: dependency Core not found") + set(Configure_Engine OFF) + endif() + endif() +endif() + +if(Configure_DataflowCore) + # Theses paths reflect the paths founds in RadiumEngine/external/Dataflow/package + set(_BaseExternalDir_ "${CMAKE_CURRENT_LIST_DIR}/../../../../") + if("@stduuid_DIR@" STREQUAL "") + set(stduuid_DIR "${_BaseExternalDir_}/@stduuid_sub_DIR@") + else() + set(stduuid_DIR "@stduuid_DIR@") + endif() + + find_dependency(stduuid REQUIRED NO_DEFAULT_PATH) + include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/DataflowCoreTargets.cmake") +endif() diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp new file mode 100644 index 00000000000..f65a7a5fc1d --- /dev/null +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -0,0 +1,725 @@ +#include +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +DataflowGraph::DataflowGraph( const std::string& name ) : DataflowGraph( name, getTypename() ) {} + +DataflowGraph::DataflowGraph( const std::string& instanceName, const std::string& typeName ) : + Node( instanceName, typeName ) { + // This will alllow to edit subgraph in an independant editor + addEditableParameter( new EditableParameter( instanceName, *this ) ); + + // A graph always use the builtin nodes factory + auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); + addFactory( defaultFactory->getName(), defaultFactory ); +} + +void DataflowGraph::init() { + Node::init(); + auto compileOK = compile(); + if ( compileOK ) { +#ifdef GRAPH_CALL_TRACE + int i = 0; + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&i]( const auto& level ) { + std::cout << "- \e[1mLevel " << i++ << "\e[0m" << std::endl; +#else + + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { +#endif + std::for_each( level.begin(), level.end(), []( auto node ) { + if ( !node->m_initialized ) { + node->init(); + node->m_initialized = true; + } + } ); + } ); + } + m_recompile = !compileOK; +} + +void DataflowGraph::execute() { + if ( m_ready ) { +#ifdef GRAPH_CALL_TRACE + std::cout << std::endl + << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": execute." + << std::endl; + + int i = 0; + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&i]( const auto& level ) { + std::cout << "- \e[1mLevel " << i++ << "\e[0m" << std::endl; +#else + + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { +#endif + std::for_each( level.begin(), level.end(), []( auto node ) { node->execute(); } ); + } ); + } +} + +void DataflowGraph::destroy() { + Node::destroy(); + int i = 0; + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&i]( const auto& level ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "- \e[1mLevel " << i << "\e[0m" << std::endl; +#endif + i++; + std::for_each( level.begin(), level.end(), []( auto node ) { node->destroy(); } ); + } ); +} + +void DataflowGraph::saveToJson( const std::string& jsonFilePath ) { + if ( !jsonFilePath.empty() ) { + nlohmann::json data; + toJson( data ); + std::ofstream file( jsonFilePath ); + file << std::setw( 4 ) << data << std::endl; + } +} + +void DataflowGraph::toJsonInternal( nlohmann::json& data ) const { + nlohmann::json factories = nlohmann::json::array(); + nlohmann::json nodes = nlohmann::json::array(); + nlohmann::json connections = nlohmann::json::array(); + nlohmann::json model; + nlohmann::json graph; + + if ( m_factories ) { + for ( const auto& [name, factory] : *m_factories ) { + // do not save the standard factory, it will always be there + if ( name != NodeFactoriesManager::dataFlowBuiltInsFactoryName ) { + factories.push_back( name ); + } + } + graph["factories"] = factories; + } + + for ( const auto& n : m_nodes ) { + nlohmann::json nodeData; + n->toJson( nodeData ); + nodes.push_back( nodeData ); + int numPort = 0; + for ( const auto& input : n->getInputs() ) { + if ( input->isLinked() ) { + nlohmann::json link = nlohmann::json::object(); + link["in_id"] = n->getUuid(); + link["in_index"] = numPort; + auto portOut = input->getLink(); + auto nodeOut = portOut->getNode(); + int outPortIndex = 0; + for ( const auto& p : nodeOut->getOutputs() ) { + if ( p.get() == portOut ) { break; } + outPortIndex++; + } + link["out_id"] = nodeOut->getUuid(); + link["out_index"] = outPortIndex; + connections.push_back( link ); + } + numPort++; + } + } + + // write the common content of the Node to the json data + graph["nodes"] = nodes; + graph["connections"] = connections; + // Fill the specific concrete node informations + data.emplace( "graph", graph ); +} + +bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": loadFromJson: " << jsonFilePath << std::endl; + std::ifstream file( jsonFilePath ); + nlohmann::json j; + file >> j; + fromJson( j ); + return true; +} + +void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { + + if ( data.contains( "graph" ) ) { + // indicate that the graph must be recompiled after loading + m_recompile = true; + // load the graph + m_factories.reset( new NodeFactorySet ); + if ( data["graph"].contains( "factories" ) ) { + auto factories = data["graph"]["factories"]; + for ( const auto& factoryName : factories ) { + // Do not add factories already registered for the graph. + if ( m_factories->hasFactory( factoryName ) ) { + std::cerr << "PLOPOPOPOO !!!!\n"; + continue; + } + auto factory = NodeFactoriesManager::getFactory( factoryName ); + if ( factory ) { m_factories->addFactory( factoryName, factory ); } + else { + std::cerr << "DataflowGraph::loadFromJson : Unable to find a factory with name " + << factoryName << std::endl; + return; + } + } + } + if ( !m_factories ) { + std::cerr << "DataflowGraph::loadFromJson : no node factories available !"; + return; + } + std::unordered_map nodeById; + + auto nodes = data["graph"]["nodes"]; + for ( auto& n : nodes ) { + std::string name = n["model"]["name"]; + std::string id = n["id"]; + + auto newNode = m_factories->createNode( name, n, this ); + if ( newNode ) { nodeById.emplace( id, newNode ); } + else { + std::cerr << "Unable to create the node " << name << std::endl; + } + } + + auto links = data["graph"]["connections"]; + for ( auto& l : links ) { + Node* nodeFrom { nullptr }; + std::string fromOutput { "" }; + Node* nodeTo { nullptr }; + std::string toInput { "" }; + + if ( nodeById.find( l["out_id"] ) != nodeById.end() ) { + nodeFrom = nodeById[l["out_id"]]; + int fromIndex = l["out_index"]; + + if ( fromIndex >= 0 && fromIndex < int( nodeFrom->getOutputs().size() ) ) { + fromOutput = nodeFrom->getOutputs()[fromIndex]->getName(); + } + else { + std::cerr << "Error when reading JSON file \"" + << "\": Output index " << fromIndex << " for node \"" + << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() + << ")\" must be between 0 and " << nodeFrom->getOutputs().size() - 1 + << ". Link not added." << std::endl; + } + } + else { + std::cerr << "Error when reading JSON file \"" + << "\": Could not find a node associated with id " << l["out_id"] + << ". Link not added." << std::endl; + } + + if ( nodeById.find( l["in_id"] ) != nodeById.end() ) { + nodeTo = nodeById[l["in_id"]]; + int toIndex = l["in_index"]; + + if ( toIndex >= 0 && toIndex < int( nodeTo->getInputs().size() ) ) { + toInput = nodeTo->getInputs()[toIndex]->getName(); + } + else { + std::cerr << "Error when reading JSON file \"" + << "\": Input index " << toIndex << " for node \"" + << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() + << ")\" must be between 0 and " << nodeTo->getInputs().size() - 1 + << ". Link not added." << std::endl; + } + } + else { + std::cerr << "Error when reading JSON file \"" + << "\": Could not find a node associated with id " << l["in_id"] + << ". Link not added." << std::endl; + } + + if ( nodeFrom && ( fromOutput != "" ) && nodeTo && ( toInput != "" ) ) { + addLink( nodeFrom, fromOutput, nodeTo, toInput ); + } + else { + std::cerr + << "Error when reading JSON file \"" + << "\": Could not add a link (missing or wrong information, please refer to " + "the previous error messages). Link not added." + << std::endl; + } + } + } +} + +bool DataflowGraph::addNode( Node* newNode ) { + std::map m_mapInputs; +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": trying to add node \"" + << newNode->getInstanceName() << "\"..." << std::endl; +#endif + // Check if the new node already exists (= same name) + if ( findNode( newNode->getInstanceName() ) == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": success adding node \"" << newNode->getInstanceName() << "\"!" + << std::endl; +#endif + if ( newNode->getInputs().size() == 0 ) { // Check if it is a source node + for ( auto& p : newNode->getOutputs() ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[33m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": reflecting OUTPUT port \"" << p->getName() << "\" from \"" + << newNode->getInstanceName() << "\"!" << std::endl; +#endif + auto portInterface = + p->reflect( this, newNode->getInstanceName() + '_' + p->getName() ); + portInterface->mustBeLinked(); + newNode->addInterface( portInterface ); + addInput( portInterface ); + } + } + if ( newNode->getOutputs().size() == 0 ) { // Check if it is a sink node + for ( auto& p : newNode->getInputs() ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[34m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": reflecting INPUT port \"" << p->getName() << "\" from \"" + << newNode->getInstanceName() << "\"!" << std::endl; +#endif + auto portInterface = + p->reflect( this, newNode->getInstanceName() + '_' + p->getName() ); + newNode->addInterface( portInterface ); + addOutput( portInterface ); + } + } + m_nodes.emplace_back( newNode ); + return true; + } + else { +#ifdef GRAPH_CALL_TRACE + std::cerr << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": could not add node \"" << newNode->getInstanceName() + << "\" (node already exists)." << std::endl; +#endif + return false; + } +} + +bool DataflowGraph::removeNode( Node* node ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": trying to remove node \"" << node->getInstanceName() << "\"..." << std::endl; +#endif + // Check if the new node already exists (= same name) + int index = -1; + if ( ( index = findNode( node->getInstanceName() ) ) == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": could not remove node \"" << node->getInstanceName() + << "\" (node does not exist)." << std::endl; +#endif + return false; + } + else { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": success removing node \"" << node->getInstanceName() << "\"!" + << std::endl; +#endif + if ( node->getInputs().size() == 0 ) { // Check if it is a source node + for ( auto& port : node->getInterface() ) { // Erase input ports of the graph associated + // to the interface ports of the node + for ( auto itG = m_inputs.begin(); itG != m_inputs.end(); ++itG ) { + if ( port->getName() == + ( *itG )->getName() ) { // Check if these ports are the same + m_inputs.erase( itG ); + } + break; + } + } + } + if ( node->getOutputs().size() == 0 ) { // Check if it is a sink node + for ( auto& port : node->getInterface() ) { // Erase input ports of the graph associated + // to the interface ports of the node + for ( auto itG = m_outputs.begin(); itG != m_outputs.end(); ++itG ) { + if ( port->getName() == + ( *itG )->getName() ) { // Check if these ports are the same + m_outputs.erase( itG ); + break; + } + } + } + } + m_nodes.erase( m_nodes.begin() + index ); + return true; + } +} + +bool DataflowGraph::addLink( Node* nodeFrom, + const std::string& nodeFromOutputName, + Node* nodeTo, + const std::string& nodeToInputName ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": ADD LINK : try to connect output \"" + nodeFromOutputName + "\" of node \"" + + nodeFrom->getInstanceName() + "\" to input \"" + nodeToInputName + + "\" of node \"" + nodeTo->getInstanceName() + "\"." + << std::endl; +#endif + // Check node "from" existence in the graph + if ( findNode( nodeFrom->getInstanceName() ) == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : node \"from\" \"" + nodeFrom->getInstanceName() + + "\" does not exist." + << std::endl; +#endif + return false; + } + + // Check node "to" existence in the graph + if ( findNode( nodeTo->getInstanceName() ) == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : node \"to\" \"" + nodeTo->getInstanceName() + "\" does not exist." + << std::endl; +#endif + return false; + } + + // Check if node "from"'s output exists + int foundFrom = -1; + int index = 0; + for ( auto& output : nodeFrom->getOutputs() ) { + if ( output->getName() == nodeFromOutputName ) { + foundFrom = index; + break; + } + index++; + } + if ( foundFrom == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : output \"" + nodeFromOutputName + "\" for node \"from\" \"" + + nodeFrom->getInstanceName() + "\" does not exist." + << std::endl; +#endif + return false; + } + + // Check if node "to"'s input exists + int foundTo = -1; + index = 0; + for ( auto& input : nodeTo->getInputs() ) { + if ( input->getName() == nodeToInputName ) { + foundTo = index; + break; + } + index++; + } + if ( foundTo == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : input \"" + nodeToInputName + "\" for target node \"" + + nodeTo->getInstanceName() + "\" does not exist." + << std::endl; +#endif + return false; + } + + // Compare types + if ( nodeTo->getInputs()[foundTo]->getType() != nodeFrom->getOutputs()[foundFrom]->getType() ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + + nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + + "\" for node \"" + nodeFrom->getInstanceName() + "\" : type mismatch." + << std::endl; +#endif + return false; + } + + // Check if input is connected + if ( nodeTo->getInputs()[foundTo]->isLinked() ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + + nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + + "\" for node \"" + nodeFrom->getInstanceName() + + "\" : input already connected." + << std::endl; +#endif + return false; + } + + // Try to connect ports + if ( !nodeTo->getInputs()[foundTo]->connect( nodeFrom->getOutputs()[foundFrom].get() ) ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + + nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + + "\" for node \"" + nodeFrom->getInstanceName() + "\"." + << std::endl; +#endif + return false; + } +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": ADD LINK : success connecting output \"" + nodeFromOutputName + + "\" of node \"" + nodeFrom->getInstanceName() + "\" to input \"" + + nodeToInputName + "\" of node \"" + nodeTo->getInstanceName() + "\"." + << std::endl; +#endif + return true; +} + +bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { + // Check node's existence in the graph + if ( findNode( node->getInstanceName() ) == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "REMOVE LINK : node \"" + node->getInstanceName() + "\" does not exist." + << std::endl; +#endif + return false; + } + + // Check if node's input exists + int found = -1; + int index = 0; + for ( auto& input : node->getInputs() ) { + if ( input->getName() == nodeInputName ) { + found = index; + break; + } + index++; + } + if ( found == -1 ) { +#ifdef GRAPH_CALL_TRACE + std::cerr << "REMOVE LINK : input \"" + nodeInputName + "\" for target node \"" + + node->getInstanceName() + "\" does not exist." + << std::endl; +#endif + return false; + } + + node->getInputs()[found]->disconnect(); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": REMOVE LINK : success disconnecting input \"" + nodeInputName + + "\" of node \"" + node->getInstanceName() + "\"." + << std::endl; +#endif + return true; +} + +int DataflowGraph::findNode( const std::string& name ) { + for ( size_t i = 0; i < m_nodes.size(); i++ ) { + if ( m_nodes[i]->getInstanceName() == name ) { return i; } + } + + return -1; +} + +bool DataflowGraph::compile() { +#ifdef GRAPH_CALL_TRACE + std::cout << std::endl + << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": begin compilation." + << std::endl; +#endif + // Find useful nodes (directly or indirectly connected to a Sink) + std::unordered_map>> infoNodes; + for ( auto const& n : m_nodes ) { + // Find all sinks + if ( n->getOutputs().size() == 0 ) { + // Add the sink in the useful nodes set + infoNodes.emplace( n.get(), std::pair>( 0, {} ) ); + // recursively add the predecessors of the sink + backtrackGraph( n.get(), infoNodes ); + } + } +#ifdef GRAPH_CALL_TRACE + std::cout << std::endl + << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": useful nodes found." + << std::endl; +#endif + // Compute the level (rank of execution) of useful nodes + int maxLevel = 0; + for ( auto& infNode : infoNodes ) { + auto n = infNode.first; + // n->setResourcesDir( m_resourceDir ); // --> This must be configured per node, e.g. by the + // factory Compute the nodes' level starting from sources + if ( n->getInputs().empty() ) { + // Tag successors + for ( auto const successor : infNode.second.second ) { + infoNodes[successor].first = + std::max( infoNodes[successor].first, infoNodes[n].first + 1 ); + maxLevel = std::max( maxLevel, + std::max( infoNodes[successor].first, + goThroughGraph( successor, infoNodes ) ) ); + } + } + } +#ifdef GRAPH_CALL_TRACE + std::cout << std::endl + << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": nodes level computed." + << std::endl; +#endif + m_nodesByLevel.clear(); + m_nodesByLevel.resize( infoNodes.size() != 0 ? maxLevel + 1 : 0 ); + for ( auto& infNode : infoNodes ) { + m_nodesByLevel[infNode.second.first].push_back( infNode.first ); + } + + // For each level + for ( auto& lvl : m_nodesByLevel ) { + // For each node + for ( size_t j = 0; j < lvl.size(); j++ ) { + if ( !lvl[j]->compile() ) { return m_ready = false; } + // For each input + for ( size_t k = 0; k < lvl[j]->getInputs().size(); k++ ) { + if ( lvl[j]->getInputs()[k]->isLinkMandatory() && + !lvl[j]->getInputs()[k]->isLinked() ) { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": compilation failed." << std::endl; +#endif + return m_ready = false; + } + } + } + } +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": end compilation." + << std::endl + << std::endl; +#endif + return m_ready = postCompilationOperation(); +} + +void DataflowGraph::clearNodes() { + + for ( size_t i = 0; i < m_nodesByLevel.size(); i++ ) { + m_nodesByLevel[i].clear(); + m_nodesByLevel[i].shrink_to_fit(); + } + m_nodesByLevel.clear(); + m_nodesByLevel.shrink_to_fit(); + +#if 0 + --> specific to rendergraph ??? + // Remove only non permanent nodes + // disconnect sink + getDisplayNode()->disconnectInputs(); + // remove node + m_nodes.erase( m_nodes.begin() + 4, m_nodes.end() ); +#endif + m_nodes.erase( std::remove_if( m_nodes.begin(), + m_nodes.end(), + []( const auto& n ) { return n->isDeletable(); } ), + m_nodes.end() ); + m_nodes.shrink_to_fit(); +} + +void DataflowGraph::backtrackGraph( + Node* current, + std::unordered_map>>& infoNodes ) { + for ( auto& input : current->getInputs() ) { + if ( input->getLink() ) { + Node* previous = input->getLink()->getNode(); + if ( previous ) { + auto previousInInfoNodes = infoNodes.find( previous ); + if ( previousInInfoNodes != infoNodes.end() ) { + // If the previous node is already in the map, + // find if the current node is already a successor node + auto& previousSuccessors = previousInInfoNodes->second.second; + bool foundCurrent = std::any_of( previousSuccessors.begin(), + previousSuccessors.end(), + [current]( auto c ) { return c == current; } ); + if ( !foundCurrent ) { + // If the current node is not a successor node, add it to the list + previousSuccessors.push_back( current ); + } + } + else { + // Add node to info nodes + std::vector successors; + successors.push_back( current ); + infoNodes.emplace( + previous, + std::pair>( 0, std::move( successors ) ) ); + backtrackGraph( previous, infoNodes ); + } + } + } + } +} + +int DataflowGraph::goThroughGraph( + Node* current, + std::unordered_map>>& infoNodes ) { + int maxLevel = 0; + if ( infoNodes.find( current ) != infoNodes.end() ) { + for ( auto const& successor : infoNodes[current].second ) { + infoNodes[successor].first = + std::max( infoNodes[successor].first, infoNodes[current].first + 1 ); + maxLevel = + std::max( infoNodes[successor].first, goThroughGraph( successor, infoNodes ) ); + } + } + return maxLevel; +} + +std::shared_ptr DataflowGraph::getDataSetter( std::string portName ) { + for ( auto& portIn : m_inputs ) { + if ( portIn->getName() == portName ) { + portIn->disconnect(); + auto portOut = portIn->reflect( this, portName ); + portOut->connect( portIn.get() ); + return std::shared_ptr( portOut ); + } + } +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[36m\e[1mDataflowGraph::graphGetInput \e[0m \"" + << "Error, can't generate the interface node for graph input " << portName + << std::endl; +#endif + return nullptr; +} + +std::vector DataflowGraph::getAllDataSetters() { + std::vector r; + r.reserve( m_inputs.size() ); + for ( auto& portIn : m_inputs ) { + portIn->disconnect(); + auto portOut = portIn->reflect( this, portIn->getName() ); + portOut->connect( portIn.get() ); + r.emplace_back( + std::shared_ptr( portOut ), portOut->getName(), portOut->getTypeName() ); + } + return r; +} + +/// Not sure this method do the right thing ... if we want to get data from the port, it must be an +/// output port ... + +PortBase* DataflowGraph::getDataGetter( std::string portName ) { + for ( auto& portOut : m_outputs ) { + if ( portOut->getName() == portName ) { return portOut.get(); } + } +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[36m\e[1mDataflowGraph::graphGetOutput \e[0m \"" + << "Error, can't generate the interface node for graph output " << portName + << std::endl; +#endif + return nullptr; +} + +std::vector DataflowGraph::getAllDataGetters() { + std::vector r; + r.reserve( m_outputs.size() ); + for ( auto& portOut : m_outputs ) { + r.emplace_back( portOut.get(), portOut->getName(), portOut->getTypeName() ); + } + return r; +} + +Node* DataflowGraph::getNode( const std::string& instanceNameNode ) { + for ( const auto& node : m_nodes ) { + if ( node->getInstanceName() == instanceNameNode ) { return node.get(); } + } + std::cerr << getTypename() + ": The node with the instance name " + instanceNameNode + + " has not been found"; + return nullptr; +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp new file mode 100644 index 00000000000..cee3d907cea --- /dev/null +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -0,0 +1,183 @@ +#pragma once +#include + +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +/** + * \brief Represent a set of connected nodes that definea computational graph + * \todo make a "graph embedding node" that allow to seemlesly integrate a graph as a node in + * another graph + * --> Edition of a graph will allow loading and saving to a file directly + * --> Edition of an embeded graph will defer loading and saving to the parent graph + * --> for this, need to decide if a subgraph is stored in the json of its parent or in a + * separate file + */ +class RA_DATAFLOW_API DataflowGraph : public Node +{ + public: + /// Constructor. + /// The nodes pointing to external data are created here. + /// @param name The name of the render graph. + explicit DataflowGraph( const std::string& name ); + virtual ~DataflowGraph() = default; + + void init() override; + void execute() override; + void destroy() override; + + /// Set the factory set to use when loading a graph + /// This function replace the existing factoryset if any + void setNodeFactories( std::shared_ptr factories ); + + /// get the node factory set associated with from the graph. + /// returns nullptr if no factoryset is associated with the graph + std::shared_ptr getNodeFactories(); + + /// Add a factory to the factoryset of the graph. + /// Creates the factoryset if it does not exists + void addFactory( const std::string& name, std::shared_ptr f ); + + /// Loads nodes and links from a JSON file. + /// @param jsonFilePath The path to the JSON file. + bool loadFromJson( const std::string& jsonFilePath ); + + /// Saves nodes and links to a JSON file. + /// @param jsonFilePath The path to the JSON file. + void saveToJson( const std::string& jsonFilePath ); + + /// Adds a node to the render graph. Adds interface ports to the node newNode and the + /// corresponding input and output ports to the graph. + /// @param newNode The node to add to the render graph. + virtual bool addNode( Node* newNode ); + /// Removes a node from the render graph. Removes input and output ports of the graph + /// corresponding to interface ports of the node. + /// @param node The node to remove from the render graph. + virtual bool removeNode( Node* node ); + /// Connects two nodes of the render graph. + /// The two nodes must already be in the render graph (with the addNode(Node* newNode) + /// function), the first node's in port must be free and the connected in port and out port must + /// have the same type of data. + /// @param nodeFrom The node that contains the out port. + /// @param nodeFromOutputName The name of the out port in nodeFrom. + /// @param nodeTo The node that contains the in port. + /// @param nodeToInputName The name of the in port in nodeTo. + bool addLink( Node* nodeFrom, + const std::string& nodeFromOutputName, + Node* nodeTo, + const std::string& nodeToInputName ); + /// Removes the link connected to this node's input port + bool removeLink( Node* node, const std::string& nodeInputName ); + + /// Gets the nodes + const std::vector>* getNodes() const; + + /// Gets a specific node according to its instance name as a parameter. + /// @param instanceNameNode The instance name of the node. + Node* getNode( const std::string& instanceNameNode ); + + /// Gets the nodes ordered by level (after compilation) + const std::vector>* getNodesByLevel() const; + + /// Compile the render graph to check its validity and simplify it. + /// The compilation has multiple goals: + /// - Remove the nodes that have no direct or indirect connections to sink nodes + /// - Order the nodes by level according to their dependencies + /// - Check if every mandatory port is linked + bool compile() override; + + /// Gets the number of nodes + size_t getNodesCount(); + + /// Deletes all nodes from the render graph. + virtual void clearNodes(); + + /// Flag used to tell the renderer to recompile the rendergraph + bool m_recompile { false }; + + /// Flag set after rendergraph compilation checking if its state is right + bool m_ready { false }; + + /// Creates an output port connected to the named input port of the graph. + /// Return the connected output port if success, transferring the ownership to the caller. + /// Allows to set data to the graph from the caller. + /// \note As ownership is transferred to the caller, the graph must survive the returned + /// pointer. \note If called multiple times for the same port, only the last returned result is + /// usable. + /// @params portName The name of the input port of the graph + std::shared_ptr getDataSetter( std::string portName ); + + /// Returns an alias to the named output port of the graph. + /// Allows to get the data stored at this port after the execution of the graph. + /// \note ownership is left to the graph. + /// @params portName the name of the output port + PortBase* getDataGetter( std::string portName ); + + using DataSetterDesc = std::tuple, std::string, std::string>; + using DataGetterDesc = std::tuple; + + /// Creates a vector that stores all the DataSetters (\see getDataSetter) of the graph. + /// A tuple is composed of an output port connected to an input port of the graph, its name its + /// type. \note If called multiple times for the same port, only the last returned result is + /// usable. + std::vector getAllDataSetters(); + + /// Creates a vector that stores all the DataGetters (\see getDataGetter) of the graph. + /// A tuple is composed of an output port belonging to the graph, its name its type. + std::vector getAllDataGetters(); + + protected: + /** Allow derived class to construct the graph with their own static type + */ + DataflowGraph( const std::string& instanceName, const std::string& typeName ); + /** + * Allow derived class to add operations on a compiled graph + * @return + */ + virtual bool postCompilationOperation() { return true; } + + void fromJsonInternal( const nlohmann::json& ) override; + void toJsonInternal( nlohmann::json& ) const override; + + private: + /// The node factory to use for loading + std::shared_ptr m_factories; + /// The unordered list of nodes. + std::vector> m_nodes; + // Internal node levels representation + /// The list of nodes ordered by levels. + /// Two nodes at the same level have no dependency between them. + std::vector> m_nodesByLevel; + + // Internal helper functions + /// Internal compilation function that allows to go back in the render graph while filling an + /// information map. + /// @param current The current node. + /// @param infoNodes The map that contains information about nodes. + void backtrackGraph( Node* current, + std::unordered_map>>& infoNodes ); + /// Internal compilation function that allows to go through the render graph, using an + /// information map. + /// @param current The current node. + /// @param infoNodes The map that contains information about nodes. + int goThroughGraph( Node* current, + std::unordered_map>>& infoNodes ); + /// Returns the index of the node with the same name as the argument, if there is none, returns + /// -1. + /// @param name The name of the node to find. + int findNode( const std::string& name ); + + public: + static const std::string getTypename(); +}; + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/DataflowGraph.inl b/src/Dataflow/Core/DataflowGraph.inl new file mode 100644 index 00000000000..b46ef2e6d54 --- /dev/null +++ b/src/Dataflow/Core/DataflowGraph.inl @@ -0,0 +1,35 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +inline void DataflowGraph::setNodeFactories( std::shared_ptr factories ) { + m_factories = factories; +} +inline std::shared_ptr DataflowGraph::getNodeFactories() { + return m_factories; +} + +inline void DataflowGraph::addFactory( const std::string& name, std::shared_ptr f ) { + if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } + m_factories->addFactory( name, f ); +} + +inline const std::vector>* DataflowGraph::getNodes() const { + return &m_nodes; +} +inline const std::vector>* DataflowGraph::getNodesByLevel() const { + return &m_nodesByLevel; +} +inline size_t DataflowGraph::getNodesCount() { + return m_nodes.size(); +} +inline const std::string DataflowGraph::getTypename() { + return "DataFlow Graph"; +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp new file mode 100644 index 00000000000..25111062384 --- /dev/null +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -0,0 +1,34 @@ +#pragma once +#include + +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +struct RA_DATAFLOW_API EditableParameterBase { + EditableParameterBase( std::string& name, size_t hashedType ); + virtual ~EditableParameterBase() = default; + std::string getName(); + std::string m_name { "" }; + size_t m_hashedType { 0 }; +}; + +template +struct EditableParameter : public EditableParameterBase { + EditableParameter() = delete; + explicit EditableParameter( std::string name, T& data ); + void addAdditionalData( T newData ); + + T& m_data; + std::vector additionalData; +}; + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/EditableParameter.inl b/src/Dataflow/Core/EditableParameter.inl new file mode 100644 index 00000000000..afd8bac33b1 --- /dev/null +++ b/src/Dataflow/Core/EditableParameter.inl @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +inline EditableParameterBase::EditableParameterBase( std::string& name, size_t hashedType ) : + m_name( name ), m_hashedType( hashedType ) {} + +template +EditableParameter::EditableParameter( std::string name, T& data ) : + EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {}; + +template +void EditableParameter::addAdditionalData( T newData ) { + additionalData.push_back( newData ); +} + +inline std::string EditableParameterBase::getName() { + return m_name; +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Enumerator.hpp b/src/Dataflow/Core/Enumerator.hpp new file mode 100644 index 00000000000..27b16acd5cc --- /dev/null +++ b/src/Dataflow/Core/Enumerator.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +template +class Enumerator : public Ra::Core::Utils::Observable&> +{ + std::vector m_values; + size_t m_currentIndex { 0 }; + T* m_currentValue; + + public: + explicit Enumerator( std::initializer_list values ); + const T& get() const; + size_t size() const; + bool set( size_t p ); + bool set( const T& v ); + typename std::vector::const_iterator begin() const; + typename std::vector::const_iterator end() const; + const T& operator[]( size_t p ) const; +}; + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Enumerator.inl b/src/Dataflow/Core/Enumerator.inl new file mode 100644 index 00000000000..9c6e855d13e --- /dev/null +++ b/src/Dataflow/Core/Enumerator.inl @@ -0,0 +1,62 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +template +Enumerator::Enumerator( std::initializer_list values ) : + m_values { values }, m_currentValue { m_values.data() } {} + +template +const T& Enumerator::get() const { + return *m_currentValue; +} + +template +size_t Enumerator::size() const { + return m_values.size(); +} + +template +bool Enumerator::set( size_t p ) { + if ( p < m_values.size() ) { + m_currentValue = m_values.data() + p; + m_currentIndex = p; + this->notify( *this ); + return true; + } + else { + return false; + } +} + +template +bool Enumerator::set( const T& v ) { + size_t p = 0; + for ( const auto& e : m_values ) { + if ( e == v ) { return set( p ); } + p++; + } + return false; +} + +template +typename std::vector::const_iterator Enumerator::begin() const { + return m_values.cbegin(); +} + +template +typename std::vector::const_iterator Enumerator::end() const { + return m_values.cend(); +} + +template +const T& Enumerator::operator[]( size_t p ) const { + return m_values.at( p ); +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp new file mode 100644 index 00000000000..5f2c0bb6ab2 --- /dev/null +++ b/src/Dataflow/Core/Node.cpp @@ -0,0 +1,109 @@ +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +using namespace Ra::Core::Utils; + +bool Node::s_uuidGeneratorInitialized { false }; +uuids::uuid_random_generator* Node::s_uidGenerator { nullptr }; + +void Node::createUuidGenerator() { + std::random_device rd; + auto seed_data = std::array {}; + std::generate( std::begin( seed_data ), std::end( seed_data ), std::ref( rd ) ); + std::seed_seq seq( std::begin( seed_data ), std::end( seed_data ) ); + auto generator = new std::mt19937( seq ); + s_uidGenerator = new uuids::uuid_random_generator( *generator ); + s_uuidGeneratorInitialized = true; +} + +/// Generates the uuid of the node +void Node::generateUuid() { + if ( !s_uuidGeneratorInitialized ) { createUuidGenerator(); } + m_uuid = ( *s_uidGenerator )(); +} +/// Gets the UUID of the node as a string +std::string Node::getUuid() const { + if ( m_uuid.is_nil() ) { + // generates the uuid (need to remove const attribute) ... + const_cast( this )->generateUuid(); + } + std::string struuid = "{" + uuids::to_string( m_uuid ) + "}"; + return struuid; +} + +bool Node::setUuid( const std::string& uid ) { + if ( m_uuid.is_nil() ) { + auto id = uuids::uuid::from_string( uid ); + if ( id ) { + m_uuid = id.value(); + return true; + } + } + return false; +} + +Node::Node( const std::string& instanceName, const std::string& typeName ) : + m_typeName { typeName }, m_instanceName { instanceName } { + generateUuid(); +} + +void Node::fromJson( const nlohmann::json& data ) { + // get the common content of the Node from the json data + if ( data.contains( "id" ) ) { + std::string struuid = data["id"]; + m_uuid = uuids::uuid::from_string( struuid ).value(); + } + else { + generateUuid(); + } + if ( data.contains( "model" ) ) { + std::string readTypeName = data["model"]["name"]; + if ( readTypeName != m_typeName ) { + LOG( logERROR ) << "Node::fromJson : incoherent type names : json data : " + << readTypeName << " -- expected : " << m_typeName; + } + if ( data["model"].contains( "instance" ) ) { m_instanceName = data["model"]["instance"]; } + + // get the specific concrete node informations + const auto& datamodel = data["model"]; + fromJsonInternal( datamodel ); + } + // get the supplemental informations related to application/gui/... + for ( auto& [key, value] : data.items() ) { + if ( key != "id" && key != "model" ) { m_extraJsonData.emplace( key, value ); } + } +} + +void Node::toJson( nlohmann::json& data ) const { + // write the common content of the Node to the json data + std::string struuid = "{" + uuids::to_string( m_uuid ) + "}"; + data["id"] = struuid; + + nlohmann::json model; + model["instance"] = m_instanceName; + model["name"] = m_typeName; + + // Fill the specific concrete node informations + toJsonInternal( model ); + data.emplace( "model", model ); + + // store the supplemental informations related to application/gui/... + for ( auto& [key, value] : m_extraJsonData.items() ) { + if ( key != "id" && key != "model" ) { data.emplace( key, value ); } + } +} + +void Node::addJsonMetaData( const nlohmann::json& data ) { + for ( auto& [key, value] : data.items() ) { + m_extraJsonData[key] = value; + } +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp new file mode 100644 index 00000000000..2704c05eb51 --- /dev/null +++ b/src/Dataflow/Core/Node.hpp @@ -0,0 +1,211 @@ +#pragma once +#include + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +class RA_DATAFLOW_API Node +{ + public: + /// Constructor. + Node() = delete; + Node( const Node& ) = delete; + Node& operator=( const Node& ) = delete; + virtual ~Node() = default; + + /// \brief Initializes the node content + /// The init() function is called once at the start of the application. + /// Its goal is to initialize the node's internal data if any. + /// The base version do nothing + virtual void init(); + + /// \brief Executes the node + /// The execute() function on an active node is called each time the graph is executed. + /// Execute the node processing on the input ports and write the results to the output ports. + virtual void execute() = 0; + + /// \brief delete the node content + /// The destroy() function is called once at the end of the application. + /// Its goal is to free the internal data that have been allocated. + virtual void destroy(); + + /// TODO : specify the json format for nodes and what is expected from the following ethods + + /// \brief serialize the content of the node. + /// Fill the given json object with the json representation of the concrete node. + virtual void toJson( nlohmann::json& data ) const; + + /// \brief unserialized the content of the node. + /// Fill the node from its json representation + virtual void fromJson( const nlohmann::json& data ); + + /// \brief Compile the node to check its validity + virtual bool compile(); + + /// \bried Add a metadata to the node to store application specific informations. + /// used, e.g. by the node editor gui to save node position in the graphical canvas. + void addJsonMetaData( const nlohmann::json& data ); + + /// \bried Give access to extra json data stored on the node. + nlohmann::json& getJsonMetaData(); + + /// \brief Gets the type name of the node. + const std::string& getTypeName() const; + + /// \brief Gets the instance name of the node. + const std::string& getInstanceName() const; + + /// \brief Sets the instance name (rename) the node + void setInstanceName( const std::string& newName ); + + /// \brief Generates the uuid of the node + void generateUuid(); + + /// \brief Gets the UUID of the node as a string + std::string getUuid() const; + + /// \brief Sets the UUID of the node from a valid string string + /// \return true if the uuid is set, false if the node already have a valid uid + bool setUuid( const std::string& uid ); + + /// \brief Tests if the node is deletable (deprecated) + /// \return the deletable status of the node + [[deprecated]] bool isDeletable(); + + /// \brief Set the deletable status of the node + [[deprecated]] void setDeletableStatus( bool deletable = true ); + + /// \brief Gets the in ports of the node. + const std::vector>& getInputs(); + + /// \brief Gets the out ports of the node. + const std::vector>& getOutputs(); + + /// \brief Get the interface ports of the node + const std::vector& getInterface(); + + /// Gets the editable parameters of the node. + const std::vector>& getEditableParameters(); + + /// \brief Sets the filesystem (real or virtual) location for the pass resources + inline void setResourcesDir( std::string resourcesRootDir ); + + /// \brief Flag that checks if the node is already initialized + bool m_initialized { false }; + + /// \brief Two nodes are considered equal if there names are the same. + bool operator==( const Node& o_node ); + + /// Adds an interface port to the node. + /// This function checks if there is no interface port with the same name already associated + /// with this node. + /// \param port The interface port to add. + bool addInterface( PortBase* port ); + + protected: + /// \param instanceName The name of the node + /// \param typeName The type name of the node + + Node( const std::string& instanceName, const std::string& typeName ); + + /// internal json representation of the Node. + /// Must be implemented by inheriting classes. + /// Be careful with template specialization and function member overriding when implementing + /// this method. + virtual void fromJsonInternal( const nlohmann::json& ) = 0; + + /// internal json representation of the Node. + /// Must be implemented by inheriting classes. + /// Be careful with template specialization and function member overriding when implementing + /// this method. + virtual void toJsonInternal( nlohmann::json& ) const = 0; + + /// Adds an in port to the node. + /// This function checks if the port is an input port, then if there is no in port with the same + /// name already associated with this node. + /// \param in The in port to add. + bool addInput( PortBase* in ); + + /// Adds an out port to the node and the data associated with it. + /// This function checks if there is no out port with the same name already associated with this + /// node. + /// \param out The in port to add. + /// \param data The data associated with the port. + template + void addOutput( PortOut* out, T* data ); + + /// Adds an out port for a GRAPH. This port is also an interface port whose reference is stored + /// in the source and sink nodes of the graph. This function checks if there is no out port with + /// the same name already associated with the graph. + /// \param out The port to add. + bool addOutput( PortBase* out ); + + /// \brief Adds an editable parameter to the node if it does not already exist. + /// \note the node will take ownership of the editable object. + /// \param editableParameter The editable parameter to add. + template + bool addEditableParameter( EditableParameter* editableParameter ); + + /// Remove an editable parameter to the node if it does exist. + /// \param name The name of the editable parameter to remove. + /// \return true if the editable parameter is found and removed. + template + bool removeEditableParameter( const std::string name ); + + /// The uuid of the node (TODO, use https://github.com/mariusbancila/stduuid instead of a + /// string) + // std::string m_uuid; + uuids::uuid m_uuid; + /// The deletable status of the node + bool m_isDeletable { true }; + /// The type name of the node. Initialized once at construction + std::string m_typeName; + /// The instance name of the node + std::string m_instanceName; + /// The in ports of the node + std::vector> m_inputs; + /// The out ports of the node + std::vector> m_outputs; + /// The reflected ports of the node if it is only a source or sink node + std::vector m_interface; + /// The editable parameters of the node + std::vector> m_editableParameters; + + /// The base resources directory + std::string m_resourceDir { "./" }; + + /// Additional data on the node, added by application or gui or ... + nlohmann::json m_extraJsonData; + + /// generator for uuid + static bool s_uuidGeneratorInitialized; + static uuids::uuid_random_generator* s_uidGenerator; + static void createUuidGenerator(); + + public: + /// \brief Returns the demangled type name of the node or any human readable representation of + /// the type name. + /// This is a public static member each node must define to be serializable + static const std::string getTypename(); +}; + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl new file mode 100644 index 00000000000..bf537cf4e75 --- /dev/null +++ b/src/Dataflow/Core/Node.inl @@ -0,0 +1,183 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +#if 0 +template +constexpr std::string_view type_name() { +# ifdef __clang__ + std::string_view p = __PRETTY_FUNCTION__; + return std::string_view( p.data() + 34, p.size() - 34 - 1 ); +# elif defined( __GNUC__ ) + std::string_view p = __PRETTY_FUNCTION__; +# if __cplusplus < 201402 + return std::string_view( p.data() + 36, p.size() - 36 - 1 ); +# else + return std::string_view( p.data() + 49, p.find( ';', 49 ) - 49 ); +# endif +# elif defined( _MSC_VER ) + std::string_view p = __FUNCSIG__; + return std::string_view( p.data() + 84, p.size() - 84 - 7 ); +# else + return type_id( T ).type_name(); +# endif +} + +inline std::size_t +replace_all_in_string( std::string& inout, std::string_view what, std::string_view with ) { + std::size_t count {}; + for ( std::string::size_type pos {}; + inout.npos != ( pos = inout.find( what.data(), pos, what.length() ) ); + pos += with.length(), ++count ) { + inout.replace( pos, what.length(), with.data(), with.length() ); + } + return count; +} + +inline std::size_t remove_all_in_string( std::string& inout, std::string_view what ) { + return replace_all_in_string( inout, what, "" ); +} +#endif + +inline void Node::init() { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName + << "\": initialization." << std::endl; +#endif +} + +inline void Node::destroy() { +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName << "\": destroy." + << std::endl; +#endif +} + +inline nlohmann::json& Node::getJsonMetaData() { + return m_extraJsonData; +} + +inline const std::string& Node::getTypeName() const { + return m_typeName; +} + +inline const std::string& Node::getInstanceName() const { + return m_instanceName; +} + +inline void Node::setInstanceName( const std::string& newName ) { + m_instanceName = newName; +} + +inline bool Node::isDeletable() { + return m_isDeletable; +} + +inline void Node::setDeletableStatus( bool deletable ) { + m_isDeletable = deletable; +} + +inline const std::vector>& Node::getInputs() { + return m_inputs; +} + +inline const std::vector>& Node::getOutputs() { + return m_outputs; +} + +inline const std::vector& Node::getInterface() { + return m_interface; +} + +inline const std::vector>& Node::getEditableParameters() { + return m_editableParameters; +} + +inline void Node::setResourcesDir( std::string resourcesRootDir ) { + m_resourceDir = std::move( resourcesRootDir ); +} + +inline bool Node::operator==( const Node& o_node ) { + return m_typeName == o_node.getTypeName(); +} + +inline bool Node::addInput( PortBase* in ) { + if ( !in->is_input() ) { return false; } + bool found = false; + for ( auto& input : m_inputs ) { + if ( input->getName() == in->getName() ) { found = true; } + } + if ( !found ) { m_inputs.emplace_back( in ); } + return !found; +} + +inline bool Node::addOutput( PortBase* out ) { + if ( out->is_input() ) { return false; } + bool found = false; + for ( auto& output : m_outputs ) { + if ( output->getName() == out->getName() ) { found = true; } + } + if ( !found ) { m_outputs.emplace_back( out ); } + return !found; +} + +template +void Node::addOutput( PortOut* out, T* data ) { + bool found = false; + for ( auto& output : m_outputs ) { + if ( output->getName() == out->getName() ) { found = true; } + } + if ( !found ) { + m_outputs.emplace_back( out ); + out->setData( data ); + } +} + +inline bool Node::addInterface( PortBase* ports ) { + bool found = false; + for ( auto& port : m_interface ) { + if ( port->getName() == ports->getName() ) { found = true; } + } + if ( !found ) { m_interface.emplace_back( ports ); } + return !found; +} + +template +bool Node::addEditableParameter( EditableParameter* editableParameter ) { + bool found = false; + for ( auto& edit : m_editableParameters ) { + if ( edit.get()->getName() == editableParameter->getName() ) { found = true; } + } + if ( !found ) { m_editableParameters.emplace_back( editableParameter ); } + return !found; +} + +template +bool Node::removeEditableParameter( const std::string name ) { + bool found = false; + auto it = m_editableParameters.begin(); + while ( it != m_editableParameters.end() ) { + if ( ( *it ).get()->getName() == name ) { + m_editableParameters.erase( it ); + found = true; + break; + } + ++it; + } + return found; +} + +inline const std::string Node::getTypename() { + return "Node"; +} + +inline bool Node::compile() { + return true; +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp new file mode 100644 index 00000000000..af3e9e3f772 --- /dev/null +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -0,0 +1,131 @@ +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +namespace NodeFactoriesManager { +const std::string dataFlowBuiltInsFactoryName { "DataFlowBuiltIns" }; +} + +NodeFactory::NodeFactory( std::string name ) : m_name( std::move( name ) ) {} + +std::string NodeFactory::getName() const { + return m_name; +} + +Node* NodeFactory::createNode( std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph ) { + auto it = m_nodesCreators.find( nodeType ); + if ( it != m_nodesCreators.end() ) { + auto node = it->second.first( data ); + if ( owningGraph != nullptr ) { owningGraph->addNode( node ); } + return node; + } +#if 0 + else { + std::cerr << "NodeFactory: no defined node for type " << nodeType << "." << std::endl; + std::cerr << "Available nodes are : " << std::endl; + for ( const auto& e : m_nodesCreators ) { + std::cerr << "\t" << e.first << std::endl; + } + } +#endif + return nullptr; +} + +void NodeFactory::registerNodeCreator( std::string nodeType, + NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory ) { + auto it = m_nodesCreators.find( nodeType ); + if ( it == m_nodesCreators.end() ) { + m_nodesCreators[nodeType] = { std::move( nodeCreator ), nodeCategory }; + } + else { + std::cerr << "NodeFactory: trying to add an already existing node creator for type " + << nodeType << "." << std::endl; + } +} + +size_t NodeFactory::nextNodeId() { + return ++m_nodesCreated; +} + +NodeFactorySet::NodeFactorySet() { + // Add the "DataFlowBuiltIns" factory + NodeFactoriesManager::getDataFlowBuiltInsFactory(); +} + +Node* NodeFactorySet::createNode( std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph ) { + for ( const auto& it : m_factories ) { + auto node = it.second->createNode( nodeType, data, owningGraph ); + if ( node ) { return node; } + } + std::cerr << "NodeFactorySet: unable to find constructor for " << nodeType + << " in any of the following factories :" << std::endl; + for ( const auto& it : m_factories ) { + std::cerr << "\t" << it.first << std::endl; + } + return nullptr; +} + +namespace NodeFactoriesManager { + +static NodeFactorySet s_factoryManager {}; + +NodeFactorySet getFactoryManager() { + return s_factoryManager; +} + +bool registerFactory( NodeFactorySet::mapped_type factory ) { + auto factoryName = factory->getName(); + return s_factoryManager.addFactory( std::move( factoryName ), std::move( factory ) ); +} + +bool removeFactory( const NodeFactorySet::key_type& factoryName ) { + return s_factoryManager.removeFactory( factoryName ); +} + +NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ) { + auto factory = s_factoryManager.find( factoryName ); + if ( factory == s_factoryManager.end() ) { return nullptr; } + return factory->second; +} + +bool unregisterFactory( NodeFactorySet::key_type factoryName ) { + return s_factoryManager.removeFactory( factoryName ); +} + +NodeFactorySet::mapped_type getDataFlowBuiltInsFactory() { + + auto i = s_factoryManager.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); + if ( i != s_factoryManager.end() ) { return i->second; } + // Create the DataFlowBuiltIns and add it to the manager + + NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( + NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; + + // TODO Call an external function to populate the default factory + coreFactory->registerNodeCreator( + Sources::BooleanValueSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::ScalarValueSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::ColorSourceNode::getTypename() + "_", "Source" ); + + s_factoryManager.addFactory( NodeFactoriesManager::dataFlowBuiltInsFactoryName, coreFactory ); + return coreFactory; +} + +} // namespace NodeFactoriesManager + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp new file mode 100644 index 00000000000..f3f314b59e1 --- /dev/null +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -0,0 +1,225 @@ +#pragma once +#include + +#include + +#include + +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +class DataflowGraph; + +/** + * NodeFactory store a set of functions allowing to dynmically create dataflow nodes. + * A NodeFactory is used when loading a nodegraph from a json representation of the graph to + * instanciate all the loaded nodes. + * Each DataflowGraph must have a reference to the nodeFactory to be used to create all the node he + * has. + * + */ +class RA_DATAFLOW_API NodeFactory +{ + public: + /** Creates an empty factory with the given name */ + explicit NodeFactory( std::string name ); + + std::string getName() const; + + /** Function that creates and initialize a node. + * Typical implementation of such a function should do the following : + * { + * auto node = new ConcreteNodeType( unique_instance_name ); + * node->fromJson( data); + * return node; + * } + */ + using NodeCreatorFunctor = std::function; + + /** + * Associate, for a given concrete node type, a custom NodeCreatorFunctor + * @tparam T Concrete node type identifier + * \param nodeCreator Functor to create an node of the corresponding concrete node type. + * \param nodeCategory Category of the node + */ + template + void registerNodeCreator( NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory = "RadiumNodes" ); + + /** + * Associate, for a given concrete node type, a generic NodeCreatorFunctor + * @tparam T Concrete node type identifier + * \param instanceNamePrefix prefix of the node instance name (will be called "prefix_i" with i + * a unique number. + * \param nodeCategory Category of the node + */ + template + void registerNodeCreator( const std::string& instanceNamePrefix, + const std::string& nodeCategory = "RadiumNodes" ); + /** + * Associate, for a given concrete node type name, a NodeCreatorFunctor + * \param nodeType the name of the concrete type + * (the same as what is obtained by T::getTypename() on a node of type T) + * \param nodeCreator Functor to create an node of the corresponding concrete node type. + */ + void registerNodeCreator( std::string nodeType, + NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory = "RadiumNodes" ); + + /** + * Get an unique, increasing node id. + * \return + */ + size_t nextNodeId(); + + /** Create a node of the requested type. + * The node is filled with the given json content. + * If owningGraph is non null, the node is added to this graph + * \param nodeType Type name of the node to be created. + * \param data json representation of the node data (might be empty) + * \param owningGraph if non null, the node is added to ths graph + * \return the new node. Ownership of the returned pointer is left to the caller. + */ + [[nodiscard]] Node* createNode( std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph = nullptr ); + + /** + * The type of the associative container used to store the factory + * this container associate a concrete node type name to a pair + * + * The name of the node category is helpful for graphical NodeGraph editor. + * By default this is set to be "RadiumNodes" + */ + using ContainerType = + std::unordered_map>; + /** + * Get a const reference on the associative map + * \return + */ + [[nodiscard]] const ContainerType& getFactoryMap() const; + + private: + ContainerType m_nodesCreators; + size_t m_nodesCreated { 0 }; + std::string m_name; +}; + +/** + * NodeFactorySet store a set of NodeFactory + */ +class RA_DATAFLOW_API NodeFactorySet +{ + public: + using key_type = std::string; + using mapped_type = std::shared_ptr; + using value_type = std::pair; + + using container_type = std::map; + + using const_iterator = container_type::const_iterator; + using iterator = container_type::iterator; + + /** + * Default constructor : Will initialize the FactorySet with the Dataflow::Core "default" + * factory + */ + NodeFactorySet(); + /** + * Add a factory to the set of factories available + * \param factoryname the name of the factory + * \param factory the factory + * \return true if the factory was inserted, false if the insertion was prevented by an + * already existing factory with the same name. + */ + bool addFactory( key_type factoryname, mapped_type factory ); + + /** + * \brief Test if a factory exists in the set with the given name + * \param factoryname The name of the factory to search for + * \return an optional that is empty (evaluates to false) if no factory exeist with the given + * name or that contains the existing factory. + */ + Ra::Core::Utils::optional + hasFactory( const NodeFactorySet::key_type& factoryname ); + + /** + * Remove the identified factory from the set + * \param factoryname the name of the factory to remove + * \return true if the factory was removed, false if the factory does not exist in the set. + */ + bool removeFactory( const key_type& factoryname ); + + /** + * Create a node using one of the functor (if it exists) registered in one factory for the given + * type name. + * \return the created node, nullptr if there is no construction functor registered for the + * type. + */ + [[nodiscard]] Node* createNode( std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph = nullptr ); + + /* Wrappers to the interface of the underlying container + * see https://en.cppreference.com/w/cpp/container/map + */ + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + const_iterator find( const key_type& key ) const; + std::pair insert( value_type value ); + size_t erase( const key_type& key ); + + private: + container_type m_factories; +}; + +/** TODO Make this a class similar to all the managers of Radium. + * Implement a NodeFactoryManager that stores a set of factories available to the system. + * Such a manager will be populated with Core::Dataflow node factories (Specialized sources, + * specialized sink, ...) and will allow users to register its own factories. + * + * When creating a graph, the set of needed factories should be given as a constructor parameter + * or built by adding factories identifier to the graph. + * + * When a graph is saved, the name of the factories he needs will be exported as string array + * in the json. + * + * When a graph is loaded, the set of factories is built using the factories array in the json. + * + * @note: the factory name "DataFlowBuiltIns" is reserved and correspond to the base nodes available + * for each dataflow graph (Specialized sources, specialized sink, ...). This factory will be + * automatically added to all created factoryset. + */ +namespace NodeFactoriesManager { +/** Names of the system Builtins factories (automatically added to each graph) */ +extern const std::string dataFlowBuiltInsFactoryName; + +RA_DATAFLOW_API NodeFactorySet getFactoryManager(); + +/** Register a factory into the manager. + * The key will be fetched from the factory (its name) + */ +RA_DATAFLOW_API bool registerFactory( NodeFactorySet::mapped_type factory ); + +/** Remove a factory from the manager*/ +RA_DATAFLOW_API bool removeFactory( const NodeFactorySet::key_type& factoryName ); + +RA_DATAFLOW_API NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ); + +RA_DATAFLOW_API NodeFactorySet::mapped_type getDataFlowBuiltInsFactory(); + +RA_DATAFLOW_API bool unregisterFactory( NodeFactorySet::key_type factoryName ); +} // namespace NodeFactoriesManager + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/NodeFactory.inl b/src/Dataflow/Core/NodeFactory.inl new file mode 100644 index 00000000000..54c71db1e81 --- /dev/null +++ b/src/Dataflow/Core/NodeFactory.inl @@ -0,0 +1,81 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +template +void NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory ) { + registerNodeCreator( T::getTypename(), std::move( nodeCreator ), nodeCategory ); +} + +template +void NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, + const std::string& nodeCategory ) { + registerNodeCreator( + T::getTypename(), + [this, instanceNamePrefix]( const nlohmann::json& data ) { + auto node = new T( instanceNamePrefix + std::to_string( this->nextNodeId() ) ); + node->fromJson( data ); + return node; + }, + nodeCategory ); +} + +inline const NodeFactory::ContainerType& NodeFactory::getFactoryMap() const { + return m_nodesCreators; +} + +inline bool NodeFactorySet::addFactory( NodeFactorySet::key_type factoryname, + NodeFactorySet::mapped_type factory ) { + const auto [loc, inserted] = insert( { std::move( factoryname ), std::move( factory ) } ); +#ifdef DEBUG + if ( !inserted ) { + std::cerr << "NodeFactorySet: Factory " << loc->first + << " already in the set. Not inserted again." << std::endl; + } +#endif + return inserted; +} + +inline Ra::Core::Utils::optional +NodeFactorySet::hasFactory( const NodeFactorySet::key_type& factoryname ) { + auto f = m_factories.find( factoryname ); + if ( f != m_factories.end() ) { return f->second; } + else { + return {}; + } +} + +inline bool NodeFactorySet::removeFactory( const NodeFactorySet::key_type& factoryname ) { + return erase( factoryname ); +} +inline NodeFactorySet::const_iterator NodeFactorySet::begin() const { + return m_factories.begin(); +} +inline NodeFactorySet::const_iterator NodeFactorySet::end() const { + return m_factories.end(); +} +inline NodeFactorySet::const_iterator NodeFactorySet::cbegin() const { + return m_factories.cbegin(); +} +inline NodeFactorySet::const_iterator NodeFactorySet::cend() const { + return m_factories.cend(); +} +inline NodeFactorySet::const_iterator +NodeFactorySet::find( const NodeFactorySet::key_type& key ) const { + return m_factories.find( key ); +} +inline std::pair +NodeFactorySet::insert( NodeFactorySet::value_type value ) { + return m_factories.insert( std::move( value ) ); +} +inline size_t NodeFactorySet::erase( const NodeFactorySet::key_type& key ) { + return m_factories.erase( key ); +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp new file mode 100644 index 00000000000..c7cad01fbaf --- /dev/null +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +// These nodes will not be added in the BuiltIns factory as they are templated. +// Only fully specialized template can be added to the factory. +// #include +// #include +// #include diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp b/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp new file mode 100644 index 00000000000..b8775d07c90 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp @@ -0,0 +1,66 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Filters { + +/** + * Filter a collection according to a unary predicate + * Copy each element which satisfies the filter unary predicate of the input std::vector + * to the output std::vector. + * @tparam T The value type of the collection + */ +template +class FilterNode : public Node +{ + public: + /** + * unaryPredicate Type + */ + using UnaryPredicate = std::function; + + /** + * \brief Construct a filter accepting all its input ( true() lambda ) + * \param instanceName + */ + explicit FilterNode( const std::string& instanceName ); + + /** + * \brief Construct a filter with the given predicate + * \param instanceName + * \param filterFunction + */ + FilterNode( const std::string& instanceName, UnaryPredicate filterFunction ); + + void init() override; + void execute() override; + + /// Sets the filtering predicate on the node + void setFilterFunction( UnaryPredicate filterFunction ); + + protected: + FilterNode( const std::string& instanceName, + const std::string& typeName, + std::function filterFunction ); + + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + private: + std::function m_filterFunction; + std::vector m_elements; + + public: + static const std::string getTypename(); +}; + +} // namespace Filters +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl b/src/Dataflow/Core/Nodes/Filters/FilterNode.inl new file mode 100644 index 00000000000..74b916b0fa0 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Filters/FilterNode.inl @@ -0,0 +1,80 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Filters { + +template +FilterNode::FilterNode( const std::string& instanceName ) : + FilterNode( instanceName, getTypename(), []( T ) { return true; } ) {} + +template +FilterNode::FilterNode( const std::string& instanceName, + FilterNode::UnaryPredicate filterFunction ) : + FilterNode( instanceName, getTypename(), filterFunction ) {} + +template +void FilterNode::setFilterFunction( UnaryPredicate filterFunction ) { + m_filterFunction = filterFunction; +} + +template +void FilterNode::init() { + Node::init(); + m_elements.clear(); +} + +template +void FilterNode::execute() { + auto input = dynamic_cast>*>( m_inputs[0].get() ); + if ( input->isLinked() ) { + const auto& inData = input->getData(); + m_elements.clear(); + m_elements.reserve( inData.size() ); + std::copy_if( + inData.begin(), inData.end(), std::back_inserter( m_elements ), m_filterFunction ); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[36m\e[1mFilterNode \e[0m \"" << m_instanceName << "\": execute, from " + << input->getData().size() << " to " << m_elements.size() << " " + << typeid( T ).name() << "." << std::endl; +#endif + } +} + +template +const std::string FilterNode::getTypename() { + return std::string { "Filter::" } + Ra::Core::Utils::demangleType(); +} + +template +FilterNode::FilterNode( const std::string& instanceName, + const std::string& typeName, + std::function filterFunction ) : + Node( instanceName, typeName ), m_filterFunction( filterFunction ) { + auto portIn = new PortIn>( "in", this ); + addInput( portIn ); + portIn->mustBeLinked(); + auto portOut = new PortOut>( "out", this ); + addOutput( portOut, &m_elements ); +} + +template +void FilterNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string { "Filtering function could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +void FilterNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; +} + +} // namespace Filters +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp new file mode 100644 index 00000000000..91cf7663a87 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -0,0 +1,43 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sinks { + +template +class SinkNode : public Node +{ + public: + explicit SinkNode( const std::string& name ); + + void execute() override; + /** + * Get the delivered data + * @return a copy of the delivered data. + */ + T getData() const; + /** + * Get the delivered data + * @return a const ref to the delivered data. + */ + const T& getDataByRef() const; + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + private: + T m_data; + + public: + static const std::string getTypename(); +}; + +} // namespace Sinks +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl new file mode 100644 index 00000000000..857e28762ea --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -0,0 +1,59 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sinks { + +template +SinkNode::SinkNode( const std::string& name ) : Node( name, getTypename() ) { + auto portIn = new PortIn( "from", this ); + portIn->mustBeLinked(); + addInput( portIn ); +} + +template +void SinkNode::execute() { + auto input = static_cast*>( m_inputs[0].get() ); + auto interface = static_cast*>( m_interface[0] ); + if ( input->isLinked() ) { + // If interface is linked, do not copy the data, just transmit the pointer + interface->setData( &( input->getData() ) ); + } + else { + m_data = input->getData(); + } +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[33m\e[1m" << getTypename() << "\e[0m \"" << getInstanceName() << "\": execute." + << std::endl; +#endif +} + +template +T SinkNode::getData() const { + return m_data; +} + +template +const T& SinkNode::getDataByRef() const { + return m_data; +} + +template +const std::string SinkNode::getTypename() { + return std::string { "Sink::" } + Ra::Core::Utils::demangleType(); +} + +template +void SinkNode::toJsonInternal( nlohmann::json& data ) const {} + +template +void SinkNode::fromJsonInternal( const nlohmann::json& data ) {} + +} // namespace Sinks +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp new file mode 100644 index 00000000000..f3a3ce8d1fc --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -0,0 +1,87 @@ +#pragma once +#include + +#include +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sources { +/** + * Specialization of SingleDataSourceNode for boolean value + */ +class BooleanValueSource : public SingleDataSourceNode +{ + public: + explicit BooleanValueSource( const std::string& name ); + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + public: + static const std::string getTypename(); +}; + +/** + * Specialization of SingleDataSourceNode for scalar value + */ +class ScalarValueSource : public SingleDataSourceNode +{ + public: + explicit ScalarValueSource( const std::string& name ); + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + public: + static const std::string getTypename(); +}; + +/** + * Specialization of SingleDataSourceNode for Core::Utils::Color value + */ +class ColorSourceNode : public SingleDataSourceNode +{ + public: + explicit ColorSourceNode( const std::string& name ); + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + public: + static const std::string getTypename(); +}; + +/** \brief Partial specialization of SingleDataSourceNode for for Core::Containers::VectorArray. + */ +template +class VectorArrayDataSource : public SingleDataSourceNode> +{ + protected: + VectorArrayDataSource( const std::string& instanceName, const std::string& typeName ); + + public: + explicit VectorArrayDataSource( const std::string& name ) : + VectorArrayDataSource( name, VectorArrayDataSource::getTypename() ) {} + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + private: + static const std::string getTypename(); +}; + +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl new file mode 100644 index 00000000000..abe8d5abffb --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl @@ -0,0 +1,99 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sources { + +/* Source bool */ +inline BooleanValueSource::BooleanValueSource( const std::string& name ) : + SingleDataSourceNode( name, BooleanValueSource::getTypename() ) { + setEditable( "boolean" ); +} + +inline void BooleanValueSource::toJsonInternal( nlohmann::json& data ) const { + data["boolean"] = *getData(); +} + +inline void BooleanValueSource::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "boolean" ) ) { + bool v = data["boolean"]; + setData( v ); + } +} + +inline const std::string BooleanValueSource::getTypename() { + return "Source::bool"; +} + +/* Source Scalar */ + +inline const std::string ScalarValueSource::getTypename() { + return "Source::Scalar"; +} + +inline ScalarValueSource::ScalarValueSource( const std::string& name ) : + SingleDataSourceNode( name, ScalarValueSource::getTypename() ) { + setEditable( "number" ); +} + +inline void ScalarValueSource::toJsonInternal( nlohmann::json& data ) const { + data["number"] = *getData(); +} + +inline void ScalarValueSource::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "number" ) ) { + Scalar v = data["number"]; + setData( v ); + } +} + +/* Source Color */ +inline const std::string ColorSourceNode::getTypename() { + return "Source::Color"; +} +inline ColorSourceNode::ColorSourceNode( const std::string& name ) : + SingleDataSourceNode( name, ColorSourceNode::getTypename() ) { + setEditable( "color" ); +} + +inline void ColorSourceNode::toJsonInternal( nlohmann::json& data ) const { + auto c = Ra::Core::Utils::Color::linearRGBTosRGB( *getData() ); + std::array color { c.x(), c.y(), c.z() }; + data["color"] = color; +} + +inline void ColorSourceNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "color" ) ) { + std::array c = data["color"]; + auto v = + Ra::Core::Utils::Color::sRGBToLinearRGB( Ra::Core::Utils::Color( c[0], c[1], c[2] ) ); + setData( v ); + } +} + +template +const std::string VectorArrayDataSource::getTypename() { + return std::string { "Source:VectorArray<" } + Ra::Core::Utils::demangleType() + ">"; +} + +template +void VectorArrayDataSource::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = std::string { "Serialization of data from a " } + + VectorArrayDataSource::getTypename() + " is not supported."; +} + +template +void VectorArrayDataSource::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "data" ) ) { + std::cerr << "Warning : deserialization of Ra::Core::VectorArray not yet implemented\n"; + } +} + +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp new file mode 100644 index 00000000000..e490d28111b --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -0,0 +1,85 @@ +#pragma once +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sources { +/** + * \brief Base class for nodes that will give access to some input data to the graph. + * This class can be used to feed nodes on a dataflow graph with some data coming + * from outside the graph or from the source node itself. + * + * The data delivered by the node can be explicitly set/get or can be made editable. + * + * @tparam T The type of the data to serve. + */ +template +class SingleDataSourceNode : public Node +{ + protected: + SingleDataSourceNode( const std::string& instanceName, const std::string& typeName ); + + public: + explicit SingleDataSourceNode( const std::string& name ) : + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) {} + + void execute() override; + + /** \brief Set the data to be delivered by the node. + * @param data + */ + void setData( T* data ); + + /** + * \brief Get the delivered data + * @return The non owning pointer (alias) to the delivered data. + */ + T* getData() const; + + /** + * \brief Set the delivered data editable using the given name + * Give access to the internal data storage. + * If the node interface is connected, the edition will not result on a propagation to the + * graph as internal data storage will be superseeded by the data from the interface. + * @param name Name of the data as it will appear on edition gui. If not given, the default + * name "Data" will be used. + */ + void setEditable( const std::string name = "Data" ); + + /** + * \brief Remove the delivered data from being editable + * @param name Name of the data given when calling setEditable + */ + void removeEditable( const std::string name = "Data" ); + + protected: + void fromJsonInternal( const nlohmann::json& ) override; + void toJsonInternal( nlohmann::json& ) const override; + + /// The data provided by the node + /// Ownership of this pointer is left to the caller + T* m_data { nullptr }; + + /// Used to deliver (and edit) data when the interface is not connected. + T m_localData; + + /// Alias to the output port + PortOut* m_portOut { nullptr }; + + /// used only at deserialization + void setData( T& data ); + + public: + static const std::string getTypename(); +}; + +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl new file mode 100644 index 00000000000..84c15d2b4eb --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -0,0 +1,95 @@ +#pragma once +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sources { + +template +SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, + const std::string& typeName ) : + Node( instanceName, typeName ) { + m_data = &m_localData; + m_portOut = new PortOut( "to", this ); + addOutput( m_portOut, m_data ); +} + +template +void SingleDataSourceNode::execute() { + auto interface = static_cast*>( m_interface[0] ); + if ( interface->isLinked() ) { + // copy the data to be delivered into the node (COPY_DATA) + m_data = &( interface->getData() ); + } + else { + m_data = &m_localData; + } + m_portOut->setData( m_data ); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[34m\e[1mSingleDataSourceNode\e[0m \"" << m_instanceName << "\": execute." + << std::endl; +#endif +} + +template +void SingleDataSourceNode::setData( T* data ) { + if ( m_data == &m_localData ) { + // TODO : do we need to copy the data ? + std::cerr << "SingleDataSourceNode::setData : overriding the local data !!!\n"; + m_localData = *data; + } + else { + m_data = data; + } + m_portOut->setData( m_data ); +} + +template +void SingleDataSourceNode::setData( T& data ) { + // TODO : assert m_data == &m_localData ??? + m_localData = data; + m_portOut->setData( m_data ); +} + +template +T* SingleDataSourceNode::getData() const { + return m_data; +} + +template +void SingleDataSourceNode::setEditable( const std::string name ) { + addEditableParameter( new EditableParameter( name, m_localData ) ); +} + +template +void SingleDataSourceNode::removeEditable( const std::string name ) { + removeEditableParameter( name ); +} + +template +const std::string SingleDataSourceNode::getTypename() { + std::string templatedTypeName = Ra::Core::Utils::demangleType(); + return "Source::" + templatedTypeName; +} + +template +void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string( "Unable to save data when serializing a SingleDataSourceNode<" ) + + Ra::Core::Utils::demangleType() + ">."; + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +void SingleDataSourceNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to read data when un-serializing a " << getTypeName() << "."; +} + +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp new file mode 100644 index 00000000000..a573f56670e --- /dev/null +++ b/src/Dataflow/Core/Port.hpp @@ -0,0 +1,170 @@ +#pragma once +#include + +#include +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +class Node; + +template +class PortOut; +template +class PortIn; + +/// Base class for nodes' ports +class RA_DATAFLOW_API PortBase +{ + private: + /// The name of the port. + std::string m_name { "" }; + /// The port's data's type's hash. + size_t m_type { 0 }; + /// A pointer to the node this port belongs to. + Node* m_node { nullptr }; + + protected: + /// Flag that tells if the port is linked. + bool m_isLinked { false }; + /// Flag that tells if the port must have a connection + bool m_isLinkMandatory { false }; + + public: + /// Constructor. + /// @param name The name of the port. + /// @param type The data's type's hash. + /// @param node The pointer to the node associated with the port. + PortBase( const std::string& name, size_t type, Node* node ); + virtual ~PortBase() = default; + /// Gets the port's name. + const std::string& getName(); + /// Gets the hash of the type of the data. + size_t getType(); + /// Gets a pointer to the node this port belongs to. + Node* getNode(); + virtual bool hasData(); + // TODO : getData() to avoid dynamic_cast to get the data of the PortOut. + /// Returns true if the port is linked + bool isLinked(); + /// Returns true if the port is flagged as being mandatory linked + bool isLinkMandatory(); + /// Flags the port as being mandatory linked + void mustBeLinked(); + virtual PortBase* getLink() = 0; + virtual bool accept( PortBase* other ); + virtual bool connect( PortBase* other ) = 0; + virtual bool disconnect() = 0; + /// Returns a reflected (In <-> Out) port of the same type + virtual PortBase* reflect( Node* node, std::string name ) = 0; + /// Returns true if the port is an input port + virtual bool is_input() { return false; } + + /// Allows to get data stored at this port if it is an output port. + /// This method copy the data onto the given object + /// @params t The reference to store the data of this port + template + void getData( T& t ); + + /// Allows to get data stored at this port if it is an output port. + /// This method do not copy the data but gives a reference to the transmitted object. + /// TODO Verify the robustness of this + /// @params t The reference to store the data of this port + template + T& getData(); + + /// Check if this port is an output port, then takes a pointer to the data this port will point + /// to. + /// @param data The pointer to the data. + template + void setData( T* data ); + + virtual std::string getTypeName() = 0; +}; + +template +class PortOut : public PortBase +{ + private: + /// The data the port points to. + T* m_data { nullptr }; + + public: + using DataType = T; + /// Constructor. + /// @param name The name of the port. + /// @param node The pointer to the node associated with the port. + PortOut( const std::string& name, Node* node ); + /// Gets a reference to the data this ports points to. + T& getData(); + /// Takes a pointer to the data this port will point to. + /// @param data The pointer to the data. + void setData( T* data ); + /// Returns true if the pointer to the data is not null. + bool hasData() override; + /// Returns nullptr because this port is an out port. + PortBase* getLink() override; + /// Returns false because out ports can not accept connection. + /// @param o The other port to test the connection. + bool accept( PortBase* ) override; + /// Calls the connect(PortBase* o) function of the o node because out ports can not connect. + /// Also sets m_isLinked. + /// @param o The other port to connect. + bool connect( PortBase* o ) override; + /// Returns false because out ports can not disconnect. + bool disconnect() override; + /// Returns a portIn of the same type + PortBase* reflect( Node* node, std::string name ) override; + + std::string getTypeName() override; +}; + +/** + * PortIn is an Observable&> that notifies its observers at connect/disconnect event + * @tparam T + */ +template +class PortIn : public PortBase, + public Ra::Core::Utils::Observable&, bool> +{ + private: + /// A pointer to the out port this port is connected to. + PortOut* m_from = nullptr; + + public: + using DataType = T; + /// Constructor. + /// @param name The name of the port. + /// @param node The pointer to the node associated with the port. + PortIn( const std::string& name, Node* node ); + /// Gets the out port this port is connected to. + PortBase* getLink() override; + /// Gets a reference to the data pointed by the connected out port. + T& getData(); + /// Checks if there is not out port already connected and if the data types are the same. + /// @param o The other port to test the connection + bool accept( PortBase* other ) override; + /// Connects this in port and the other out port if there is no out port already connected and + /// if the data types are the same. Also sets m_isLinked. + /// @param o The other port to connect. + bool connect( PortBase* other ) override; + /// Disconnects this port if it is connected. + /// Also sets m_isLinked to false. + bool disconnect() override; + /// Returns a portOut of the same type + PortBase* reflect( Node* node, std::string name ) override; + /// Returns true if the port is an input port + bool is_input() override; + + std::string getTypeName() override; +}; + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl new file mode 100644 index 00000000000..ad88976b001 --- /dev/null +++ b/src/Dataflow/Core/Port.inl @@ -0,0 +1,208 @@ +#pragma once +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +inline PortBase::PortBase( const std::string& name, size_t type, Node* node ) : + m_name( name ), m_type( type ), m_node( node ) {} + +inline const std::string& PortBase::getName() { + return m_name; +} + +inline size_t PortBase::getType() { + return m_type; +} + +inline Node* PortBase::getNode() { + return m_node; +} + +inline bool PortBase::hasData() { + return false; +} + +inline bool PortBase::isLinked() { + return m_isLinked; +} + +inline bool PortBase::isLinkMandatory() { + return m_isLinkMandatory; +} + +inline void PortBase::mustBeLinked() { + m_isLinkMandatory = true; +} + +inline bool PortBase::accept( PortBase* other ) { + return m_type == other->getType(); +} + +template +PortOut::PortOut( const std::string& name, Node* node ) : + PortBase( name, typeid( T ).hash_code(), node ) {} + +template +T& PortOut::getData() { + return *m_data; +} + +template +void PortOut::setData( T* data ) { + m_data = data; +} + +template +bool PortOut::hasData() { + return m_data != nullptr; +} + +template +PortBase* PortOut::getLink() { + return nullptr; +} + +template +bool PortOut::accept( PortBase* ) { + return false; +} + +template +bool PortOut::connect( PortBase* o ) { + m_isLinked = o->connect( this ); + return m_isLinked; +} + +template +std::string PortOut::getTypeName() { + return Ra::Core::Utils::demangleType(); +} + +template +std::string PortIn::getTypeName() { + return Ra::Core::Utils::demangleType(); +} + +template +void PortBase::setData( T* data ) { + if ( !is_input() ) { + auto thisOut = dynamic_cast*>( this ); + if ( thisOut ) { + thisOut->setData( data ); + return; + } + } +#ifdef GRAPH_CALL_TRACE + if ( is_input() ) { + std::cout << "\e[41m\e[1mError, can't set data on the input port " << this->getName() + << "\e[0m" << std::endl; + } + else { + std::cout << "\e[41m\e[1mError, can't set data on the port of incompatible data type." + << this->getName() << "\e[0m" << std::endl; + } +#endif +} + +template +void PortBase::getData( T& t ) { + if ( !is_input() ) { + auto thisOut = dynamic_cast*>( this ); + if ( thisOut ) { t = thisOut->getData(); } + } +#ifdef GRAPH_CALL_TRACE + if ( is_input() ) { + std::cout << "\e[41m\e[1mError, can't get data on the input port " << this->getName() + << "\e[0m" << std::endl; + } + else { + std::cout << "\e[41m\e[1mError, can't get data on the port of incompatible data type " + << this->getName() << " (" << this->getTypeName() << " vs " + << RadiumAddons::Core::Utils::demang() << ")\e[0m" << std::endl; + } +#endif +} + +template +T& PortBase::getData() { + if ( !is_input() ) { + auto thisOut = dynamic_cast*>( this ); + if ( thisOut ) { return thisOut->getData(); } + } + std::cerr << "Could not call T& PortBase::getData() on an input port !!!\n"; + std::abort(); +} + +template +bool PortOut::disconnect() { + return false; +} + +template +PortBase* PortOut::reflect( Node* node, std::string name ) { + auto port = new PortIn( name, node ); + return port; +} + +/** + * PortIn is an Observable&> that notifies its observers at connect/disconnect event + * @tparam T + */ +template +PortIn::PortIn( const std::string& name, Node* node ) : + PortBase( name, typeid( T ).hash_code(), node ) {} + +template +PortBase* PortIn::getLink() { + return m_from; +} + +template +T& PortIn::getData() { + return m_from->getData(); +} + +template +bool PortIn::accept( PortBase* other ) { + if ( !m_from && ( other->getType() == getType() ) ) { return PortBase::accept( other ); } + return false; +} + +template +bool PortIn::connect( PortBase* other ) { + if ( accept( other ) ) { + m_from = dynamic_cast*>( other ); + m_isLinked = true; + // notify after connect + this->notify( getName(), *this, true ); + } + return m_isLinked; +} + +template +bool PortIn::disconnect() { + if ( m_isLinked ) { + // notify before disconnect + this->notify( getName(), *this, false ); + m_from = nullptr; + m_isLinked = false; + return true; + } + return false; +} +template +PortBase* PortIn::reflect( Node* node, std::string name ) { + return new PortOut( name, node ); +} + +template +bool PortIn::is_input() { + return true; +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake new file mode 100644 index 00000000000..da255b1b8ae --- /dev/null +++ b/src/Dataflow/Core/filelist.cmake @@ -0,0 +1,34 @@ +# ---------------------------------------------------- +# This file can be generated from a script: +# To do so, run "./generateFilelistForModule.sh Dataflow/Core" +# from ./scripts directory +# ---------------------------------------------------- + +set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp) + +set(dataflow_core_headers + DataflowGraph.hpp + EditableParameter.hpp + Enumerator.hpp + Node.hpp + NodeFactory.hpp + Nodes/CoreBuiltInsNodes.hpp + Nodes/Filters/FilterNode.hpp + Nodes/Sinks/SinkNode.hpp + Nodes/Sources/CoreDataSources.hpp + Nodes/Sources/SingleDataSourceNode.hpp + Port.hpp +) + +set(dataflow_core_inlines + DataflowGraph.inl + EditableParameter.inl + Enumerator.inl + Node.inl + NodeFactory.inl + Nodes/Filters/FilterNode.inl + Nodes/Sinks/SinkNode.inl + Nodes/Sources/CoreDataSources.inl + Nodes/Sources/SingleDataSourceNode.inl + Port.inl +) diff --git a/src/Dataflow/Core/pch.hpp b/src/Dataflow/Core/pch.hpp new file mode 100644 index 00000000000..6f70f09beec --- /dev/null +++ b/src/Dataflow/Core/pch.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/src/Dataflow/RaDataflow.hpp b/src/Dataflow/RaDataflow.hpp new file mode 100644 index 00000000000..02bec913fca --- /dev/null +++ b/src/Dataflow/RaDataflow.hpp @@ -0,0 +1,11 @@ +#pragma once +#include + +/// Defines the correct macro to export dll symbols. +#if defined RA_DATAFLOW_EXPORTS +# define RA_DATAFLOW_API DLL_EXPORT +#elif defined RA_DATAFLOW_STATIC +# define RA_DATAFLOW_API +#else +# define RA_DATAFLOW_API DLL_IMPORT +#endif From 5ea3a90209d3ef2d42b9b9e4d621afbf00171cc4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 8 Sep 2022 00:16:30 +0200 Subject: [PATCH 008/239] [examples][dataflow] add helloDataflow example --- examples/CMakeLists.txt | 1 + examples/DataflowExamples/CMakeLists.txt | 12 ++ .../HelloGraph/CMakeLists.txt | 46 ++++++++ examples/DataflowExamples/HelloGraph/main.cpp | 109 ++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 examples/DataflowExamples/CMakeLists.txt create mode 100644 examples/DataflowExamples/HelloGraph/CMakeLists.txt create mode 100644 examples/DataflowExamples/HelloGraph/main.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c17881c905b..8d69d7d9011 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -15,6 +15,7 @@ foreach( APP CoreExample CustomCameraManipulator + DataflowExamples DrawPrimitives EntityAnimation EnvMap diff --git a/examples/DataflowExamples/CMakeLists.txt b/examples/DataflowExamples/CMakeLists.txt new file mode 100644 index 00000000000..df56d9e0889 --- /dev/null +++ b/examples/DataflowExamples/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.16) +cmake_policy(SET CMP0042 NEW) + +project(DataflowExamples VERSION 1.0.0) + +add_custom_target(${PROJECT_NAME}) +add_custom_target(Install_${PROJECT_NAME}) +foreach(APP HelloGraph) + add_subdirectory(${APP}) + add_dependencies(${PROJECT_NAME} ${APP}) + add_dependencies(Install_${PROJECT_NAME} Install_${APP}) +endforeach() diff --git a/examples/DataflowExamples/HelloGraph/CMakeLists.txt b/examples/DataflowExamples/HelloGraph/CMakeLists.txt new file mode 100644 index 00000000000..2f6a4b64b9c --- /dev/null +++ b/examples/DataflowExamples/HelloGraph/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.6) +if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") + cmake_policy(SET CMP0071 NEW) +endif() +if(APPLE) + cmake_policy(SET CMP0042 NEW) +endif(APPLE) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(HelloGraph VERSION 0.0.1) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Set default install location to installed- folder in build dir we do not want to +# install to /usr by default +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX + "${CMAKE_CURRENT_BINARY_DIR}/installed-${CMAKE_CXX_COMPILER_ID}-${CMAKE_BUILD_TYPE}" + CACHE PATH "Install path prefix, prepended onto install directories." FORCE + ) + message(STATUS "Set install prefix to ${CMAKE_INSTALL_PREFIX}") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# /////////////////////////////// +find_package(Radium REQUIRED COMPONENTS Dataflow) + +# -------------------------------------------------------------------------------------------------- + +set(app_sources main.cpp) +# set(app_headers) + +# delete ${app_headers} +add_executable(${PROJECT_NAME} ${app_sources}) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + +target_link_libraries(${PROJECT_NAME} PUBLIC Radium::Dataflow) + +configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp new file mode 100644 index 00000000000..c5ac1c0bdc8 --- /dev/null +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +using namespace Ra::Dataflow::Core; + +/* ----------------------------------------------------------------------------------- */ +/** + * \brief Demonstrate how to use a graph to filter a collection + */ +int main( int argc, char* argv[] ) { + //! [Creating an empty graph] + DataflowGraph g { "helloGraph" }; + //! [Creating an empty graph] + + //! [Creating Nodes] + auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); + // non serializable node using a custom filter + auto filterNode = + new Filters::FilterNode( "Filter", []( Scalar x ) { return x > 0.5_ra; } ); + auto sinkNode = new Sinks::SinkNode>( "Sink" ); + //! [Creating Nodes] + + //! [Adding Nodes to the graph] + g.addNode( sourceNode ); + g.addNode( filterNode ); + g.addNode( sinkNode ); + //! [Adding Nodes to the graph] + + //! [Creating links between Nodes] + g.addLink( sourceNode, "to", filterNode, "in" ); + g.addLink( filterNode, "out", sinkNode, "from" ); + //! [Creating links between Nodes] + + //! [Inspect the graph interface : inputs and outputs port] + auto inputs = g.getAllDataSetters(); + std::cout << "Input ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\"" << portName << "\" accepting type " << portType << "\n"; + } + auto outputs = g.getAllDataGetters(); + std::cout << "Output ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; + } + //! [Inspect the graph interface : inputs and outputs port] + + //! [Verifing the graph can be compiled] + if ( !g.compile() ) { + std::cout << " compilation failed"; + return 1; + } + //! [Verifing the graph can be compiled] + + ///! [Configure the interface ports (input and output of the graph) + auto input = g.getDataSetter( "Source_to" ); + std::vector test; + input->setData( &test ); + auto output = g.getDataGetter( "Sink_from" ); + // The reference to the result will not be available before the first run + // auto& result = output->getData>(); + ///! [Configure the interface ports (input and output of the graph) + + //! [Initializing input variable to test the graph] + test.reserve( 10 ); + std::mt19937 gen( 0 ); + std::uniform_real_distribution<> dis( 0.0, 1.0 ); + // Fill the vector with random numbers between 0 and 1 + for ( int n = 0; n < test.capacity(); ++n ) { + test.push_back( dis( gen ) ); + } + + std::cout << "Input values : \n\t"; + for ( auto ord : test ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Initializing input variable to test the graph] + + //! [Execute the graph] + g.execute(); + // The reference to the result is now available + auto& result = output->getData>(); + //! [Execute the graph] + + //! [Print the output result] + std::cout << "Output values (reference): \n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Print the output result] + + //! [Modify input and rerun the graph] + // here, we of re-use the same interface objects (pointer for input, reference for output) + for ( int n = 0; n < 10; ++n ) { + test.push_back( dis( gen ) ); + } + g.execute(); + std::cout << "Output values after second execution: \n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Modify input and rerun the graph] + + return 0; +} From 665d27e26931cebb3d1ddb25baf80c3417416fe4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 17 Oct 2022 15:02:06 +0200 Subject: [PATCH 009/239] [examples][dataflow] add graph serialization example --- examples/DataflowExamples/CMakeLists.txt | 2 +- .../GraphSerialization/CMakeLists.txt | 46 +++++ .../GraphSerialization/main.cpp | 173 ++++++++++++++++++ 3 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 examples/DataflowExamples/GraphSerialization/CMakeLists.txt create mode 100644 examples/DataflowExamples/GraphSerialization/main.cpp diff --git a/examples/DataflowExamples/CMakeLists.txt b/examples/DataflowExamples/CMakeLists.txt index df56d9e0889..8f199a8063f 100644 --- a/examples/DataflowExamples/CMakeLists.txt +++ b/examples/DataflowExamples/CMakeLists.txt @@ -5,7 +5,7 @@ project(DataflowExamples VERSION 1.0.0) add_custom_target(${PROJECT_NAME}) add_custom_target(Install_${PROJECT_NAME}) -foreach(APP HelloGraph) +foreach(APP GraphSerialization HelloGraph) add_subdirectory(${APP}) add_dependencies(${PROJECT_NAME} ${APP}) add_dependencies(Install_${PROJECT_NAME} Install_${APP}) diff --git a/examples/DataflowExamples/GraphSerialization/CMakeLists.txt b/examples/DataflowExamples/GraphSerialization/CMakeLists.txt new file mode 100644 index 00000000000..98f9d1aa241 --- /dev/null +++ b/examples/DataflowExamples/GraphSerialization/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.6) +if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") + cmake_policy(SET CMP0071 NEW) +endif() +if(APPLE) + cmake_policy(SET CMP0042 NEW) +endif(APPLE) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(GraphSerialization VERSION 0.0.1) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Set default install location to installed- folder in build dir we do not want to +# install to /usr by default +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX + "${CMAKE_CURRENT_BINARY_DIR}/installed-${CMAKE_CXX_COMPILER_ID}-${CMAKE_BUILD_TYPE}" + CACHE PATH "Install path prefix, prepended onto install directories." FORCE + ) + message(STATUS "Set install prefix to ${CMAKE_INSTALL_PREFIX}") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# /////////////////////////////// +find_package(Radium REQUIRED COMPONENTS Dataflow) + +# -------------------------------------------------------------------------------------------------- + +set(app_sources main.cpp) +# set(app_headers) + +# delete ${app_headers} +add_executable(${PROJECT_NAME} ${app_sources}) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + +target_link_libraries(${PROJECT_NAME} PUBLIC Radium::Dataflow) + +configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp new file mode 100644 index 00000000000..58e0904959e --- /dev/null +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include + +using namespace Ra::Dataflow::Core; + +/* ----------------------------------------------------------------------------------- */ +/** + * \brief Demonstrate how serialize a graph with custom nodes. + */ +int main( int argc, char* argv[] ) { + + //! [Creating the factory for the custom nodes and add it to the nodes system] + // custom node type are either specialization of templated nodes or user-define nodes class + + // create the custom node factory + NodeFactorySet::mapped_type customFactory { + new NodeFactorySet::mapped_type::element_type( "ExampleCustomFactory" ) }; + + // add node creators to the factory + customFactory->registerNodeCreator>>( + Sources::SingleDataSourceNode>::getTypename() + "_", "Source" ); + customFactory->registerNodeCreator>( + Filters::FilterNode::getTypename() + "_", "Filters" ); + customFactory->registerNodeCreator>>( + Sinks::SinkNode>::getTypename() + "_", "Sink" ); + + // register the factory into the system to enable loading any graph that use these nodes + NodeFactoriesManager::registerFactory( customFactory ); + //! [Creating the factory for the custom node types and add it to the node system] + + { + //! [Creating an empty graph using the custom nodes factory] + DataflowGraph g { "Serialization example" }; + // Add to the graph the custom factory (built-in nodes are automatically managed) + g.addFactory( "ExampleCustomFactory", + NodeFactoriesManager::getFactory( "ExampleCustomFactory" ) ); + //! [Creating an empty graph using the custom nodes factory] + + //! [Creating Nodes] + auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); + // non serializable node using a custom filter + auto filterNode = + new Filters::FilterNode( "Filter", []( Scalar x ) { return x > 0.5_ra; } ); + auto sinkNode = new Sinks::SinkNode>( "Sink" ); + //! [Creating Nodes] + + //! [Adding Nodes to the graph] + g.addNode( sourceNode ); + g.addNode( filterNode ); + g.addNode( sinkNode ); + //! [Adding Nodes to the graph] + + //! [Creating links between Nodes] + g.addLink( sourceNode, "to", filterNode, "in" ); + g.addLink( filterNode, "out", sinkNode, "from" ); + //! [Creating links between Nodes] + + //! [Inspect the graph interface : inputs and outputs port] + auto inputs = g.getAllDataSetters(); + std::cout << "Input ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\"" << portName << "\" accepting type " << portType << "\n"; + } + auto outputs = g.getAllDataGetters(); + std::cout << "Output ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; + } + //! [Inspect the graph interface : inputs and outputs port] + + //! [Verifing the graph can be compiled] + if ( !g.compile() ) { + std::cout << " compilation failed"; + return 1; + } + //! [Verifing the graph can be compiled] + + //! [Serializing the graph] + g.saveToJson( "GraphSerializeExample.json" ); + //! [Serializing the graph] + } + + std::cout << "\t==**==**==*==**==*==**==*==**==*==**==*==**==*==**==\n"; + std::cout << "\t\tLoading and using the graph ...\n"; + std::cout << "\t==**==**==*==**==*==**==*==**==*==**==*==**==*==**==\n"; + + //! [Creating an empty graph and load it from a file] + DataflowGraph g1 { "" }; + g1.loadFromJson( "GraphSerializeExample.json" ); + //! [Creating an empty graph and load it from a file] + + //! [Inspect the graph interface : inputs and outputs port] + auto inputs_g1 = g1.getAllDataSetters(); + std::cout << "Input ports (" << inputs_g1.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs_g1 ) { + std::cout << "\t\"" << portName << "\" accepting type " << portType << "\n"; + } + auto outputs_g1 = g1.getAllDataGetters(); + std::cout << "Output ports (" << outputs_g1.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs_g1 ) { + std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; + } + //! [Inspect the graph interface : inputs and outputs port] + + //! [Verifing the graph can be compiled] + if ( !g1.compile() ) { + std::cout << "Compilation failed for the loaded graph"; + return 2; + } + //! [Verifing the graph can be compiled] + + //! [Creating input variable to test the graph] + std::vector test; + test.reserve( 10 ); + std::mt19937 gen( 0 ); + std::uniform_real_distribution<> dis( 0.0, 1.0 ); + // Fill the vector with random numbers between 0 and 1 + for ( int n = 0; n < test.capacity(); ++n ) { + test.push_back( dis( gen ) ); + } + + std::cout << "Input values : \n\t"; + for ( auto ord : test ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Creating input variable to test the graph] + + //! [setting the values processed by the graph] + auto input = g1.getDataSetter( "Source_to" ); + input->setData( &test ); + //! [setting the values processed by the graph] + + //! [Execute the graph] + std::cout << "Executing the loaded graph ...\n"; + g1.execute(); + //! [Execute the graph] + + //! [Print the output result] + auto output = g1.getDataGetter( "Sink_from" ); + std::vector result; + output->getData( result ); + std::cout << "Output values : \n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Print the output result] + + //! [Set the correct filter on the filter node] + auto filter = dynamic_cast*>( g1.getNode( "Filter" ) ); + if ( !filter ) { + std::cerr << "Unable to cast the filter to the right type\n"; + return 3; + } + filter->setFilterFunction( []( Scalar f ) { return f > 0.5_ra; } ); + //! [Set the correct filter on the filter node] + + //! [Execute the graph] + std::cout << "Executing the re-parameterized graph ...\n"; + g1.execute(); + output->getData( result ); + std::cout << "Output values : \n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Execute the graph] + + return 0; +} From 72fe1f40a6622f2ea8ce8311381861021f71ee5a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 17 Oct 2022 15:18:38 +0200 Subject: [PATCH 010/239] [dataflow-qtgui] add QtGui subcomponent --- src/Dataflow/CMakeLists.txt | 10 +- src/Dataflow/Config.cmake.in | 12 + src/Dataflow/QtGui/CMakeLists.txt | 71 +++ src/Dataflow/QtGui/Config.cmake.in | 41 ++ .../GraphEditor/ConnectionStatusData.hpp | 37 ++ .../QtGui/GraphEditor/GraphEditorView.cpp | 161 ++++++ .../QtGui/GraphEditor/GraphEditorView.hpp | 63 +++ .../QtGui/GraphEditor/NodeAdapterModel.cpp | 341 +++++++++++ .../QtGui/GraphEditor/NodeAdapterModel.hpp | 84 +++ .../QtGui/GraphEditor/WidgetFactory.cpp | 364 ++++++++++++ .../QtGui/GraphEditor/WidgetFactory.hpp | 83 +++ .../QtGui/QtNodeEditor/CMakeLists.txt | 153 +++++ src/Dataflow/QtGui/QtNodeEditor/LICENSE | 28 + src/Dataflow/QtGui/QtNodeEditor/README.md | 41 ++ .../cmake/NodeEditorConfig.cmake.in | 11 + .../QtNodeEditor/cmake/QtFunctions.cmake | 59 ++ .../QtNodeEditor/include/nodes/Connection | 1 + .../include/nodes/ConnectionStyle | 1 + .../include/nodes/DataModelRegistry | 1 + .../QtNodeEditor/include/nodes/FlowScene | 1 + .../QtGui/QtNodeEditor/include/nodes/FlowView | 1 + .../QtNodeEditor/include/nodes/FlowViewStyle | 1 + .../QtGui/QtNodeEditor/include/nodes/Node | 1 + .../QtGui/QtNodeEditor/include/nodes/NodeData | 1 + .../QtNodeEditor/include/nodes/NodeDataModel | 1 + .../QtNodeEditor/include/nodes/NodeGeometry | 1 + .../include/nodes/NodePainterDelegate | 1 + .../QtNodeEditor/include/nodes/NodeState | 1 + .../QtNodeEditor/include/nodes/NodeStyle | 1 + .../include/nodes/StyleCollection | 1 + .../QtNodeEditor/include/nodes/TypeConverter | 1 + .../include/nodes/internal/Compiler.hpp | 40 ++ .../include/nodes/internal/Connection.hpp | 126 +++++ .../nodes/internal/ConnectionGeometry.hpp | 47 ++ .../internal/ConnectionGraphicsObject.hpp | 66 +++ .../nodes/internal/ConnectionState.hpp | 48 ++ .../nodes/internal/ConnectionStyle.hpp | 54 ++ .../nodes/internal/DataModelRegistry.hpp | 162 ++++++ .../include/nodes/internal/Export.hpp | 48 ++ .../include/nodes/internal/FlowScene.hpp | 163 ++++++ .../include/nodes/internal/FlowView.hpp | 63 +++ .../include/nodes/internal/FlowViewStyle.hpp | 32 ++ .../include/nodes/internal/Node.hpp | 96 ++++ .../include/nodes/internal/NodeData.hpp | 29 + .../include/nodes/internal/NodeDataModel.hpp | 119 ++++ .../include/nodes/internal/NodeGeometry.hpp | 128 +++++ .../nodes/internal/NodeGraphicsObject.hpp | 83 +++ .../nodes/internal/NodePainterDelegate.hpp | 21 + .../include/nodes/internal/NodeState.hpp | 71 +++ .../include/nodes/internal/NodeStyle.hpp | 51 ++ .../nodes/internal/OperatingSystem.hpp | 49 ++ .../include/nodes/internal/PortType.hpp | 48 ++ .../include/nodes/internal/QStringStdHash.hpp | 21 + .../include/nodes/internal/QUuidStdHash.hpp | 13 + .../include/nodes/internal/Serializable.hpp | 16 + .../include/nodes/internal/Style.hpp | 20 + .../nodes/internal/StyleCollection.hpp | 42 ++ .../include/nodes/internal/TypeConverter.hpp | 18 + .../include/nodes/internal/memory.hpp | 23 + .../QtNodeEditor/resources/DefaultStyle.json | 42 ++ .../QtGui/QtNodeEditor/resources/convert.png | Bin 0 -> 10203 bytes .../QtNodeEditor/resources/resources.qrc | 6 + .../QtGui/QtNodeEditor/src/Connection.cpp | 316 +++++++++++ .../QtNodeEditor/src/ConnectionBlurEffect.cpp | 21 + .../QtNodeEditor/src/ConnectionBlurEffect.hpp | 19 + .../QtNodeEditor/src/ConnectionGeometry.cpp | 103 ++++ .../src/ConnectionGraphicsObject.cpp | 187 ++++++ .../QtNodeEditor/src/ConnectionPainter.cpp | 256 +++++++++ .../QtNodeEditor/src/ConnectionPainter.hpp | 18 + .../QtNodeEditor/src/ConnectionState.cpp | 32 ++ .../QtNodeEditor/src/ConnectionStyle.cpp | 173 ++++++ .../QtNodeEditor/src/DataModelRegistry.cpp | 42 ++ .../QtGui/QtNodeEditor/src/FlowScene.cpp | 532 ++++++++++++++++++ .../QtGui/QtNodeEditor/src/FlowView.cpp | 316 +++++++++++ .../QtGui/QtNodeEditor/src/FlowViewStyle.cpp | 94 ++++ src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp | 179 ++++++ .../src/NodeConnectionInteraction.cpp | 180 ++++++ .../src/NodeConnectionInteraction.hpp | 59 ++ .../QtGui/QtNodeEditor/src/NodeDataModel.cpp | 26 + .../QtGui/QtNodeEditor/src/NodeGeometry.cpp | 279 +++++++++ .../QtNodeEditor/src/NodeGraphicsObject.cpp | 305 ++++++++++ .../QtGui/QtNodeEditor/src/NodePainter.cpp | 341 +++++++++++ .../QtGui/QtNodeEditor/src/NodePainter.hpp | 57 ++ .../QtGui/QtNodeEditor/src/NodeState.cpp | 83 +++ .../QtGui/QtNodeEditor/src/NodeStyle.cpp | 119 ++++ .../QtGui/QtNodeEditor/src/Properties.cpp | 7 + .../QtGui/QtNodeEditor/src/Properties.hpp | 36 ++ .../QtNodeEditor/src/StyleCollection.cpp | 36 ++ src/Dataflow/QtGui/filelist.cmake | 13 + 89 files changed, 7143 insertions(+), 5 deletions(-) create mode 100644 src/Dataflow/QtGui/CMakeLists.txt create mode 100644 src/Dataflow/QtGui/Config.cmake.in create mode 100644 src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp create mode 100644 src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp create mode 100644 src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp create mode 100644 src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp create mode 100644 src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp create mode 100644 src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp create mode 100644 src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt create mode 100644 src/Dataflow/QtGui/QtNodeEditor/LICENSE create mode 100644 src/Dataflow/QtGui/QtNodeEditor/README.md create mode 100644 src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in create mode 100644 src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json create mode 100644 src/Dataflow/QtGui/QtNodeEditor/resources/convert.png create mode 100644 src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp create mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp create mode 100644 src/Dataflow/QtGui/filelist.cmake diff --git a/src/Dataflow/CMakeLists.txt b/src/Dataflow/CMakeLists.txt index 508b3ec96f6..6c7026c3014 100644 --- a/src/Dataflow/CMakeLists.txt +++ b/src/Dataflow/CMakeLists.txt @@ -18,12 +18,12 @@ if(OFF) add_dependencies(${ra_dataflow_target} DataflowRendering) target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) endif() +endif() - if(RADIUM_GENERATE_LIB_GUI) - add_subdirectory(QtGui) - add_dependencies(${ra_dataflow_target} DataflowQtGui) - target_link_libraries(${ra_dataflow_target} INTERFACE DataflowQtGui) - endif() +if(RADIUM_GENERATE_LIB_GUI) + add_subdirectory(QtGui) + add_dependencies(${ra_dataflow_target} DataflowQtGui) + target_link_libraries(${ra_dataflow_target} INTERFACE DataflowQtGui) endif() message(STATUS "Configuring library ${ra_dataflow_target} with standard settings") diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in index da59da4882e..35e71aef4de 100644 --- a/src/Dataflow/Config.cmake.in +++ b/src/Dataflow/Config.cmake.in @@ -14,6 +14,18 @@ if (Dataflow_FOUND AND NOT TARGET Dataflow) endif() endif() + if(NOT DataflowQtGui_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowQtGui/RadiumDataflowQtGuiConfig.cmake") + set(DataflowQtGui_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../DataflowQtGui/RadiumDataflowQtGuiConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowQtGui not found") + set(Configure_Dataflow OFF) + endif() + endif() + + if (OFF) if(@RADIUM_GENERATE_LIB_ENGINE@ AND NOT Engine_FOUND) if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake") diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt new file mode 100644 index 00000000000..f73933d6ad7 --- /dev/null +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -0,0 +1,71 @@ +set(ra_dataflowqtgui_target DataflowQtGui) +list(APPEND CMAKE_MESSAGE_INDENT "[${ra_dataflowqtgui_target}] ") + +project(${ra_dataflowqtgui_target} LANGUAGES CXX VERSION ${Radium_VERSION}) + +add_subdirectory(QtNodeEditor) + +include(filelist.cmake) + +# Qt utility functions +include(QtFunctions) + +# Find Qt packages +find_qt_package(COMPONENTS Core Widgets OpenGL Xml REQUIRED) +set(QT_DEFAULT_MAJOR_VERSION ${QT_DEFAULT_MAJOR_VERSION} PARENT_SCOPE) +set(Qt_LIBRARIES Qt::Core Qt::Widgets Qt::OpenGL Qt::Xml) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +qt_wrap_ui(gui_uis ${gui_uis}) + +# configure library +find_package(PowerSlider REQUIRED) + +add_library( + ${ra_dataflowqtgui_target} SHARED ${dataflow_qtgui_sources} ${dataflow_qtgui_headers} + ${dataflow_qtgui_inlines} +) + +# This one should be extracted directly from external project properties. +target_compile_definitions(${ra_dataflowqtgui_target} PUBLIC THIS_A_REMINDER) + +target_compile_options(${ra_dataflowqtgui_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) + +target_include_directories( + ${ra_dataflowqtgui_target} + PUBLIC $ + $ +) +target_include_directories( + ${ra_dataflowqtgui_target} PUBLIC $ + $ + PRIVATE ${CMAKE_CURRENT_BINARY_DIR} +) +add_dependencies(${ra_dataflowqtgui_target} DataflowCore Gui) +target_link_libraries( + ${ra_dataflowqtgui_target} PUBLIC ${Qt_LIBRARIES} DataflowCore Gui PUBLIC NodeEditor::NodeEditor + PRIVATE PowerSlider::PowerSlider +) + +message(STATUS "Configuring library ${ra_dataflowqtgui_target} with standard settings") +configure_radium_target(${ra_dataflowqtgui_target}) +# configure the library only. The package is a sub-package and should be configured independently +configure_radium_library( + TARGET ${ra_dataflowqtgui_target} COMPONENT TARGET_DIR "Dataflow/QtGui" + FILES "${dataflow_qtgui_headers};${dataflow_qtgui_inlines}" +) +# Generate cmake package +configure_radium_package( + NAME ${ra_dataflowqtgui_target} PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in + PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowqtgui_target}" NAME_PREFIX "Radium" +) + +set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowqtgui_target} PARENT_SCOPE) + +if(RADIUM_ENABLE_PCH) + target_precompile_headers(${ra_dataflowqtgui_target} PRIVATE pch.hpp) +endif() + +list(REMOVE_AT CMAKE_MESSAGE_INDENT -1) diff --git a/src/Dataflow/QtGui/Config.cmake.in b/src/Dataflow/QtGui/Config.cmake.in new file mode 100644 index 00000000000..22554130efa --- /dev/null +++ b/src/Dataflow/QtGui/Config.cmake.in @@ -0,0 +1,41 @@ +# -------------- Configuration of the Radium DataflowCore targets and definitions ----------------------- +# Setup Engine and check for dependencies + +if (DataflowQtGui_FOUND AND NOT TARGET DataflowQtGui) + set(Configure_DataflowQtGui ON) + # verify dependencies + if(NOT DataflowCore_FOUND) + # if in source dir + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake") + set(Core_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake) + else() + # if in install dir + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake") + set(Core_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Engine: dependency DataflowCore not found") + set(Configure_Engine OFF) + endif() + endif() + endif() + if(NOT Gui_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake") + set(Gui_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::PluginBase: dependency Gui not found") + set(Configure_PluginBase OFF) + endif() + endif() +endif() + +if(Configure_DataflowQtGui) + if(NOT TARGET NodeEditor) + find_dependency(NodeEditor REQUIRED) + endif() + include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/QtGui/DataflowQtGuiTargets.cmake") +endif() diff --git a/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp b/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp new file mode 100644 index 00000000000..7b9ae6444a1 --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp @@ -0,0 +1,37 @@ +#pragma once +#include + +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API ConnectionStatusData : public QtNodes::NodeData +{ + public: + ConnectionStatusData( Node* node, const std::string& outputName ) : + m_node { node }, m_outputName { outputName } {} + + QtNodes::NodeDataType type() const override { + return QtNodes::NodeDataType { "connection", "connectionStatus" }; + } + + Node* getNode() { return m_node; } + std::string getOutputName() { return m_outputName; } + + private: + Node* m_node { nullptr }; + std::string m_outputName; +}; + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp new file mode 100644 index 00000000000..7d1d567a944 --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -0,0 +1,161 @@ +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { + +GraphEditorView::GraphEditorView( QWidget* parent ) : QWidget( parent, Qt::Window ) { + QtNodes::ConnectionStyle::setConnectionStyle( + R"( + { + "ConnectionStyle": { + "UseDataDefinedColors": true + } + } + )" ); + + QVBoxLayout* l = new QVBoxLayout( this ); + + scene = new QtNodes::FlowScene( l ); + view = new QtNodes::FlowView( scene ); + + l->addWidget( view ); + l->setContentsMargins( 0, 0, 0, 0 ); + l->setSpacing( 0 ); + + // Create widgets + WidgetFactory::initializeWidgetFactory(); + + connectAll(); +} + +GraphEditorView::~GraphEditorView() { + disconnectAll(); +} + +void GraphEditorView::disconnectAll() { + for ( size_t i = 0; i < connections.size(); i++ ) { + QObject::disconnect( connections[i] ); + } + connections.clear(); +} + +void GraphEditorView::connectAll() { + + // This one is only here to allow modifying node parameters through the embedded widget + // TODO, find another way to do this. + connections.push_back( QObject::connect( + scene, &QtNodes::FlowScene::nodeHoverLeft, [this]() { emit needUpdate(); } ) ); + + connections.push_back( QObject::connect( + scene, &QtNodes::FlowScene::nodePlaced, [this]() { emit needUpdate(); } ) ); + connections.push_back( QObject::connect( + scene, &QtNodes::FlowScene::nodeDeleted, [this]() { emit needUpdate(); } ) ); + connections.push_back( QObject::connect( + scene, &QtNodes::FlowScene::connectionCreated, [this]() { emit needUpdate(); } ) ); + connections.push_back( QObject::connect( + scene, &QtNodes::FlowScene::connectionDeleted, [this]() { emit needUpdate(); } ) ); + connections.push_back( QObject::connect( + scene, &QtNodes::FlowScene::nodeMoved, []( QtNodes::Node& n, const QPointF& ) { + QJsonObject obj; + obj["x"] = n.nodeGraphicsObject().pos().x(); + obj["y"] = n.nodeGraphicsObject().pos().y(); + QJsonObject nodeJson; + nodeJson["position"] = obj; + n.nodeDataModel()->addMetaData( nodeJson ); + } ) ); +} + +void GraphEditorView::buildAdapterRegistry( const NodeFactorySet& factories ) { + m_editorRegistry.reset( new QtNodes::DataModelRegistry ); + for ( const auto& [factoryName, factory] : factories ) { + for ( const auto& [typeName, creator] : factory->getFactoryMap() ) { + auto f = creator.first; + m_editorRegistry->registerModel( + typeName.c_str(), + [f, this]() -> std::unique_ptr { + nlohmann::json data; + auto node = f( data ); + this->m_dataflowGraph->addNode( node ); + return std::make_unique( this->m_dataflowGraph, node ); + }, + creator.second.c_str() ); + } + } + scene->setRegistry( m_editorRegistry ); +} + +DataflowGraph* GraphEditorView::editedGraph() { + return m_dataflowGraph; +} + +void GraphEditorView::editGraph( DataflowGraph* g ) { + // Disconnect all event and clear the previous graph + disconnectAll(); + scene->clearScene(); + + // Setup the graph to edit + m_dataflowGraph = g; + if ( m_dataflowGraph ) { + buildAdapterRegistry( *( m_dataflowGraph->getNodeFactories() ) ); + + const auto& nodes = *( m_dataflowGraph->getNodes() ); + // inserting nodes + for ( const auto& n : nodes ) { + scene->importNode( std::make_unique( m_dataflowGraph, n.get() ) ); + } + // inserting connections + for ( const auto& n : nodes ) { + int numPort = 0; + for ( const auto& input : n->getInputs() ) { + if ( input->isLinked() ) { + auto portOut = input->getLink(); + auto nodeOut = portOut->getNode(); + int outPortIndex = 0; + for ( const auto& p : nodeOut->getOutputs() ) { + if ( p.get() == portOut ) { break; } + outPortIndex++; + } + scene->importConnection( + nodeOut->getUuid().c_str(), outPortIndex, n->getUuid().c_str(), numPort ); + } + numPort++; + } + } + scene->setSceneName( m_dataflowGraph->getInstanceName().c_str() ); + } + else { + scene->setSceneName( "untitled" ); + } + scene->iterateOverNodes( []( QtNodes::Node* n ) { n->onNodeSizeUpdated(); } ); + + // view->fitInView( view->sceneRect(), Qt::KeepAspectRatio); + // view->resetTransform(); + view->ensureVisible( view->sceneRect() ); + view->centerOn( view->sceneRect().center() ); + // Re-connect events + connectAll(); +} + +// Find the way to see all the scene in the editor (or, at leas 75% of the scene) +void GraphEditorView::resizeEvent( QResizeEvent* ) { + // view->resetTransform(); + view->ensureVisible( view->sceneRect() ); + view->centerOn( view->sceneRect().center() ); +} + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp new file mode 100644 index 00000000000..f98f1c696b8 --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp @@ -0,0 +1,63 @@ +#pragma once +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +class DataflowGraph; +class NodeFactorySet; +} // namespace Core + +/** + * + */ +namespace QtGui { +namespace GraphEditor { + +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API GraphEditorView : public QWidget +{ + Q_OBJECT + public: + explicit GraphEditorView( QWidget* parent ); + ~GraphEditorView(); + void disconnectAll(); + void connectAll(); + + /// Fill the editor with the existing nodes in the graph + void editGraph( DataflowGraph* g ); + + DataflowGraph* editedGraph(); + + Q_SIGNALS: + void needUpdate(); + + protected: + void resizeEvent( QResizeEvent* event ) override; + + private: + std::vector connections; + + // TODO, put all these as private data + QtNodes::FlowScene* scene { nullptr }; + QtNodes::FlowView* view { nullptr }; + + /// Build the registries from the graph's NodeFactory + void buildAdapterRegistry( const NodeFactorySet& factory ); + + DataflowGraph* m_dataflowGraph { nullptr }; + + /// Node creator registry to be used when creating a node in the editor + std::shared_ptr m_editorRegistry; +}; + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp new file mode 100644 index 00000000000..195973556e7 --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -0,0 +1,341 @@ +#include + +#include + +#include + +#include +#include +#include +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { + +namespace NodeDataModelTools { +/** + * Construct the widget needed to edit all the EditableParameter of the given node + * @param node The node to decorate + * @return the widget (of type RadiumAddons::Gui::Widgets::ControlPanel) exposing the parameters + */ +QWidget* getWidget( Node* node ); + +/** + * Update the state of the widget to reflect the ones of the node. + * @param node + * @param widget + */ +void updateWidget( Node* node, QWidget* widget ); + +/** + * Convert a QJsonObject to a nlohmann::json + */ +void QJsonObjectToNlohmannObject( const QJsonObject& p, nlohmann::json& data ); + +/** + * Convert a nlohmann::json to a QJsonObject + */ +void NlohmannObjectToQJsonObject( const nlohmann::json& data, QJsonObject& p ); +} // namespace NodeDataModelTools + +using namespace Ra::Dataflow::Core; +using namespace Ra::Gui::Widgets; + +NodeAdapterModel::NodeAdapterModel( DataflowGraph* graph, Node* n ) : + m_node { n }, m_dataflowGraph { graph } { + m_inputsConnected.resize( m_node->getInputs().size() ); + m_widget = NodeDataModelTools::getWidget( m_node ); + NodeDataModelTools::updateWidget( m_node, m_widget ); + checkConnections(); +} + +void NodeAdapterModel::checkConnections() const { + int errors = 0; + for ( size_t i = 0; i < m_inputsConnected.size(); i++ ) { + if ( !m_inputsConnected[i] && m_node->getInputs()[i]->isLinkMandatory() ) { errors++; } + } + + if ( errors == 0 ) { + m_validationState = QtNodes::NodeValidationState::Valid; + m_validationError = QString( "" ); + } + else { + if ( errors > 1 ) { + m_validationError = QString( + std::string( std::to_string( errors ) + " mandatory ports are not linked (*)." ) + .c_str() ); + } + else { + m_validationError = "1 mandatory port is not linked (*)."; + } + + m_validationState = QtNodes::NodeValidationState::Error; + } +} + +unsigned int NodeAdapterModel::nPorts( QtNodes::PortType portType ) const { + unsigned int result = 1; + + switch ( portType ) { + case QtNodes::PortType::In: { + result = m_node->getInputs().size(); + break; + } + + case QtNodes::PortType::Out: { + result = m_node->getOutputs().size(); + break; + } + + default: { + break; + } + } + + return result; +} + +QtNodes::NodeDataType NodeAdapterModel::dataType( QtNodes::PortType portType, + QtNodes::PortIndex portIndex ) const { + QtNodes::NodeDataType result { "incorrect", "incorrect" }; + + switch ( portType ) { + case QtNodes::PortType::In: { + std::string mandatory = ( m_node->getInputs()[portIndex]->isLinkMandatory() ) ? "*" : ""; + return IOToDataType( m_node->getInputs()[portIndex]->getType(), + m_node->getInputs()[portIndex]->getName() + mandatory ); + } + + case QtNodes::PortType::Out: { + return IOToDataType( m_node->getOutputs()[portIndex]->getType(), + m_node->getOutputs()[portIndex]->getName() ); + } + default: + break; + } + + checkConnections(); + + return result; +} + +std::shared_ptr NodeAdapterModel::outData( QtNodes::PortIndex port ) { + return std::make_shared( m_node, m_node->getOutputs()[port]->getName() ); +} + +void NodeAdapterModel::setInData( std::shared_ptr data, int port ) { + auto connectionData = dynamic_cast( data.get() ); + if ( connectionData ) { + // Add connection + m_dataflowGraph->addLink( connectionData->getNode(), + connectionData->getOutputName(), + m_node, + m_node->getInputs()[port]->getName() ); + m_inputsConnected[port] = true; + } + else { + // Remove connection + m_dataflowGraph->removeLink( m_node, m_node->getInputs()[port]->getName() ); + m_inputsConnected[port] = false; + } + checkConnections(); + m_dataflowGraph->m_recompile = true; +} + +QtNodes::NodeDataType NodeAdapterModel::IOToDataType( size_t hashType, + const std::string& ioName ) const { + return QtNodes::NodeDataType { std::to_string( hashType ).c_str(), ioName.c_str() }; +} + +void NodeAdapterModel::updateState() { + int i = 0; + for ( const auto& in : m_node->getInputs() ) { + if ( in->isLinked() ) { m_inputsConnected[i] = true; } + i++; + } + checkConnections(); +} + +void NodeAdapterModel::addMetaData( QJsonObject& json ) { + nlohmann::json data; + NodeDataModelTools::QJsonObjectToNlohmannObject( json, data ); + m_node->addJsonMetaData( data ); +} + +NodeAdapterModel::~NodeAdapterModel() { + if ( m_dataflowGraph->removeNode( m_node ) ) { m_dataflowGraph->m_recompile = true; } +} + +QJsonObject NodeAdapterModel::save() const { + QJsonObject o; // = QtNodes::NodeDataModel::save(); + nlohmann::json nodeData; + m_node->toJson( nodeData ); + NodeDataModelTools::NlohmannObjectToQJsonObject( nodeData, o ); + return o; +} + +void NodeAdapterModel::restore( QJsonObject const& p ) { + // QtNodes::NodeDataModel::restore( p ); + // 1 - convert the QJsonObject to nlohmann::json + nlohmann::json nodeData; + NodeDataModelTools::QJsonObjectToNlohmannObject( p, nodeData ); + // 2 - call fromjson on the node using this json object + m_node->fromJson( nodeData ); + // 3 - update the widget according to the editable parameters + NodeDataModelTools::updateWidget( m_node, m_widget ); +} + +namespace NodeDataModelTools { + +QWidget* getWidget( Node* node ) { + if ( node->getEditableParameters().size() == 0 ) { return nullptr; } + + auto controlPanel = new ControlPanel( "Editable parameters", false, nullptr ); + controlPanel->beginLayout( QBoxLayout::TopToBottom ); + for ( size_t i = 0; i < node->getEditableParameters().size(); i++ ) { + auto edtParam = node->getEditableParameters()[i].get(); + QWidget* newWidget = WidgetFactory::createWidget( edtParam ); + if ( newWidget ) { + newWidget->setParent( controlPanel ); + newWidget->setObjectName( edtParam->m_name.c_str() ); + controlPanel->addLabel( edtParam->m_name ); + controlPanel->addWidget( newWidget ); + + if ( i != node->getEditableParameters().size() - 1 ) { controlPanel->addSeparator(); } + } + } + controlPanel->endLayout(); + controlPanel->setVisible( true ); + + return controlPanel; +} + +void updateWidget( Node* node, QWidget* widget ) { + if ( node->getEditableParameters().size() == 0 ) { return; } + for ( size_t i = 0; i < node->getEditableParameters().size(); i++ ) { + auto edtParam = node->getEditableParameters()[i].get(); + if ( !WidgetFactory::updateWidget( widget, edtParam ) ) { + std::cerr << "Unable to update parameter " << edtParam->m_name << "\n"; + } + } +} + +/** Convert from Qt::Json to nlohmann::json */ +void QJsonEntryToNlohmannEntry( const QString& key, + const QJsonValue& value, + nlohmann::json& data ) { + switch ( value.type() ) { + case QJsonValue::Bool: + data[key.toStdString()] = value.toBool(); + break; + case QJsonValue::Double: + // as there is no QJsonValue::Int, manage explicitely keys with int value :( + // TODO find a better way to do that ... + // type is a specific entry of envmapdatasource + if ( key.compare( "type" ) == 0 ) { data[key.toStdString()] = int( value.toDouble() ); } + else { + data[key.toStdString()] = Scalar( value.toDouble() ); + } + + break; + case QJsonValue::String: + data[key.toStdString()] = value.toString().toStdString(); + break; + case QJsonValue::Array: { + auto jsArray = value.toArray(); + switch ( jsArray.first().type() ) { + case QJsonValue::Double: { + std::vector array; + for ( auto v : jsArray ) { + array.push_back( Scalar( v.toDouble() ) ); + } + data[key.toStdString()] = array; + break; + } + default: + LOG( Ra::Core::Utils::logERROR ) + << "Only Scalar arrays are supported for Json conversion."; + } + } break; + default: + LOG( Ra::Core::Utils::logERROR ) << "QJson to nlohmann::json : QtJson value type " + << value.type() << " is not suported."; + } +} + +void NlohmannEntryToQJsonEntry( const nlohmann::json& data, QJsonValue& value ) { + switch ( data.type() ) { + case nlohmann::detail::value_t::boolean: { + auto v = data.get(); + value = v; + } break; + case nlohmann::detail::value_t::number_float: { + auto v = double( data.get() ); + value = v; + } break; + case nlohmann::detail::value_t::number_integer: { + auto v = data.get(); + value = v; + } break; + case nlohmann::detail::value_t::number_unsigned: { + auto v = data.get(); + value = int( v ); + } break; + case nlohmann::detail::value_t::string: { + auto v = data.get(); + value = v.c_str(); + } break; + case nlohmann::detail::value_t::array: { + QJsonArray a; + QJsonValue v; + for ( auto& x : data.items() ) { + NlohmannEntryToQJsonEntry( x.value(), v ); + a.append( v ); + } + value = a; + } break; + default: + LOG( Ra::Core::Utils::logERROR ) << "nlohmann::json to QJson : nlohmann json type " + << int( data.type() ) << " is not supported."; + } +} + +void QJsonObjectToNlohmannObject( const QJsonObject& p, nlohmann::json& data ) { + for ( const auto& key : p.keys() ) { + auto value = p.value( key ); + if ( value.isObject() ) { + nlohmann::json j; + QJsonObjectToNlohmannObject( value.toObject(), j ); + data[key.toStdString()] = j; + } + else { + QJsonEntryToNlohmannEntry( key, value, data ); + } + } +} + +void NlohmannObjectToQJsonObject( const nlohmann::json& data, QJsonObject& p ) { + for ( const auto& [key, value] : data.items() ) { + if ( value.is_object() ) { + QJsonObject o; + NlohmannObjectToQJsonObject( data[key], o ); + p.insert( key.c_str(), o ); + } + else { + QJsonValue v; + NlohmannEntryToQJsonEntry( value, v ); + p.insert( key.c_str(), v ); + } + } +} + +} // namespace NodeDataModelTools + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp new file mode 100644 index 00000000000..ecacf28128b --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp @@ -0,0 +1,84 @@ +#pragma once +#include + +#include + +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { + +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API NodeAdapterModel : public QtNodes::NodeDataModel +{ + public: + NodeAdapterModel( DataflowGraph* graph, Node* n ); + NodeAdapterModel() = delete; + NodeAdapterModel( const NodeAdapterModel& ) = delete; + NodeAdapterModel( NodeAdapterModel&& ) = delete; + NodeAdapterModel& operator=( const NodeAdapterModel& ) = delete; + NodeAdapterModel& operator=( NodeAdapterModel&& ) = delete; + ~NodeAdapterModel() override; + + public: + QString caption() const override { return m_node->getTypeName().c_str(); } + + bool captionVisible() const override { return true; } + + QString name() const override { return m_node->getTypeName().c_str(); } + + QString uuid() const override { return m_node->getUuid().c_str(); } + + bool isDeletable() override { return m_node->isDeletable(); } + + void updateState() override; + + void addMetaData( QJsonObject& json ) override; + + private: + QtNodes::NodeDataType IOToDataType( size_t hashType, const std::string& ioName ) const; + + void checkConnections() const; + + public: + unsigned int nPorts( QtNodes::PortType portType ) const override; + + QtNodes::NodeDataType dataType( QtNodes::PortType portType, + QtNodes::PortIndex portIndex ) const override; + + std::shared_ptr outData( QtNodes::PortIndex port ) override; + + void setInData( std::shared_ptr data, int port ) override; + + QtNodes::NodeValidationState validationState() const override { return m_validationState; } + + QString validationMessage() const override { return m_validationError; } + + QWidget* embeddedWidget() override { return m_widget; } + + private: + Node* m_node; + DataflowGraph* m_dataflowGraph { nullptr }; + + QWidget* m_widget { nullptr }; + + std::vector m_inputsConnected; + mutable QtNodes::NodeValidationState m_validationState = QtNodes::NodeValidationState::Valid; + mutable QString m_validationError = QString( "" ); + + public: + QJsonObject save() const override; + void restore( QJsonObject const& p ) override; +}; + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp new file mode 100644 index 00000000000..23a8ac4af9d --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -0,0 +1,364 @@ +#include + +#include + +#include +#include + +// #include + +#include +#include +// #include +// #include + +#include +#include +#include +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { + +using namespace Ra::Dataflow::Core; +using namespace Ra::Gui::Widgets; + +using namespace Ra::Engine::Data; + +namespace WidgetFactory { +using WidgetFunctionPair = std::pair; + +std::unordered_map widgetsfunctions; + +void registerWidgetInternal( size_t hashedType, + WidgetCreatorFunc widgetCreator, + WidgetUpdaterFunc widgetUpdater ) { + if ( widgetsfunctions.find( hashedType ) == widgetsfunctions.end() ) { + widgetsfunctions[hashedType] = { std::move( widgetCreator ), std::move( widgetUpdater ) }; + } + else { + std::cerr + << "WidgetFactory: trying to add an already existing widget builder for hashed type " + << hashedType << "." << std::endl; + } +} + +QWidget* createWidget( EditableParameterBase* editableParameter ) { + if ( widgetsfunctions.find( editableParameter->m_hashedType ) != widgetsfunctions.end() ) { + return widgetsfunctions[editableParameter->m_hashedType].first( editableParameter ); + } + else { + std::cerr << "WidgetFactory: no defined widget builder for hashed type " + << editableParameter->m_hashedType << "." << std::endl; + } + return nullptr; +} + +bool updateWidget( QWidget* widget, EditableParameterBase* editableParameter ) { + if ( widgetsfunctions.find( editableParameter->m_hashedType ) != widgetsfunctions.end() ) { + return widgetsfunctions[editableParameter->m_hashedType].second( widget, + editableParameter ); + } + else { + std::cerr << "WidgetFactory: no defined widget updater for hashed type " + << editableParameter->m_hashedType << "." << std::endl; + } + return false; +} + +void initializeWidgetFactory() { + /* + * Environment map "edition" widget + */ + WidgetFactory::registerWidget>( + []( EditableParameterBase* editableParameter ) { + auto editable = dynamic_cast>*>( + editableParameter ); + + auto controlPanel = new ControlPanel( editable->m_name, false ); + auto envmpClbck = [editable, controlPanel]( const std::string& files ) { + if ( files.empty() ) { editable->m_data = nullptr; } + else { + // for now, only skyboxes are managed + editable->m_data = std::make_shared( files, true ); + auto slider = controlPanel->findChild( "strength" ); + if ( slider ) { editable->m_data->setStrength( slider->value() / 100. ); } + } + }; + // TODO : display the name of the envmap image somewhere + controlPanel->addFileInput( + "files", envmpClbck, "../", "Images (*.png *.jpg *.pfm *.exr *hdr)" ); + + auto strengthClbk = [editable]( double v ) { + if ( editable->m_data ) { + auto* env = editable->m_data.get(); + if ( env ) { env->setStrength( v / 100. ); } + } + }; + float s_init = 100.; + if ( editable->m_data ) { s_init = editable->m_data->getStrength() * 100.; } + controlPanel->addPowerSliderInput( "strength", strengthClbk, s_init, 0., 100 ); + controlPanel->setVisible( true ); + return controlPanel; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = dynamic_cast>*>( + editableParameter ); + auto slider = widget->findChild( "strength" ); + if ( slider && editable->m_data ) { + slider->setValue( editable->m_data->getStrength() * 100. ); + return true; + } + else { + return slider != nullptr; + } + } ); + + /* + * Scalar edition + */ + WidgetFactory::registerWidget( + []( EditableParameterBase* editableParameter ) { + auto editable = dynamic_cast*>( editableParameter ); + auto powerSlider = new PowerSlider(); + powerSlider->setObjectName( editable->m_name.c_str() ); + editable->m_data = 0.0; + powerSlider->setValue( editable->m_data ); + if ( editable->additionalData.size() >= 2 ) { + powerSlider->setRange( editable->additionalData[0], editable->additionalData[1] ); + } + else { + powerSlider->setRange( 0.0, 9999.0 ); + } + PowerSlider::connect( powerSlider, + &PowerSlider::valueChanged, + [editable]( Scalar value ) { editable->m_data = value; } ); + return powerSlider; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = dynamic_cast*>( editableParameter ); + auto slider = widget->findChild( editableParameter->m_name.c_str() ); + if ( slider ) { + slider->setValue( editable->m_data ); + return true; + } + else { + return false; + } + } ); + + /* + * Boolean edition + */ + WidgetFactory::registerWidget( + []( EditableParameterBase* editableParameter ) { + auto editable = dynamic_cast*>( editableParameter ); + auto checkBox = new QCheckBox(); + checkBox->setObjectName( editable->m_name.c_str() ); + checkBox->setCheckState( editable->m_data ? Qt::CheckState::Checked + : Qt::CheckState::Unchecked ); + QCheckBox::connect( checkBox, &QCheckBox::stateChanged, [editable]( int state ) { + if ( state == Qt::Unchecked ) { editable->m_data = false; } + else if ( state == Qt::Checked ) { + editable->m_data = true; + } + } ); + return checkBox; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = dynamic_cast*>( editableParameter ); + auto checkBox = widget->findChild( editableParameter->m_name.c_str() ); + if ( checkBox ) { + checkBox->setCheckState( editable->m_data ? Qt::Checked : Qt::Unchecked ); + return true; + } + else { + return false; + } + } ); + + /* + * Color edition + */ + WidgetFactory::registerWidget( + []( EditableParameterBase* editableParameter ) { + auto editable = + dynamic_cast*>( editableParameter ); + auto c = 255_ra * Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); + auto button = new QPushButton( "Choose color" ); + m_colorDialog = new QColorDialog( QColor( c.x(), c.y(), c.z() ) ); + m_colorDialog->setModal( true ); + m_colorDialog->setObjectName( editable->m_name.c_str() ); +#if !defined( __APPLE__ ) + m_colorDialog->setOption( QColorDialog::DontUseNativeDialog ); +#endif + QColorDialog::connect( + m_colorDialog, &QColorDialog::currentColorChanged, [editable]( QColor value ) { + int newR, newG, newB; + value.getRgb( &newR, &newG, &newB ); + editable->m_data = ( Ra::Core::Utils::Color::sRGBToLinearRGB( + Ra::Core::Utils::Color( float( newR ), float( newG ), float( newB ) ) * + ( 1_ra / 255_ra ) ) ); + } ); + QPushButton::connect( button, &QPushButton::clicked, [editable]() { + Ra::Core::Utils::Color c = + 255_ra * Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); + m_colorDialog->setCurrentColor( QColor( c.x(), c.y(), c.z() ) ); + m_colorDialog->show(); + } ); + return button; + }, + []( QWidget*, EditableParameterBase* editableParameter ) -> bool { + auto editable = + dynamic_cast*>( editableParameter ); + // auto colorDialog = widget->findChild( editable->m_name.c_str() ); + auto c = 255_ra * Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); + if ( m_colorDialog ) { + m_colorDialog->setCurrentColor( + QColor( int( c.x() ), int( c.y() ), int( c.z() ) ) ); + return true; + } + else { + return false; + } + } ); + + /* + * Button edition + */ + WidgetFactory::registerWidget( + []( EditableParameterBase* editableParameter ) { + auto editable = dynamic_cast*>( editableParameter ); + auto button = new QPushButton( "Show graph" ); + button->setObjectName( editableParameter->m_name.c_str() ); + QPushButton::connect( button, &QPushButton::clicked, [editable]() { + // Display a window to see the graph + /*auto graph = reinterpret_cast( editable ); + auto nodeEditor = new GraphEditorView( nullptr ); + + nodeEditor->editGraph( nullptr ); + nodeEditor->editGraph( graph ); + + nodeEditor->resize( 900, 500 ); + nodeEditor->move( 450, 260 ); + nodeEditor->show(); */ + } ); + + return button; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = dynamic_cast*>( editableParameter ); + auto button = widget->findChild( editable->m_name.c_str() ); + if ( button ) { return true; } + else { + return false; + } + } ); + +#if HAS_TRANSFER_FUNCTION + /* + * Transfer function + */ + WidgetFactory::registerWidget>( + []( EditableParameterBase* editableParameter ) { + auto editableTransferFunction = + dynamic_cast>*>( editableParameter ); + // TODO, give a name to the widget so that they can be updated automatically when + // loading a node + auto button = new QPushButton( "Open widget" ); + auto transferEditor = new TransferEditor(); + TransferEditor::connect( + transferEditor, + &TransferEditor::propertiesChanged, + [editableTransferFunction, transferEditor]() { + int pos = 0; + for ( int i = 0; i < 256; i++ ) { + unsigned int color = transferEditor->colorAt( i ); + editableTransferFunction->m_data.at( pos ) = + (unsigned char)( ( 0x00ff0000 & color ) >> 16 ) / 255.f; + editableTransferFunction->m_data.at( pos + 1 ) = + (unsigned char)( ( 0x0000ff00 & color ) >> 8 ) / 255.f; + editableTransferFunction->m_data.at( pos + 2 ) = + (unsigned char)( ( 0x000000ff & color ) ) / 255.f; + editableTransferFunction->m_data.at( pos + 3 ) = + (unsigned char)( ( 0xff000000 & color ) >> 24 ) / 255.f; + pos = pos + 4; + } + } ); + QPushButton::connect( + button, &QPushButton::clicked, [transferEditor]() { transferEditor->show(); } ); + return button; + }, + []( QWidget*, EditableParameterBase* ) -> bool { return false; } ); +#endif + /* + * String enumerator + */ + WidgetFactory::registerWidget>( + []( EditableParameterBase* editableParameter ) { + auto editable = + dynamic_cast>*>( editableParameter ); + auto selector = new QComboBox(); + selector->setObjectName( editable->m_name.c_str() ); + for ( const auto& e : editable->m_data ) { + selector->addItem( e.c_str() ); + } + QComboBox::connect( + selector, &QComboBox::currentTextChanged, [editable]( const QString& string ) { + editable->m_data.set( string.toStdString() ); + } ); + return selector; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = + dynamic_cast>*>( editableParameter ); + auto comboBox = widget->findChild( editable->m_name.c_str() ); + if ( comboBox ) { + comboBox->setCurrentText( editable->m_data.get().c_str() ); + return true; + } + else { + return false; + } + } ); + + /* + * String + */ + WidgetFactory::registerWidget( + []( EditableParameterBase* editableParameter ) { + auto editable = dynamic_cast*>( editableParameter ); + auto line = new QLineEdit(); + line->setObjectName( editable->m_name.c_str() ); + QLineEdit::connect( line, &QLineEdit::textEdited, [editable]( const QString& string ) { + editable->m_data = string.toStdString(); + } ); + return line; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = dynamic_cast*>( editableParameter ); + auto line = widget->findChild( editable->m_name.c_str() ); + if ( line ) { + line->setText( editable->m_data.c_str() ); + return true; + } + else { + return false; + } + } ); + /* + * Ra::Engine::Data::ShaderConfiguration --> Code Editor + */ +} + +} // namespace WidgetFactory + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp new file mode 100644 index 00000000000..00d6d5a0b15 --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp @@ -0,0 +1,83 @@ +#pragma once +#include + +#include + +#include + +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { +using namespace Ra::Dataflow::Core; + +// TODO : instead of a namespace, make it a class ? +/** + * Set of functions to manage automatic widget creation and update for editable parameters in the Qt + * Node Editor. + */ +namespace WidgetFactory { +/** Type of the function that creates a widget. + * @param the editable parameter that defines the widget content + * @return the created widget + */ +using WidgetCreatorFunc = std::function; +/** Type of the function that creates a widget. + * @param the widget whose content must be updated + * @param the editable parameter that defines the widget content + * @return true if update is done, false if not + */ +using WidgetUpdaterFunc = std::function; + +/** private method to manage the factory + */ +RA_DATAFLOW_API void registerWidgetInternal( size_t hashedType, + WidgetCreatorFunc widgetCreator, + WidgetUpdaterFunc widgetUpdater ); + +/** Register a widget builder and updater in the factory given the type of the editable parameter + * @tparam T The concrete type of the editable parameter + * @param widgetCreator a function that build a widget to edit the given parameter. + * @param widgetUpdater a function to update the widget state according to the given parameter. + */ +template +void registerWidget( WidgetCreatorFunc widgetCreator, WidgetUpdaterFunc widgetUpdater ) { + registerWidgetInternal( + typeid( T ).hash_code(), std::move( widgetCreator ), std::move( widgetUpdater ) ); +} + +/** + * Create a widget from an editable parameter using the widget factory + * @param editableParameter the data whose type will define the widget + * @return the created widget, nullptr if no widget creator is associated with the editable + * parameter type. + */ +RA_DATAFLOW_API QWidget* createWidget( EditableParameterBase* editableParameter ); + +/** + * Update a widget from an editable parameter using the widget factory + * + * @param widget the widget to update + * @param editableParameter the data whose content will be transfered to the widget + * @return true if the update is done, false if thereis no updater associated with the + * editableParrameter type. + */ +RA_DATAFLOW_API bool updateWidget( QWidget* widget, EditableParameterBase* editableParameter ); + +/** + * Initialize the factory with pre-defined widgets according to the Radium NodeGraph predefined + * nodes. + */ +RA_DATAFLOW_API void initializeWidgetFactory(); + +inline QColorDialog* m_colorDialog; +}; // namespace WidgetFactory + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt b/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt new file mode 100644 index 00000000000..7274a1b55f2 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt @@ -0,0 +1,153 @@ +cmake_minimum_required(VERSION 3.2) +# version 3.4 is required as other do not work with C++14 and clang + +project(NodeEditor CXX) + +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +set(CMAKE_DISABLE_SOURCE_CHANGES ON) + +get_directory_property(_has_parent PARENT_DIRECTORY) +if(_has_parent) + set(is_root_project OFF) +else() + set(is_root_project ON) +endif() + +set(NE_DEVELOPER_DEFAULTS "${is_root_project}" + CACHE BOOL "Turns on default settings for development of NodeEditor" +) + +option(BUILD_SHARED_LIBS "Build as shared library" ON) +option(BUILD_DEBUG_POSTFIX_D "Append d suffix to debug libraries" OFF) + +enable_testing() + +if(NE_DEVELOPER_DEFAULTS) + set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX_EXTENSIONS OFF) + + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") +endif() + +if(BUILD_DEBUG_POSTFIX_D) + set(CMAKE_DEBUG_POSTFIX d) +endif() + +# include(${CMAKE_CURRENT_LIST_DIR}/../../../../cmake/QtFunctions.cmake) Qt utility functions +include(QtFunctions) + +# Find the QtWidgets library +find_qt_package(COMPONENTS Core Widgets Gui OpenGL REQUIRED) + +qt_add_resources(RESOURCES ./resources/resources.qrc) + +# Unfortunately, as we have a split include/src, AUTOMOC doesn't work. We'll have to manually +# specify some files +set(CMAKE_AUTOMOC ON) + +set(CPP_SOURCE_FILES + src/Connection.cpp + src/ConnectionBlurEffect.cpp + src/ConnectionGeometry.cpp + src/ConnectionGraphicsObject.cpp + src/ConnectionPainter.cpp + src/ConnectionState.cpp + src/ConnectionStyle.cpp + src/DataModelRegistry.cpp + src/FlowScene.cpp + src/FlowView.cpp + src/FlowViewStyle.cpp + src/Node.cpp + src/NodeConnectionInteraction.cpp + src/NodeDataModel.cpp + src/NodeGeometry.cpp + src/NodeGraphicsObject.cpp + src/NodePainter.cpp + src/NodeState.cpp + src/NodeStyle.cpp + src/Properties.cpp + src/StyleCollection.cpp +) + +# If we want to give the option to build a static library, set BUILD_SHARED_LIBS option to OFF +add_library(NodeEditor ${CPP_SOURCE_FILES} ${RESOURCES}) +add_library(NodeEditor::NodeEditor ALIAS NodeEditor) + +target_include_directories( + NodeEditor + PUBLIC $ $ + PRIVATE $ + $ +) + +target_link_libraries(NodeEditor PUBLIC Qt::Core Qt::Widgets Qt::Gui Qt::OpenGL) + +target_compile_definitions( + NodeEditor + PUBLIC ${QtWidgets_DEFINITIONS} NODE_EDITOR_SHARED + PRIVATE NODE_EDITOR_EXPORTS + # NODE_DEBUG_DRAWING + QT_NO_KEYWORDS +) + +target_compile_options( + NodeEditor PRIVATE $<$:/W4 /wd4127 /EHsc> $<$:-Wall + -Wextra> $<$:-Wall -Wextra> +) + +target_compile_features(NodeEditor PUBLIC cxx_generic_lambdas # Require C++14 +) + +set_target_properties( + NodeEditor + PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib LIBRARY_OUTPUT_DIRECTORY + ${CMAKE_BINARY_DIR}/lib + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin +) + +# +# Moc +# + +file(GLOB_RECURSE HEADERS_TO_MOC include/nodes/internal/*.hpp) + +qt_wrap_cpp( + nodes_moc ${HEADERS_TO_MOC} TARGET NodeEditor OPTIONS --no-notes # Don't display a note for the + # headers which don't produce a + # moc_*.cpp +) + +target_sources(NodeEditor PRIVATE ${nodes_moc}) + +# ################################################################################################## +# Installation +# + +include(GNUInstallDirs) + +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/NodeEditor) + +install(TARGETS NodeEditor EXPORT NodeEditorTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +install(EXPORT NodeEditorTargets FILE NodeEditorTargets.cmake NAMESPACE NodeEditor:: + DESTINATION ${INSTALL_CONFIGDIR} +) + +export(TARGETS NodeEditor NAMESPACE NodeEditor:: FILE NodeEditorTargets.cmake) + +include(CMakePackageConfigHelpers) + +configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/NodeEditorConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake INSTALL_DESTINATION ${INSTALL_CONFIGDIR} +) + +install(FILES ${CMAKE_CURRENT_LIST_DIR}/cmake/QtFunctions.cmake DESTINATION ${INSTALL_CONFIGDIR}) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake DESTINATION ${INSTALL_CONFIGDIR}) diff --git a/src/Dataflow/QtGui/QtNodeEditor/LICENSE b/src/Dataflow/QtGui/QtNodeEditor/LICENSE new file mode 100644 index 00000000000..bc2bbeaf955 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2017, Dmitry Pinaev +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of copyright holder, nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/Dataflow/QtGui/QtNodeEditor/README.md b/src/Dataflow/QtGui/QtNodeEditor/README.md new file mode 100644 index 00000000000..49d407f429c --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/README.md @@ -0,0 +1,41 @@ +### Radium adaptation + +Code source taken from https://github.com/paceholder/nodeeditor +Some adaptations were made to allow seamless integration with Radium dataflow graph. + -> only used for graph editing (no evaluation with data propagation) + -> externalization of load/save and NodeDataModel management operation. + -> modified CMakeLists.txt and directory structure to keep only what is used in Radium + +### Purpose + +**NodeEditor** is conceived as a general-purpose Qt-based library aimed at +graph-controlled data processing. Nodes represent algorithms with certain inputs +and outputs. Connections transfer data from the output (source) of the first node +to the input (sink) of the second one. + +**NodeEditor** framework is a Visual [Dataflow +Programming](https://en.wikipedia.org/wiki/Dataflow_programming) tool. A library +client defines models and registers them in the data model registry. Further +work is driven by events taking place in DataModels and Nodes. The model +computing is triggered upon arriving of any new input data. The computed result +is propagated to the output connections. Each new connection fetches available +data and propagates is further. + +Each change in the source node is immediately propagated through all the +connections updating the whole graph. + +### Citing + + Dmitry Pinaev et al, Qt5 Node Editor, (2017), GitHub repository, https://github.com/paceholder/nodeeditor + +BibTeX + + @misc{Pinaev2017, + author = {Dmitry Pinaev et al}, + title = {Qt5 Node Editor}, + year = {2017}, + publisher = {GitHub}, + journal = {GitHub repository}, + howpublished = {\url{https://github.com/paceholder/nodeeditor}}, + commit = {1d1757d09b03cea0e4921bc19659465fe6e65b9b} + } diff --git a/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in b/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in new file mode 100644 index 00000000000..f3081994a0e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in @@ -0,0 +1,11 @@ +get_filename_component(NodeEditor_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) + +if(NOT TARGET NodeEditor::NodeEditor) + include(${CMAKE_CURRENT_LIST_DIR}/../QtFunctions.cmake) + check_and_set_qt_version("@QT_DEFAULT_MAJOR_VERSION@") + find_qt_dependency(COMPONENTS Core Widgets OpenGL Gui REQUIRED) + + include("${NodeEditor_CMAKE_DIR}/NodeEditorTargets.cmake") +endif() + +set(NodeEditor_LIBRARIES NodeEditor::NodeEditor) diff --git a/src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake b/src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake new file mode 100644 index 00000000000..ae9d0e99be1 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.12) + +# Find Qt5 or Qt6 packages Parameters: COMPONENTS : optional parameter listing the +# Qt packages (e.g. Core, Widgets REQUIRED: optional parameter propagated to find_package +# +# Usage: find_qt_package(COMPONENTS Core Widgets OpenGL Xml REQUIRED) which is equivalent to: +# find_package(Qt6 COMPONENTS Core Widgets OpenGL Xml REQUIRED) if Qt6 is available, or: +# find_package(Qt5 COMPONENTS Core Widgets OpenGL Xml REQUIRED) otherwise. +# +# Qt5 and Qt6 can be retrieved using versionless targets introduced in Qt5.15: +# https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#versionless-targets +macro(find_qt_package) + set(options REQUIRED) + set(oneValueArgs "") + set(multiValueArgs COMPONENTS) + + cmake_parse_arguments(MY_OPTIONS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT MY_OPTIONS_COMPONENTS) # User didn't enter any component + set(MY_OPTIONS_COMPONENTS "") + endif() + + find_package(Qt6 COMPONENTS ${MY_OPTIONS_COMPONENTS} QUIET) + if(NOT Qt6_FOUND) + if(${MY_OPTIONS_REQUIRED}) + find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS} REQUIRED) + else() + find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS}) + endif() + endif() +endmacro() + +# Find Qt5 or Qt6 dependency Parameters: COMPONENTS : optional parameter listing the +# Qt packages (e.g. Core, Widgets REQUIRED: optional parameter propagated to find_package +# +# Usage example: find_package(Qt5 COMPONENTS Core Widgets OpenGL Xml REQUIRED) +# +# Qt5 and Qt6 can be retrieved using versionless targets introduced in Qt5.15: +# https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#versionless-targets +macro(find_qt_dependency) + set(options REQUIRED) + set(oneValueArgs "") + set(multiValueArgs COMPONENTS) + + cmake_parse_arguments(MY_OPTIONS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT MY_OPTIONS_COMPONENTS) # User didn't enter any component + set(MY_OPTIONS_COMPONENTS "") + endif() + + find_package(Qt6 COMPONENTS ${MY_OPTIONS_COMPONENTS} QUIET) + if(NOT Qt6_FOUND) + if(${MY_OPTIONS_REQUIRED}) + find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS} REQUIRED) + else() + find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS}) + endif() + endif() +endmacro() diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection new file mode 100644 index 00000000000..9b6d297dd38 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection @@ -0,0 +1 @@ +#include "internal/Connection.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle new file mode 100644 index 00000000000..b771bf33f7e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle @@ -0,0 +1 @@ +#include "internal/ConnectionStyle.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry new file mode 100644 index 00000000000..2bb975c00d9 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry @@ -0,0 +1 @@ +#include "internal/DataModelRegistry.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene new file mode 100644 index 00000000000..b4e0745b64e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene @@ -0,0 +1 @@ +#include "internal/FlowScene.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView new file mode 100644 index 00000000000..ca35f57633e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView @@ -0,0 +1 @@ +#include "internal/FlowView.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle new file mode 100644 index 00000000000..e66055b1b5c --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle @@ -0,0 +1 @@ +#include "internal/FlowViewStyle.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node new file mode 100644 index 00000000000..9270781ad50 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node @@ -0,0 +1 @@ +#include "internal/Node.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData new file mode 100644 index 00000000000..5fcbd7b3adb --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData @@ -0,0 +1 @@ +#include "internal/NodeData.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel new file mode 100644 index 00000000000..5346e982816 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel @@ -0,0 +1 @@ +#include "internal/NodeDataModel.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry new file mode 100644 index 00000000000..5e1b523ad9f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry @@ -0,0 +1 @@ +#include "internal/NodeGeometry.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate new file mode 100644 index 00000000000..3824071debd --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate @@ -0,0 +1 @@ +#include "internal/NodePainterDelegate.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState new file mode 100644 index 00000000000..5b531fd0e8b --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState @@ -0,0 +1 @@ +#include "internal/NodeState.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle new file mode 100644 index 00000000000..d96092dc265 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle @@ -0,0 +1 @@ +#include "internal/NodeStyle.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection new file mode 100644 index 00000000000..e1f93ecac9f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection @@ -0,0 +1 @@ +#include "internal/StyleCollection.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter new file mode 100644 index 00000000000..9bcdbbd23c2 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter @@ -0,0 +1 @@ +#include "internal/TypeConverter.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp new file mode 100644 index 00000000000..0e0eb0eee1b --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp @@ -0,0 +1,40 @@ +#pragma once + +#if defined( __MINGW32__ ) || defined( __MINGW64__ ) +# define NODE_EDITOR_COMPILER "MinGW" +# define NODE_EDITOR_COMPILER_MINGW +#elif defined( __GNUC__ ) +# define NODE_EDITOR_COMPILER "GNU" +# define NODE_EDITOR_COMPILER_GNU +# define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__ +# define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__ +# define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__ +#elif defined( __clang__ ) +# define NODE_EDITOR_COMPILER "Clang" +# define NODE_EDITOR_COMPILER_CLANG +#elif defined( _MSC_VER ) +# define NODE_EDITOR_COMPILER "Microsoft Visual C++" +# define NODE_EDITOR_COMPILER_MICROSOFT +#elif defined( __BORLANDC__ ) +# define NODE_EDITOR_COMPILER "Borland C++ Builder" +# define NODE_EDITOR_COMPILER_BORLAND +#elif defined( __CODEGEARC__ ) +# define NODE_EDITOR_COMPILER "CodeGear C++ Builder" +# define NODE_EDITOR_COMPILER_CODEGEAR +#elif defined( __INTEL_COMPILER ) || defined( __ICL ) +# define NODE_EDITOR_COMPILER "Intel C++" +# define NODE_EDITOR_COMPILER_INTEL +#elif defined( __xlC__ ) || defined( __IBMCPP__ ) +# define NODE_EDITOR_COMPILER "IBM XL C++" +# define NODE_EDITOR_COMPILER_IBM +#elif defined( __HP_aCC ) +# define NODE_EDITOR_COMPILER "HP aC++" +# define NODE_EDITOR_COMPILER_HP +#elif defined( __WATCOMC__ ) +# define NODE_EDITOR_COMPILER "Watcom C++" +# define NODE_EDITOR_COMPILER_WATCOM +#endif + +#ifndef NODE_EDITOR_COMPILER +# error "Current compiler is not supported." +#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp new file mode 100644 index 00000000000..24fbe5245fd --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include + +#include "NodeData.hpp" +#include "PortType.hpp" + +#include "ConnectionGeometry.hpp" +#include "ConnectionState.hpp" +#include "Export.hpp" +#include "QUuidStdHash.hpp" +#include "Serializable.hpp" +#include "TypeConverter.hpp" +#include "memory.hpp" + +class QPointF; + +namespace QtNodes { + +class Node; +class NodeData; +class ConnectionGraphicsObject; + +/// +class NODE_EDITOR_PUBLIC Connection : public QObject, public Serializable +{ + + Q_OBJECT + + public: + /// New Connection is attached to the port of the given Node. + /// The port has parameters (portType, portIndex). + /// The opposite connection end will require anothre port. + Connection( PortType portType, Node& node, PortIndex portIndex ); + + Connection( Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter converter = TypeConverter {} ); + + Connection( const Connection& ) = delete; + Connection operator=( const Connection& ) = delete; + + ~Connection(); + + public: + QJsonObject save() const override; + + public: + QUuid id() const; + + /// Remembers the end being dragged. + /// Invalidates Node address. + /// Grabs mouse. + void setRequiredPort( PortType portType ); + PortType requiredPort() const; + + void setGraphicsObject( std::unique_ptr&& graphics ); + + /// Assigns a node to the required port. + /// It is assumed that there is a required port, no extra checks + void setNodeToPort( Node& node, PortType portType, PortIndex portIndex ); + + void removeFromNodes() const; + + public: + ConnectionGraphicsObject& getConnectionGraphicsObject() const; + + ConnectionState const& connectionState() const; + ConnectionState& connectionState(); + + ConnectionGeometry& connectionGeometry(); + + ConnectionGeometry const& connectionGeometry() const; + + Node* getNode( PortType portType ) const; + + Node*& getNode( PortType portType ); + + PortIndex getPortIndex( PortType portType ) const; + + void clearNode( PortType portType ); + + NodeDataType dataType( PortType portType ) const; + + void setTypeConverter( TypeConverter converter ); + + bool complete() const; + + public: // data propagation + void propagateData( std::shared_ptr nodeData ) const; + + void propagateEmptyData() const; + + Q_SIGNALS: + + void connectionCompleted( Connection const& ) const; + + void connectionMadeIncomplete( Connection const& ) const; + + private: + QUuid _uid; + + private: + Node* _outNode = nullptr; + Node* _inNode = nullptr; + + PortIndex _outPortIndex; + PortIndex _inPortIndex; + + private: + ConnectionState _connectionState; + ConnectionGeometry _connectionGeometry; + + std::unique_ptr _connectionGraphicsObject; + + TypeConverter _converter; + + Q_SIGNALS: + + void updated( Connection& conn ) const; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp new file mode 100644 index 00000000000..7ca5b965b7d --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "PortType.hpp" + +#include +#include + +#include + +namespace QtNodes { + +class ConnectionGeometry +{ + public: + ConnectionGeometry(); + + public: + QPointF const& getEndPoint( PortType portType ) const; + + void setEndPoint( PortType portType, QPointF const& point ); + + void moveEndPoint( PortType portType, QPointF const& offset ); + + QRectF boundingRect() const; + + std::pair pointsC1C2() const; + + QPointF source() const { return _out; } + QPointF sink() const { return _in; } + + double lineWidth() const { return _lineWidth; } + + bool hovered() const { return _hovered; } + void setHovered( bool hovered ) { _hovered = hovered; } + + private: + // local object coordinates + QPointF _in; + QPointF _out; + + // int _animationPhase; + + double _lineWidth; + + bool _hovered; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp new file mode 100644 index 00000000000..b0286da0e9f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include + +class QGraphicsSceneMouseEvent; + +namespace QtNodes { + +class FlowScene; +class Connection; +class ConnectionGeometry; +class Node; + +/// Graphic Object for connection. Adds itself to scene +class ConnectionGraphicsObject : public QGraphicsObject +{ + Q_OBJECT + + public: + ConnectionGraphicsObject( FlowScene& scene, Connection& connection ); + + virtual ~ConnectionGraphicsObject(); + + enum { Type = UserType + 2 }; + int type() const override { return Type; } + + public: + Connection& connection(); + + QRectF boundingRect() const override; + + QPainterPath shape() const override; + + void setGeometryChanged(); + + /// Updates the position of both ends + void move(); + + void lock( bool locked ); + + protected: + void paint( QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget* widget = 0 ) override; + + void mousePressEvent( QGraphicsSceneMouseEvent* event ) override; + + void mouseMoveEvent( QGraphicsSceneMouseEvent* event ) override; + + void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) override; + + void hoverEnterEvent( QGraphicsSceneHoverEvent* event ) override; + + void hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) override; + + private: + void addGraphicsEffect(); + + private: + FlowScene& _scene; + + Connection& _connection; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp new file mode 100644 index 00000000000..1e9d9588e10 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include + +#include "PortType.hpp" + +class QPointF; + +namespace QtNodes { + +class Node; + +/// Stores currently draggind end. +/// Remembers last hovered Node. +class ConnectionState +{ + public: + ConnectionState( PortType port = PortType::None ) : _requiredPort( port ) {} + + ConnectionState( const ConnectionState& ) = delete; + ConnectionState operator=( const ConnectionState& ) = delete; + + ~ConnectionState(); + + public: + void setRequiredPort( PortType end ) { _requiredPort = end; } + + PortType requiredPort() const { return _requiredPort; } + + bool requiresPort() const { return _requiredPort != PortType::None; } + + void setNoRequiredPort() { _requiredPort = PortType::None; } + + public: + void interactWithNode( Node* node ); + + void setLastHoveredNode( Node* node ); + + Node* lastHoveredNode() const { return _lastHoveredNode; } + + void resetLastHoveredNode(); + + private: + PortType _requiredPort; + + Node* _lastHoveredNode { nullptr }; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp new file mode 100644 index 00000000000..df4883402f1 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "Style.hpp" + +namespace QtNodes { + +class NODE_EDITOR_PUBLIC ConnectionStyle : public Style +{ + public: + ConnectionStyle(); + + ConnectionStyle( QString jsonText ); + + public: + static void setConnectionStyle( QString jsonText ); + + private: + void loadJsonText( QString jsonText ) override; + + void loadJsonFile( QString fileName ) override; + + void loadJsonFromByteArray( QByteArray const& byteArray ) override; + + public: + QColor constructionColor() const; + QColor normalColor() const; + QColor normalColor( QString typeId ) const; + QColor selectedColor() const; + QColor selectedHaloColor() const; + QColor hoveredColor() const; + + float lineWidth() const; + float constructionLineWidth() const; + float pointDiameter() const; + + bool useDataDefinedColors() const; + + private: + QColor ConstructionColor; + QColor NormalColor; + QColor SelectedColor; + QColor SelectedHaloColor; + QColor HoveredColor; + + float LineWidth; + float ConstructionLineWidth; + float PointDiameter; + + bool UseDataDefinedColors; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp new file mode 100644 index 00000000000..3003b489bd8 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Export.hpp" +#include "NodeDataModel.hpp" +#include "QStringStdHash.hpp" +#include "TypeConverter.hpp" +#include "memory.hpp" + +namespace QtNodes { + +inline bool operator<( QtNodes::NodeDataType const& d1, QtNodes::NodeDataType const& d2 ) { + return d1.id < d2.id; +} + +/// Class uses map for storing models (name, model) +class NODE_EDITOR_PUBLIC DataModelRegistry +{ + + public: + using RegistryItemPtr = std::unique_ptr; + using RegistryItemCreator = std::function; + using RegisteredModelCreatorsMap = std::unordered_map; + using RegisteredModelsCategoryMap = std::unordered_map; + using CategoriesSet = std::set; + + using RegisteredTypeConvertersMap = std::map; + + DataModelRegistry() = default; + ~DataModelRegistry() = default; + + DataModelRegistry( DataModelRegistry const& ) = delete; + DataModelRegistry( DataModelRegistry&& ) = default; + + DataModelRegistry& operator=( DataModelRegistry const& ) = delete; + DataModelRegistry& operator=( DataModelRegistry&& ) = default; + + public: + // --- + /* Added for Radium */ + void + registerModel( QString const& typeName, RegistryItemCreator creator, QString const& category ) { + if ( !_registeredItemCreators.count( typeName ) ) { + _registeredItemCreators[typeName] = std::move( creator ); + _categories.insert( category ); + _registeredModelsCategory[typeName] = category; + } + } + // --- + template + void registerModel( RegistryItemCreator creator, QString const& category = "Nodes" ) { + const QString name = computeName( HasStaticMethodName {}, creator ); + if ( !_registeredItemCreators.count( name ) ) { + _registeredItemCreators[name] = std::move( creator ); + _categories.insert( category ); + _registeredModelsCategory[name] = category; + } + } + + template + void registerModel( QString const& category = "Nodes" ) { + RegistryItemCreator creator = []() { return std::make_unique(); }; + registerModel( std::move( creator ), category ); + } + + template + void registerModel( QString const& category, RegistryItemCreator creator ) { + registerModel( std::move( creator ), category ); + } + + template + void registerModel( ModelCreator&& creator, QString const& category = "Nodes" ) { + using ModelType = compute_model_type_t; + registerModel( std::forward( creator ), category ); + } + + template + void registerModel( QString const& category, ModelCreator&& creator ) { + registerModel( std::forward( creator ), category ); + } + + void registerTypeConverter( TypeConverterId const& id, TypeConverter typeConverter ) { + _registeredTypeConverters[id] = std::move( typeConverter ); + } + + std::unique_ptr create( QString const& modelName ); + + RegisteredModelCreatorsMap const& registeredModelCreators() const; + + RegisteredModelsCategoryMap const& registeredModelsCategoryAssociation() const; + + CategoriesSet const& categories() const; + + TypeConverter getTypeConverter( NodeDataType const& d1, NodeDataType const& d2 ) const; + + private: + RegisteredModelsCategoryMap _registeredModelsCategory; + + CategoriesSet _categories; + + RegisteredModelCreatorsMap _registeredItemCreators; + + RegisteredTypeConvertersMap _registeredTypeConverters; + + private: + // If the registered ModelType class has the static member method + // + // static Qstring Name(); + // + // use it. Otherwise use the non-static method: + // + // virtual QString name() const; + + template + struct HasStaticMethodName : std::false_type {}; + + template + struct HasStaticMethodName< + T, + typename std::enable_if::value>::type> + : std::true_type {}; + + template + static QString computeName( std::true_type, RegistryItemCreator const& ) { + return ModelType::Name(); + } + + template + static QString computeName( std::false_type, RegistryItemCreator const& creator ) { + return creator()->name(); + } + + template + struct UnwrapUniquePtr { + // Assert always fires, but the compiler doesn't know this: + static_assert( !std::is_same::value, + "The ModelCreator must return a std::unique_ptr, where T " + "inherits from NodeDataModel" ); + }; + + template + struct UnwrapUniquePtr> { + static_assert( std::is_base_of::value, + "The ModelCreator must return a std::unique_ptr, where T " + "inherits from NodeDataModel" ); + using type = T; + }; + + template + using compute_model_type_t = typename UnwrapUniquePtr::type; +}; + +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp new file mode 100644 index 00000000000..15d4cea17f2 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "Compiler.hpp" +#include "OperatingSystem.hpp" + +#ifdef NODE_EDITOR_PLATFORM_WINDOWS +# define NODE_EDITOR_EXPORT __declspec( dllexport ) +# define NODE_EDITOR_IMPORT __declspec( dllimport ) +# define NODE_EDITOR_LOCAL +#elif NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || defined( NODE_EDITOR_COMPILER_CLANG ) +# define NODE_EDITOR_EXPORT __attribute__( ( visibility( "default" ) ) ) +# define NODE_EDITOR_IMPORT __attribute__( ( visibility( "default" ) ) ) +# define NODE_EDITOR_LOCAL __attribute__( ( visibility( "hidden" ) ) ) +#else +# define NODE_EDITOR_EXPORT +# define NODE_EDITOR_IMPORT +# define NODE_EDITOR_LOCAL +#endif + +#ifdef __cplusplus +# define NODE_EDITOR_DEMANGLED extern "C" +#else +# define NODE_EDITOR_DEMANGLED +#endif + +#if defined( NODE_EDITOR_SHARED ) && !defined( NODE_EDITOR_STATIC ) +# ifdef NODE_EDITOR_EXPORTS +# define NODE_EDITOR_PUBLIC NODE_EDITOR_EXPORT +# else +# define NODE_EDITOR_PUBLIC NODE_EDITOR_IMPORT +# endif +# define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL +#elif !defined( NODE_EDITOR_SHARED ) && defined( NODE_EDITOR_STATIC ) +# define NODE_EDITOR_PUBLIC +# define NODE_EDITOR_PRIVATE +#elif defined( NODE_EDITOR_SHARED ) && defined( NODE_EDITOR_STATIC ) +# ifdef NODE_EDITOR_EXPORTS +# error "Cannot build as shared and static simultaneously." +# else +# error "Cannot link against shared and static simultaneously." +# endif +#else +# ifdef NODE_EDITOR_EXPORTS +# error "Choose whether to build as shared or static." +# else +# error "Choose whether to link against shared or static." +# endif +#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp new file mode 100644 index 00000000000..69915c230af --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp @@ -0,0 +1,163 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include "DataModelRegistry.hpp" +#include "Export.hpp" +#include "QUuidStdHash.hpp" +#include "TypeConverter.hpp" +#include "memory.hpp" + +namespace QtNodes { + +class NodeDataModel; +class FlowItemInterface; +class Node; +class NodeGraphicsObject; +class Connection; +class ConnectionGraphicsObject; +class NodeStyle; + +/// Scene holds connections and nodes. +class NODE_EDITOR_PUBLIC FlowScene : public QGraphicsScene +{ + Q_OBJECT + public: + FlowScene( std::shared_ptr registry, QObject* parent = Q_NULLPTR ); + + FlowScene( QObject* parent = Q_NULLPTR ); + + ~FlowScene(); + + public: + std::shared_ptr + createConnection( PortType connectedPort, Node& node, PortIndex portIndex ); + + std::shared_ptr + createConnection( Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter const& converter = TypeConverter {} ); + + std::shared_ptr restoreConnection( QJsonObject const& connectionJson ); + + void importConnection( const QString& fromNodeId, + int fromNodePort, + const QString& toNodeId, + int toNodePort ); + + void deleteConnection( Connection& connection ); + + Node& createNode( std::unique_ptr&& dataModel ); + + Node& importNode( std::unique_ptr&& dataModel ); + + Node& restoreNode( QJsonObject const& nodeJson ); + + void removeNode( Node& node ); + + DataModelRegistry& registry() const; + + void setRegistry( std::shared_ptr registry ); + + void iterateOverNodes( std::function const& visitor ); + + void iterateOverNodeData( std::function const& visitor ); + + void iterateOverNodeDataDependentOrder( std::function const& visitor ); + + QPointF getNodePosition( Node const& node ) const; + + void setNodePosition( Node& node, QPointF const& pos ) const; + + QSizeF getNodeSize( Node const& node ) const; + + public: + std::unordered_map> const& nodes() const; + + std::unordered_map> const& connections() const; + + std::vector allNodes() const; + + std::vector selectedNodes() const; + + public: + void clearScene(); + + void save() const; + + void load(); + + QByteArray saveToMemory() const; + + void loadFromMemory( const QByteArray& data ); + + void setSceneName( const QString& newname ) { _scenename = newname; } + + QString getSceneName() const { return _scenename; } + + Q_SIGNALS: + + /** + * @brief Node has been created but not on the scene yet. + * @see nodePlaced() + */ + void nodeCreated( Node& n ); + + /** + * @brief Node has been added to the scene. + * @details Connect to this signal if need a correct position of node. + * @see nodeCreated() + */ + void nodePlaced( Node& n ); + + void nodeDeleted( Node& n ); + + void connectionCreated( Connection const& c ); + void connectionDeleted( Connection const& c ); + + void nodeMoved( Node& n, const QPointF& newLocation ); + + void nodeDoubleClicked( Node& n ); + + void connectionHovered( Connection& c, QPoint screenPos ); + + void nodeHovered( Node& n, QPoint screenPos ); + + void connectionHoverLeft( Connection& c ); + + void nodeHoverLeft( Node& n ); + + void nodeContextMenu( Node& n, const QPointF& pos ); + + private: + using SharedConnection = std::shared_ptr; + using UniqueNode = std::unique_ptr; + + // DO NOT reorder this member to go after the others. + // This should outlive all the connections and nodes of + // the graph, so that nodes can potentially have pointers into it, + // which is why it comes first in the class. + std::shared_ptr _registry; + + std::unordered_map _connections; + std::unordered_map _nodes; + + // name of the scene + QString _scenename { "" }; + private Q_SLOTS: + + void setupConnectionSignals( Connection const& c ); + + void sendConnectionCreatedToNodes( Connection const& c ); + void sendConnectionDeletedToNodes( Connection const& c ); +}; + +Node* locateNodeAt( QPointF scenePoint, FlowScene& scene, QTransform const& viewTransform ); +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp new file mode 100644 index 00000000000..79a23b35a30 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "Export.hpp" + +namespace QtNodes { + +class FlowScene; + +class NODE_EDITOR_PUBLIC FlowView : public QGraphicsView +{ + Q_OBJECT + public: + FlowView( QWidget* parent = Q_NULLPTR ); + FlowView( FlowScene* scene, QWidget* parent = Q_NULLPTR ); + + FlowView( const FlowView& ) = delete; + FlowView operator=( const FlowView& ) = delete; + + QAction* clearSelectionAction() const; + + QAction* deleteSelectionAction() const; + + void setScene( FlowScene* scene ); + + public Q_SLOTS: + + void scaleUp(); + + void scaleDown(); + + void deleteSelectedNodes(); + + protected: + void contextMenuEvent( QContextMenuEvent* event ) override; + + void wheelEvent( QWheelEvent* event ) override; + + void keyPressEvent( QKeyEvent* event ) override; + + void keyReleaseEvent( QKeyEvent* event ) override; + + void mousePressEvent( QMouseEvent* event ) override; + + void mouseMoveEvent( QMouseEvent* event ) override; + + void drawBackground( QPainter* painter, const QRectF& r ) override; + + void showEvent( QShowEvent* event ) override; + + protected: + FlowScene* scene(); + + private: + QAction* _clearSelectionAction; + QAction* _deleteSelectionAction; + + QPointF _clickPos; + + FlowScene* _scene; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp new file mode 100644 index 00000000000..e98dbfda7ce --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "Style.hpp" + +namespace QtNodes { + +class NODE_EDITOR_PUBLIC FlowViewStyle : public Style +{ + public: + FlowViewStyle(); + + FlowViewStyle( QString jsonText ); + + public: + static void setStyle( QString jsonText ); + + private: + void loadJsonText( QString jsonText ) override; + + void loadJsonFile( QString fileName ) override; + + void loadJsonFromByteArray( QByteArray const& byteArray ) override; + + public: + QColor BackgroundColor; + QColor FineGridColor; + QColor CoarseGridColor; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp new file mode 100644 index 00000000000..f766be6b78f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include + +#include + +#include "PortType.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "Export.hpp" +#include "NodeData.hpp" +#include "NodeGeometry.hpp" +#include "NodeGraphicsObject.hpp" +#include "NodeState.hpp" +#include "Serializable.hpp" +#include "memory.hpp" + +namespace QtNodes { + +class Connection; +class ConnectionState; +class NodeGraphicsObject; +class NodeDataModel; + +class NODE_EDITOR_PUBLIC Node : public QObject, public Serializable +{ + Q_OBJECT + + public: + /// NodeDataModel should be an rvalue and is moved into the Node + Node( std::unique_ptr&& dataModel ); + + virtual ~Node(); + + public: + QJsonObject save() const override; + + void restore( QJsonObject const& json ) override; + + void import(); + + public: + QUuid id() const; + + void reactToPossibleConnection( PortType, NodeDataType const&, QPointF const& scenePoint ); + + void resetReactionToConnection(); + + public: + NodeGraphicsObject const& nodeGraphicsObject() const; + + NodeGraphicsObject& nodeGraphicsObject(); + + void setGraphicsObject( std::unique_ptr&& graphics ); + + NodeGeometry& nodeGeometry(); + + NodeGeometry const& nodeGeometry() const; + + NodeState const& nodeState() const; + + NodeState& nodeState(); + + NodeDataModel* nodeDataModel() const; + + public Q_SLOTS: // data propagation + + /// Propagates incoming data to the underlying model. + void propagateData( std::shared_ptr nodeData, PortIndex inPortIndex ) const; + + /// Fetches data from model's OUT #index port + /// and propagates it to the connection + void onDataUpdated( PortIndex index ); + + /// update the graphic part if the size of the embeddedwidget changes + void onNodeSizeUpdated(); + + private: + // addressing + friend class FlowScene; + QUuid _uid; + + // data + + std::unique_ptr _nodeDataModel; + + NodeState _nodeState; + + // painting + + NodeGeometry _nodeGeometry; + + std::unique_ptr _nodeGraphicsObject; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp new file mode 100644 index 00000000000..b1176f9fed2 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "Export.hpp" + +namespace QtNodes { + +struct NodeDataType { + QString id; + QString name; +}; + +/// Class represents data transferred between nodes. +/// @param type is used for comparing the types +/// The actual data is stored in subtypes +class NODE_EDITOR_PUBLIC NodeData +{ + public: + virtual ~NodeData() = default; + + virtual bool sameType( NodeData const& nodeData ) const { + return ( this->type().id == nodeData.type().id ); + } + + /// Type for inner use + virtual NodeDataType type() const = 0; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp new file mode 100644 index 00000000000..4d8d021df1a --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "NodeData.hpp" +#include "NodeGeometry.hpp" +#include "NodePainterDelegate.hpp" +#include "NodeStyle.hpp" +#include "PortType.hpp" +#include "Serializable.hpp" +#include "memory.hpp" + +namespace QtNodes { + +enum class NodeValidationState { Valid, Warning, Error }; + +class Connection; + +class StyleCollection; + +class NODE_EDITOR_PUBLIC NodeDataModel : public QObject, public Serializable +{ + Q_OBJECT + + public: + NodeDataModel(); + + virtual ~NodeDataModel() = default; + + /// Caption is used in GUI + virtual QString caption() const = 0; + + /// It is possible to hide caption in GUI + virtual bool captionVisible() const { return true; } + + /// Port caption is used in GUI to label individual ports + virtual QString portCaption( PortType, PortIndex ) const { return QString(); } + + /// It is possible to hide port caption in GUI + virtual bool portCaptionVisible( PortType, PortIndex ) const { return false; } + + /// Name makes this model unique + virtual QString name() const = 0; + + /// Added to allow node ownership management from outside the editor + /// The uuid of the node + virtual QString uuid() const = 0; //{return "";} + + virtual bool isDeletable() { return true; } + + virtual void updateState() {} + + virtual void addMetaData( QJsonObject& ) {} + + public: + QJsonObject save() const override; + + public: + virtual unsigned int nPorts( PortType portType ) const = 0; + + virtual NodeDataType dataType( PortType portType, PortIndex portIndex ) const = 0; + + public: + enum class ConnectionPolicy { + One, + Many, + }; + + virtual ConnectionPolicy portOutConnectionPolicy( PortIndex ) const { + return ConnectionPolicy::Many; + } + + NodeStyle const& nodeStyle() const; + + void setNodeStyle( NodeStyle const& style ); + + public: + /// Triggers the algorithm + virtual void setInData( std::shared_ptr nodeData, PortIndex port ) = 0; + + virtual std::shared_ptr outData( PortIndex port ) = 0; + + virtual QWidget* embeddedWidget() = 0; + + virtual bool resizable() const { return false; } + + virtual NodeValidationState validationState() const { return NodeValidationState::Valid; } + + virtual QString validationMessage() const { return QString( "" ); } + + virtual NodePainterDelegate* painterDelegate() const { return nullptr; } + + public Q_SLOTS: + + virtual void inputConnectionCreated( Connection const& ) {} + + virtual void inputConnectionDeleted( Connection const& ) {} + + virtual void outputConnectionCreated( Connection const& ) {} + + virtual void outputConnectionDeleted( Connection const& ) {} + + Q_SIGNALS: + + void dataUpdated( PortIndex index ); + + void dataInvalidated( PortIndex index ); + + void computingStarted(); + + void computingFinished(); + + void embeddedWidgetSizeUpdated(); + + private: + NodeStyle _nodeStyle; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp new file mode 100644 index 00000000000..7c8800b5053 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include +#include + +#include "Export.hpp" +#include "PortType.hpp" +#include "memory.hpp" + +namespace QtNodes { + +class NodeState; +class NodeDataModel; +class Node; + +class NODE_EDITOR_PUBLIC NodeGeometry +{ + public: + NodeGeometry( std::unique_ptr const& dataModel ); + + public: + unsigned int height() const { return _height; } + + void setHeight( unsigned int h ) { _height = h; } + + unsigned int width() const { return _width; } + + void setWidth( unsigned int w ) { _width = w; } + + unsigned int entryHeight() const { return _entryHeight; } + void setEntryHeight( unsigned int h ) { _entryHeight = h; } + + unsigned int entryWidth() const { return _entryWidth; } + + void setEntryWidth( unsigned int w ) { _entryWidth = w; } + + unsigned int spacing() const { return _spacing; } + + void setSpacing( unsigned int s ) { _spacing = s; } + + bool hovered() const { return _hovered; } + + void setHovered( unsigned int h ) { _hovered = h; } + + unsigned int nSources() const; + + unsigned int nSinks() const; + + QPointF const& draggingPos() const { return _draggingPos; } + + void setDraggingPosition( QPointF const& pos ) { _draggingPos = pos; } + + public: + QRectF entryBoundingRect() const; + + QRectF boundingRect() const; + + /// Updates size unconditionally + void recalculateSize() const; + + /// Updates size if the QFontMetrics is changed + void recalculateSize( QFont const& font ) const; + + // TODO removed default QTransform() + QPointF portScenePosition( PortIndex index, + PortType portType, + QTransform const& t = QTransform() ) const; + + PortIndex checkHitScenePoint( PortType portType, + QPointF point, + QTransform const& t = QTransform() ) const; + + QRect resizeRect() const; + + /// Returns the position of a widget on the Node surface + QPointF widgetPosition() const; + + /// Returns the maximum height a widget can be without causing the node to grow. + int equivalentWidgetHeight() const; + + unsigned int validationHeight() const; + + unsigned int validationWidth() const; + + static QPointF calculateNodePositionBetweenNodePorts( PortIndex targetPortIndex, + PortType targetPort, + Node* targetNode, + PortIndex sourcePortIndex, + PortType sourcePort, + Node* sourceNode, + Node& newNode ); + + private: + unsigned int captionHeight() const; + + unsigned int captionWidth() const; + + unsigned int portWidth( PortType portType ) const; + + private: + // some variables are mutable because + // we need to change drawing metrics + // corresponding to fontMetrics + // but this doesn't change constness of Node + + mutable unsigned int _width; + mutable unsigned int _height; + unsigned int _entryWidth; + mutable unsigned int _inputPortWidth; + mutable unsigned int _outputPortWidth; + mutable unsigned int _entryHeight; + unsigned int _spacing; + + bool _hovered; + + unsigned int _nSources; + unsigned int _nSinks; + + QPointF _draggingPos; + + std::unique_ptr const& _dataModel; + + mutable QFontMetrics _fontMetrics; + mutable QFontMetrics _boldFontMetrics; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp new file mode 100644 index 00000000000..0c25331890b --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +#include "Connection.hpp" + +#include "NodeGeometry.hpp" +#include "NodeState.hpp" + +class QGraphicsProxyWidget; + +namespace QtNodes { + +class FlowScene; +class FlowItemEntry; + +/// Class reacts on GUI events, mouse clicks and +/// forwards painting operation. +class NodeGraphicsObject : public QGraphicsObject +{ + Q_OBJECT + + public: + NodeGraphicsObject( FlowScene& scene, Node& node ); + + virtual ~NodeGraphicsObject(); + + Node& node(); + + Node const& node() const; + + QRectF boundingRect() const override; + + void setGeometryChanged(); + + /// Visits all attached connections and corrects + /// their corresponding end points. + void moveConnections() const; + + enum { Type = UserType + 1 }; + + int type() const override { return Type; } + + void lock( bool locked ); + + protected: + void paint( QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget* widget = 0 ) override; + + QVariant itemChange( GraphicsItemChange change, const QVariant& value ) override; + + void mousePressEvent( QGraphicsSceneMouseEvent* event ) override; + + void mouseMoveEvent( QGraphicsSceneMouseEvent* event ) override; + + void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) override; + + void hoverEnterEvent( QGraphicsSceneHoverEvent* event ) override; + + void hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) override; + + void hoverMoveEvent( QGraphicsSceneHoverEvent* ) override; + + void mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) override; + + void contextMenuEvent( QGraphicsSceneContextMenuEvent* event ) override; + + private: + void embedQWidget(); + + private: + FlowScene& _scene; + + Node& _node; + + bool _locked; + + // either nullptr or owned by parent QGraphicsItem + QGraphicsProxyWidget* _proxyWidget; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp new file mode 100644 index 00000000000..09e7513d96f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "NodeDataModel.hpp" +#include "NodeGeometry.hpp" + +namespace QtNodes { + +/// Class to allow for custom painting +class NODE_EDITOR_PUBLIC NodePainterDelegate +{ + + public: + virtual ~NodePainterDelegate() = default; + + virtual void + paint( QPainter* painter, NodeGeometry const& geom, NodeDataModel const* model ) = 0; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp new file mode 100644 index 00000000000..e9e635c9b2f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include + +#include "Export.hpp" + +#include "NodeData.hpp" +#include "PortType.hpp" +#include "memory.hpp" + +namespace QtNodes { + +class Connection; +class NodeDataModel; + +/// Contains vectors of connected input and output connections. +/// Stores bool for reacting on hovering connections +class NODE_EDITOR_PUBLIC NodeState +{ + public: + enum ReactToConnectionState { REACTING, NOT_REACTING }; + + public: + NodeState( std::unique_ptr const& model ); + + public: + using ConnectionPtrSet = std::unordered_map; + + /// Returns vector of connections ID. + /// Some of them can be empty (null) + std::vector const& getEntries( PortType ) const; + + std::vector& getEntries( PortType ); + + ConnectionPtrSet connections( PortType portType, PortIndex portIndex ) const; + + void setConnection( PortType portType, PortIndex portIndex, Connection& connection ); + + void eraseConnection( PortType portType, PortIndex portIndex, QUuid id ); + + ReactToConnectionState reaction() const; + + PortType reactingPortType() const; + + NodeDataType reactingDataType() const; + + void setReaction( ReactToConnectionState reaction, + PortType reactingPortType = PortType::None, + + NodeDataType reactingDataType = NodeDataType() ); + + bool isReacting() const; + + void setResizing( bool resizing ); + + bool resizing() const; + + private: + std::vector _inConnections; + std::vector _outConnections; + + ReactToConnectionState _reaction; + PortType _reactingPortType; + NodeDataType _reactingDataType; + + bool _resizing; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp new file mode 100644 index 00000000000..43c3866163f --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "Export.hpp" +#include "Style.hpp" + +namespace QtNodes { + +class NODE_EDITOR_PUBLIC NodeStyle : public Style +{ + public: + NodeStyle(); + + NodeStyle( QString jsonText ); + + public: + static void setNodeStyle( QString jsonText ); + + private: + void loadJsonText( QString jsonText ) override; + + void loadJsonFile( QString fileName ) override; + + void loadJsonFromByteArray( QByteArray const& byteArray ) override; + + public: + QColor NormalBoundaryColor; + QColor SelectedBoundaryColor; + QColor GradientColor0; + QColor GradientColor1; + QColor GradientColor2; + QColor GradientColor3; + QColor ShadowColor; + QColor FontColor; + QColor FontColorFaded; + + QColor ConnectionPointColor; + QColor FilledConnectionPointColor; + + QColor WarningColor; + QColor ErrorColor; + + float PenWidth; + float HoveredPenWidth; + + float ConnectionPointDiameter; + + float Opacity; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp new file mode 100644 index 00000000000..423894a1f4a --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp @@ -0,0 +1,49 @@ +#pragma once + +#if defined( __CYGWIN__ ) || defined( __CYGWIN32__ ) +# define NODE_EDITOR_PLATFORM "Cygwin" +# define NODE_EDITOR_PLATFORM_CYGWIN +# define NODE_EDITOR_PLATFORM_UNIX +# define NODE_EDITOR_PLATFORM_WINDOWS +#elif defined( _WIN16 ) || defined( _WIN32 ) || defined( _WIN64 ) || defined( __WIN32__ ) || \ + defined( __TOS_WIN__ ) || defined( __WINDOWS__ ) +# define NODE_EDITOR_PLATFORM "Windows" +# define NODE_EDITOR_PLATFORM_WINDOWS +#elif defined( macintosh ) || defined( Macintosh ) || defined( __TOS_MACOS__ ) || \ + ( defined( __APPLE__ ) && defined( __MACH__ ) ) +# define NODE_EDITOR_PLATFORM "Mac" +# define NODE_EDITOR_PLATFORM_MAC +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( linux ) || defined( __linux ) || defined( __linux__ ) || defined( __TOS_LINUX__ ) +# define NODE_EDITOR_PLATFORM "Linux" +# define NODE_EDITOR_PLATFORM_LINUX +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( __FreeBSD__ ) || defined( __OpenBSD__ ) || defined( __NetBSD__ ) || \ + defined( __bsdi__ ) || defined( __DragonFly__ ) +# define NODE_EDITOR_PLATFORM "BSD" +# define NODE_EDITOR_PLATFORM_BSD +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( sun ) || defined( __sun ) +# define NODE_EDITOR_PLATFORM "Solaris" +# define NODE_EDITOR_PLATFORM_SOLARIS +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( _AIX ) || defined( __TOS_AIX__ ) +# define NODE_EDITOR_PLATFORM "AIX" +# define NODE_EDITOR_PLATFORM_AIX +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( hpux ) || defined( _hpux ) || defined( __hpux ) +# define NODE_EDITOR_PLATFORM "HPUX" +# define NODE_EDITOR_PLATFORM_HPUX +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( __QNX__ ) +# define NODE_EDITOR_PLATFORM "QNX" +# define NODE_EDITOR_PLATFORM_QNX +# define NODE_EDITOR_PLATFORM_UNIX +#elif defined( unix ) || defined( __unix ) || defined( __unix__ ) +# define NODE_EDITOR_PLATFORM "Unix" +# define NODE_EDITOR_PLATFORM_UNIX +#endif + +#ifndef NODE_EDITOR_PLATFORM +# error "Current platform is not supported." +#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp new file mode 100644 index 00000000000..a39bcfddcfb --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace QtNodes { + +enum class PortType { None, In, Out }; + +static const int INVALID = -1; + +using PortIndex = int; + +struct Port { + PortType type; + + PortIndex index; + + Port() : type( PortType::None ), index( INVALID ) {} + + Port( PortType t, PortIndex i ) : type( t ), index( i ) {} + + bool indexIsValid() { return index != INVALID; } + + bool portTypeIsValid() { return type != PortType::None; } +}; + +// using PortAddress = std::pair; + +inline PortType oppositePort( PortType port ) { + PortType result = PortType::None; + + switch ( port ) { + case PortType::In: + result = PortType::Out; + break; + + case PortType::Out: + result = PortType::In; + break; + + default: + break; + } + + return result; +} +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp new file mode 100644 index 00000000000..191e882b3d3 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +#if ( QT_VERSION < QT_VERSION_CHECK( 5, 14, 0 ) ) + +// As of 5.14 there is a specialization std::hash + +# include + +# include +# include + +namespace std { +template <> +struct hash { + inline std::size_t operator()( QString const& s ) const { return qHash( s ); } +}; +} // namespace std + +#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp new file mode 100644 index 00000000000..ee33f3ff6a3 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include + +namespace std { +template <> +struct hash { + inline std::size_t operator()( QUuid const& uid ) const { return qHash( uid ); } +}; +} // namespace std diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp new file mode 100644 index 00000000000..1058b5d6ac7 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace QtNodes { + +class Serializable +{ + public: + virtual ~Serializable() = default; + + virtual QJsonObject save() const = 0; + + virtual void restore( QJsonObject const& /*p*/ ) {} +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp new file mode 100644 index 00000000000..6979b1e759b --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace QtNodes { + +class Style +{ + public: + virtual ~Style() = default; + + private: + virtual void loadJsonText( QString jsonText ) = 0; + + virtual void loadJsonFile( QString fileName ) = 0; + + virtual void loadJsonFromByteArray( QByteArray const& byteArray ) = 0; +}; + +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp new file mode 100644 index 00000000000..b56f159bce7 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "ConnectionStyle.hpp" +#include "Export.hpp" +#include "FlowViewStyle.hpp" +#include "NodeStyle.hpp" + +namespace QtNodes { + +class NODE_EDITOR_PUBLIC StyleCollection +{ + public: + static NodeStyle const& nodeStyle(); + + static ConnectionStyle const& connectionStyle(); + + static FlowViewStyle const& flowViewStyle(); + + public: + static void setNodeStyle( NodeStyle ); + + static void setConnectionStyle( ConnectionStyle ); + + static void setFlowViewStyle( FlowViewStyle ); + + private: + StyleCollection() = default; + + StyleCollection( StyleCollection const& ) = delete; + + StyleCollection& operator=( StyleCollection const& ) = delete; + + static StyleCollection& instance(); + + private: + NodeStyle _nodeStyle; + + ConnectionStyle _connectionStyle; + + FlowViewStyle _flowViewStyle; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp new file mode 100644 index 00000000000..740d178bd11 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "NodeData.hpp" +#include "memory.hpp" + +#include + +namespace QtNodes { + +using SharedNodeData = std::shared_ptr; + +// a function taking in NodeData and returning NodeData +using TypeConverter = std::function; + +// data-type-in, data-type-out +using TypeConverterId = std::pair; + +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp new file mode 100644 index 00000000000..7e0d5bf28b2 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace QtNodes { +namespace detail { +#if ( !defined( _MSC_VER ) && ( __cplusplus < 201300 ) ) || \ + ( defined( _MSC_VER ) && ( _MSC_VER < 1800 ) ) +//_MSC_VER == 1800 is Visual Studio 2013, which is already somewhat C++14 compilant, +// and it has make_unique in it's standard library implementation +template +std::unique_ptr make_unique( Args&&... args ) { + return std::unique_ptr( new T( std::forward( args )... ) ); +} +#else +template +std::unique_ptr make_unique( Args&&... args ) { + return std::make_unique( std::forward( args )... ); +} +#endif +} // namespace detail +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json b/src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json new file mode 100644 index 00000000000..8375b4a15d4 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json @@ -0,0 +1,42 @@ +{ + "FlowViewStyle": { + "BackgroundColor": [53, 53, 53], + "FineGridColor": [60, 60, 60], + "CoarseGridColor": [25, 25, 25] + }, + "NodeStyle": { + "NormalBoundaryColor": [255, 255, 255], + "SelectedBoundaryColor": [255, 165, 0], + "GradientColor0": "gray", + "GradientColor1": [80, 80, 80], + "GradientColor2": [64, 64, 64], + "GradientColor3": [58, 58, 58], + "ShadowColor": [20, 20, 20], + "FontColor" : "white", + "FontColorFaded" : "gray", + "ConnectionPointColor": [169, 169, 169], + "FilledConnectionPointColor": "cyan", + "ErrorColor": "red", + "WarningColor": [128, 128, 0], + + "PenWidth": 1.0, + "HoveredPenWidth": 1.5, + + "ConnectionPointDiameter": 8.0, + + "Opacity": 0.8 + }, + "ConnectionStyle": { + "ConstructionColor": "gray", + "NormalColor": "darkcyan", + "SelectedColor": [100, 100, 100], + "SelectedHaloColor": "orange", + "HoveredColor": "lightcyan", + + "LineWidth": 3.0, + "ConstructionLineWidth": 2.0, + "PointDiameter": 10.0, + + "UseDataDefinedColors": false + } +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/resources/convert.png b/src/Dataflow/QtGui/QtNodeEditor/resources/convert.png new file mode 100644 index 0000000000000000000000000000000000000000..f3797885497d0810382068634de30476db82f535 GIT binary patch literal 10203 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 BsEPmp literal 0 HcmV?d00001 diff --git a/src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc b/src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc new file mode 100644 index 00000000000..14ef4318183 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc @@ -0,0 +1,6 @@ + + + DefaultStyle.json + convert.png + + diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp new file mode 100644 index 00000000000..3425777527e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp @@ -0,0 +1,316 @@ +#include "Connection.hpp" + +#include +#include + +#include +#include + +#include "FlowScene.hpp" +#include "FlowView.hpp" +#include "Node.hpp" + +#include "NodeDataModel.hpp" +#include "NodeGeometry.hpp" +#include "NodeGraphicsObject.hpp" + +#include "ConnectionGeometry.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" + +using QtNodes::Connection; +using QtNodes::ConnectionGeometry; +using QtNodes::ConnectionGraphicsObject; +using QtNodes::ConnectionState; +using QtNodes::Node; +using QtNodes::NodeData; +using QtNodes::NodeDataType; +using QtNodes::PortIndex; +using QtNodes::PortType; +using QtNodes::TypeConverter; + +Connection::Connection( PortType portType, Node& node, PortIndex portIndex ) : + _uid( QUuid::createUuid() ), + _outPortIndex( INVALID ), + _inPortIndex( INVALID ), + _connectionState() { + setNodeToPort( node, portType, portIndex ); + + setRequiredPort( oppositePort( portType ) ); +} + +Connection::Connection( Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter typeConverter ) : + _uid( QUuid::createUuid() ), + _outNode( &nodeOut ), + _inNode( &nodeIn ), + _outPortIndex( portIndexOut ), + _inPortIndex( portIndexIn ), + _connectionState(), + _converter( std::move( typeConverter ) ) { + setNodeToPort( nodeIn, PortType::In, portIndexIn ); + setNodeToPort( nodeOut, PortType::Out, portIndexOut ); +} + +Connection::~Connection() { + if ( complete() ) { connectionMadeIncomplete( *this ); } + + propagateEmptyData(); + + if ( _inNode ) { _inNode->nodeGraphicsObject().update(); } + + if ( _outNode ) { _outNode->nodeGraphicsObject().update(); } +} + +QJsonObject Connection::save() const { + QJsonObject connectionJson; + + if ( _inNode && _outNode ) { + connectionJson["in_id"] = _inNode->id().toString(); + connectionJson["in_index"] = _inPortIndex; + + connectionJson["out_id"] = _outNode->id().toString(); + connectionJson["out_index"] = _outPortIndex; + + if ( _converter ) { + auto getTypeJson = [this]( PortType type ) { + QJsonObject typeJson; + NodeDataType nodeType = this->dataType( type ); + typeJson["id"] = nodeType.id; + typeJson["name"] = nodeType.name; + + return typeJson; + }; + + QJsonObject converterTypeJson; + + converterTypeJson["in"] = getTypeJson( PortType::In ); + converterTypeJson["out"] = getTypeJson( PortType::Out ); + + connectionJson["converter"] = converterTypeJson; + } + } + + return connectionJson; +} + +QUuid Connection::id() const { + return _uid; +} + +bool Connection::complete() const { + return _inNode != nullptr && _outNode != nullptr; +} + +void Connection::setRequiredPort( PortType dragging ) { + _connectionState.setRequiredPort( dragging ); + + switch ( dragging ) { + case PortType::Out: + _outNode = nullptr; + _outPortIndex = INVALID; + break; + + case PortType::In: + _inNode = nullptr; + _inPortIndex = INVALID; + break; + + default: + break; + } +} + +PortType Connection::requiredPort() const { + return _connectionState.requiredPort(); +} + +void Connection::setGraphicsObject( std::unique_ptr&& graphics ) { + _connectionGraphicsObject = std::move( graphics ); + + // This function is only called when the ConnectionGraphicsObject + // is newly created. At this moment both end coordinates are (0, 0) + // in Connection G.O. coordinates. The position of the whole + // Connection G. O. in scene coordinate system is also (0, 0). + // By moving the whole object to the Node Port position + // we position both connection ends correctly. + + if ( requiredPort() != PortType::None ) { + + PortType attachedPort = oppositePort( requiredPort() ); + + PortIndex attachedPortIndex = getPortIndex( attachedPort ); + + auto node = getNode( attachedPort ); + + QTransform nodeSceneTransform = node->nodeGraphicsObject().sceneTransform(); + + QPointF pos = node->nodeGeometry().portScenePosition( + attachedPortIndex, attachedPort, nodeSceneTransform ); + + _connectionGraphicsObject->setPos( pos ); + } + + _connectionGraphicsObject->move(); +} + +PortIndex Connection::getPortIndex( PortType portType ) const { + PortIndex result = INVALID; + + switch ( portType ) { + case PortType::In: + result = _inPortIndex; + break; + + case PortType::Out: + result = _outPortIndex; + + break; + + default: + break; + } + + return result; +} + +void Connection::setNodeToPort( Node& node, PortType portType, PortIndex portIndex ) { + bool wasIncomplete = !complete(); + + auto& nodeWeak = getNode( portType ); + + nodeWeak = &node; + + if ( portType == PortType::Out ) + _outPortIndex = portIndex; + else + _inPortIndex = portIndex; + + _connectionState.setNoRequiredPort(); + + updated( *this ); + if ( complete() && wasIncomplete ) { connectionCompleted( *this ); } +} + +void Connection::removeFromNodes() const { + if ( _inNode ) _inNode->nodeState().eraseConnection( PortType::In, _inPortIndex, id() ); + + if ( _outNode ) _outNode->nodeState().eraseConnection( PortType::Out, _outPortIndex, id() ); +} + +ConnectionGraphicsObject& Connection::getConnectionGraphicsObject() const { + return *_connectionGraphicsObject; +} + +ConnectionState& Connection::connectionState() { + return _connectionState; +} + +ConnectionState const& Connection::connectionState() const { + return _connectionState; +} + +ConnectionGeometry& Connection::connectionGeometry() { + return _connectionGeometry; +} + +ConnectionGeometry const& Connection::connectionGeometry() const { + return _connectionGeometry; +} + +Node* Connection::getNode( PortType portType ) const { + switch ( portType ) { + case PortType::In: + return _inNode; + break; + + case PortType::Out: + return _outNode; + break; + + default: + // not possible + break; + } + return nullptr; +} + +Node*& Connection::getNode( PortType portType ) { + switch ( portType ) { + case PortType::In: + return _inNode; + break; + + case PortType::Out: + return _outNode; + break; + + default: + // not possible + break; + } + Q_UNREACHABLE(); +} + +void Connection::clearNode( PortType portType ) { + if ( complete() ) { connectionMadeIncomplete( *this ); } + + getNode( portType ) = nullptr; + + if ( portType == PortType::In ) + _inPortIndex = INVALID; + else + _outPortIndex = INVALID; +} + +NodeDataType Connection::dataType( PortType portType ) const { + if ( _inNode && _outNode ) { + auto const& model = + ( portType == PortType::In ) ? _inNode->nodeDataModel() : _outNode->nodeDataModel(); + PortIndex index = ( portType == PortType::In ) ? _inPortIndex : _outPortIndex; + + return model->dataType( portType, index ); + } + else { + Node* validNode; + PortIndex index = INVALID; + + if ( ( validNode = _inNode ) ) { + index = _inPortIndex; + portType = PortType::In; + } + else if ( ( validNode = _outNode ) ) { + index = _outPortIndex; + portType = PortType::Out; + } + + if ( validNode ) { + auto const& model = validNode->nodeDataModel(); + + return model->dataType( portType, index ); + } + } + + Q_UNREACHABLE(); +} + +void Connection::setTypeConverter( TypeConverter converter ) { + _converter = std::move( converter ); +} + +void Connection::propagateData( std::shared_ptr nodeData ) const { + if ( _inNode ) { + if ( _converter ) { nodeData = _converter( nodeData ); } + + _inNode->propagateData( nodeData, _inPortIndex ); + } +} + +void Connection::propagateEmptyData() const { + std::shared_ptr emptyData; + + propagateData( emptyData ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp new file mode 100644 index 00000000000..7d2922494a8 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp @@ -0,0 +1,21 @@ +#include "ConnectionBlurEffect.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionPainter.hpp" + +using QtNodes::ConnectionBlurEffect; +using QtNodes::ConnectionGraphicsObject; + +ConnectionBlurEffect::ConnectionBlurEffect( ConnectionGraphicsObject* ) { + // +} + +void ConnectionBlurEffect::draw( QPainter* painter ) { + QGraphicsBlurEffect::draw( painter ); + + // ConnectionPainter::paint(painter, + //_object->connectionGeometry(), + //_object->connectionState()); + + //_item->paint(painter, nullptr, nullptr); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp new file mode 100644 index 00000000000..f730a2a50b4 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp @@ -0,0 +1,19 @@ +#include + +#include + +namespace QtNodes { + +class ConnectionGraphicsObject; + +class ConnectionBlurEffect : public QGraphicsBlurEffect +{ + + public: + ConnectionBlurEffect( ConnectionGraphicsObject* item ); + + void draw( QPainter* painter ) override; + + private: +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp new file mode 100644 index 00000000000..2212abc988d --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp @@ -0,0 +1,103 @@ +#include "ConnectionGeometry.hpp" + +#include + +#include "StyleCollection.hpp" + +using QtNodes::ConnectionGeometry; +using QtNodes::PortType; + +ConnectionGeometry::ConnectionGeometry() : + _in( 0, 0 ), + _out( 0, 0 ) + //, _animationPhase(0) + , + _lineWidth( 3.0 ), + _hovered( false ) {} + +QPointF const& ConnectionGeometry::getEndPoint( PortType portType ) const { + Q_ASSERT( portType != PortType::None ); + + return ( portType == PortType::Out ? _out : _in ); +} + +void ConnectionGeometry::setEndPoint( PortType portType, QPointF const& point ) { + switch ( portType ) { + case PortType::Out: + _out = point; + break; + + case PortType::In: + _in = point; + break; + + default: + break; + } +} + +void ConnectionGeometry::moveEndPoint( PortType portType, QPointF const& offset ) { + switch ( portType ) { + case PortType::Out: + _out += offset; + break; + + case PortType::In: + _in += offset; + break; + + default: + break; + } +} + +QRectF ConnectionGeometry::boundingRect() const { + auto points = pointsC1C2(); + + QRectF basicRect = QRectF( _out, _in ).normalized(); + + QRectF c1c2Rect = QRectF( points.first, points.second ).normalized(); + + auto const& connectionStyle = StyleCollection::connectionStyle(); + + float const diam = connectionStyle.pointDiameter(); + + QRectF commonRect = basicRect.united( c1c2Rect ); + + QPointF const cornerOffset( diam, diam ); + + commonRect.setTopLeft( commonRect.topLeft() - cornerOffset ); + commonRect.setBottomRight( commonRect.bottomRight() + 2 * cornerOffset ); + + return commonRect; +} + +std::pair ConnectionGeometry::pointsC1C2() const { + const double defaultOffset = 200; + + double xDistance = _in.x() - _out.x(); + + double horizontalOffset = qMin( defaultOffset, std::abs( xDistance ) ); + + double verticalOffset = 0; + + double ratioX = 0.5; + + if ( xDistance <= 0 ) { + double yDistance = _in.y() - _out.y() + 20; + + double vector = yDistance < 0 ? -1.0 : 1.0; + + verticalOffset = qMin( defaultOffset, std::abs( yDistance ) ) * vector; + + ratioX = 1.0; + } + + horizontalOffset *= ratioX; + + QPointF c1( _out.x() + horizontalOffset, _out.y() + verticalOffset ); + + QPointF c2( _in.x() - horizontalOffset, _in.y() - verticalOffset ); + + return std::make_pair( c1, c2 ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp new file mode 100644 index 00000000000..cbb0b71d3c6 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp @@ -0,0 +1,187 @@ +#include "ConnectionGraphicsObject.hpp" + +#include +#include +#include +#include +#include + +#include "FlowScene.hpp" + +#include "Connection.hpp" +#include "ConnectionBlurEffect.hpp" +#include "ConnectionGeometry.hpp" +#include "ConnectionPainter.hpp" +#include "ConnectionState.hpp" + +#include "NodeGraphicsObject.hpp" + +#include "NodeConnectionInteraction.hpp" + +#include "Node.hpp" + +using QtNodes::Connection; +using QtNodes::ConnectionGraphicsObject; +using QtNodes::FlowScene; + +ConnectionGraphicsObject::ConnectionGraphicsObject( FlowScene& scene, Connection& connection ) : + _scene( scene ), _connection( connection ) { + _scene.addItem( this ); + + setFlag( QGraphicsItem::ItemIsMovable, true ); + setFlag( QGraphicsItem::ItemIsFocusable, true ); + setFlag( QGraphicsItem::ItemIsSelectable, true ); + + setAcceptHoverEvents( true ); + + // addGraphicsEffect(); + + setZValue( -1.0 ); +} + +ConnectionGraphicsObject::~ConnectionGraphicsObject() { + _scene.removeItem( this ); +} + +QtNodes::Connection& ConnectionGraphicsObject::connection() { + return _connection; +} + +QRectF ConnectionGraphicsObject::boundingRect() const { + return _connection.connectionGeometry().boundingRect(); +} + +QPainterPath ConnectionGraphicsObject::shape() const { +#ifdef DEBUG_DRAWING + + // QPainterPath path; + + // path.addRect(boundingRect()); + // return path; + +#else + auto const& geom = _connection.connectionGeometry(); + + return ConnectionPainter::getPainterStroke( geom ); + +#endif +} + +void ConnectionGraphicsObject::setGeometryChanged() { + prepareGeometryChange(); +} + +void ConnectionGraphicsObject::move() { + for ( PortType portType : { PortType::In, PortType::Out } ) { + if ( auto node = _connection.getNode( portType ) ) { + auto const& nodeGraphics = node->nodeGraphicsObject(); + + auto const& nodeGeom = node->nodeGeometry(); + + QPointF scenePos = nodeGeom.portScenePosition( + _connection.getPortIndex( portType ), portType, nodeGraphics.sceneTransform() ); + + QTransform sceneTransform = this->sceneTransform(); + + QPointF connectionPos = sceneTransform.inverted().map( scenePos ); + + _connection.connectionGeometry().setEndPoint( portType, connectionPos ); + + _connection.getConnectionGraphicsObject().setGeometryChanged(); + _connection.getConnectionGraphicsObject().update(); + } + } +} + +void ConnectionGraphicsObject::lock( bool locked ) { + setFlag( QGraphicsItem::ItemIsMovable, !locked ); + setFlag( QGraphicsItem::ItemIsFocusable, !locked ); + setFlag( QGraphicsItem::ItemIsSelectable, !locked ); +} + +void ConnectionGraphicsObject::paint( QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget* ) { + painter->setClipRect( option->exposedRect ); + + ConnectionPainter::paint( painter, _connection ); +} + +void ConnectionGraphicsObject::mousePressEvent( QGraphicsSceneMouseEvent* event ) { + QGraphicsItem::mousePressEvent( event ); + // event->ignore(); +} + +void ConnectionGraphicsObject::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) { + prepareGeometryChange(); + + auto view = static_cast( event->widget() ); + auto node = locateNodeAt( event->scenePos(), _scene, view->transform() ); + + auto& state = _connection.connectionState(); + + state.interactWithNode( node ); + if ( node ) { + node->reactToPossibleConnection( + state.requiredPort(), + _connection.dataType( oppositePort( state.requiredPort() ) ), + event->scenePos() ); + } + + //------------------- + + QPointF offset = event->pos() - event->lastPos(); + + auto requiredPort = _connection.requiredPort(); + + if ( requiredPort != PortType::None ) { + _connection.connectionGeometry().moveEndPoint( requiredPort, offset ); + } + + //------------------- + + update(); + + event->accept(); +} + +void ConnectionGraphicsObject::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) { + ungrabMouse(); + event->accept(); + auto node = locateNodeAt( event->scenePos(), _scene, _scene.views()[0]->transform() ); + + if ( node ) { + NodeConnectionInteraction interaction( *node, _connection, _scene ); + if ( interaction.tryConnect() ) { node->resetReactionToConnection(); } + } + + if ( _connection.connectionState().requiresPort() ) { _scene.deleteConnection( _connection ); } +} + +void ConnectionGraphicsObject::hoverEnterEvent( QGraphicsSceneHoverEvent* event ) { + _connection.connectionGeometry().setHovered( true ); + + update(); + _scene.connectionHovered( connection(), event->screenPos() ); + event->accept(); +} + +void ConnectionGraphicsObject::hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) { + _connection.connectionGeometry().setHovered( false ); + + update(); + _scene.connectionHoverLeft( connection() ); + event->accept(); +} + +void ConnectionGraphicsObject::addGraphicsEffect() { + auto effect = new QGraphicsBlurEffect; + + effect->setBlurRadius( 5 ); + setGraphicsEffect( effect ); + + // auto effect = new QGraphicsDropShadowEffect; + // auto effect = new ConnectionBlurEffect(this); + // effect->setOffset(4, 4); + // effect->setColor(QColor(Qt::gray).darker(800)); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp new file mode 100644 index 00000000000..263c2e87e84 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp @@ -0,0 +1,256 @@ +#include "ConnectionPainter.hpp" + +#include + +#include "Connection.hpp" +#include "ConnectionGeometry.hpp" +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" + +#include "NodeData.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::Connection; +using QtNodes::ConnectionGeometry; +using QtNodes::ConnectionPainter; + +static QPainterPath cubicPath( ConnectionGeometry const& geom ) { + QPointF const& source = geom.source(); + QPointF const& sink = geom.sink(); + + auto c1c2 = geom.pointsC1C2(); + + // cubic spline + QPainterPath cubic( source ); + + cubic.cubicTo( c1c2.first, c1c2.second, sink ); + + return cubic; +} + +QPainterPath ConnectionPainter::getPainterStroke( ConnectionGeometry const& geom ) { + auto cubic = cubicPath( geom ); + + QPointF const& source = geom.source(); + QPainterPath result( source ); + + unsigned segments = 20; + + for ( auto i = 0ul; i < segments; ++i ) { + double ratio = double( i + 1 ) / segments; + result.lineTo( cubic.pointAtPercent( ratio ) ); + } + + QPainterPathStroker stroker; + stroker.setWidth( 10.0 ); + + return stroker.createStroke( result ); +} + +#ifdef NODE_DEBUG_DRAWING +static void debugDrawing( QPainter* painter, Connection const& connection ) { + Q_UNUSED( painter ); + Q_UNUSED( connection ); + ConnectionGeometry const& geom = connection.connectionGeometry(); + + { + QPointF const& source = geom.source(); + QPointF const& sink = geom.sink(); + + auto points = geom.pointsC1C2(); + + painter->setPen( Qt::red ); + painter->setBrush( Qt::red ); + + painter->drawLine( QLineF( source, points.first ) ); + painter->drawLine( QLineF( points.first, points.second ) ); + painter->drawLine( QLineF( points.second, sink ) ); + painter->drawEllipse( points.first, 3, 3 ); + painter->drawEllipse( points.second, 3, 3 ); + + painter->setBrush( Qt::NoBrush ); + + painter->drawPath( cubicPath( geom ) ); + } + + { + painter->setPen( Qt::yellow ); + + painter->drawRect( geom.boundingRect() ); + } +} +#endif + +static void drawSketchLine( QPainter* painter, Connection const& connection ) { + using QtNodes::ConnectionState; + + ConnectionState const& state = connection.connectionState(); + + if ( state.requiresPort() ) { + auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); + + QPen p; + p.setWidth( connectionStyle.constructionLineWidth() ); + p.setColor( connectionStyle.constructionColor() ); + p.setStyle( Qt::DashLine ); + + painter->setPen( p ); + painter->setBrush( Qt::NoBrush ); + + using QtNodes::ConnectionGeometry; + ConnectionGeometry const& geom = connection.connectionGeometry(); + + auto cubic = cubicPath( geom ); + // cubic spline + painter->drawPath( cubic ); + } +} + +static void drawHoveredOrSelected( QPainter* painter, Connection const& connection ) { + using QtNodes::ConnectionGeometry; + + ConnectionGeometry const& geom = connection.connectionGeometry(); + bool const hovered = geom.hovered(); + + auto const& graphicsObject = connection.getConnectionGraphicsObject(); + + bool const selected = graphicsObject.isSelected(); + + // drawn as a fat background + if ( hovered || selected ) { + QPen p; + + auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); + double const lineWidth = connectionStyle.lineWidth(); + + p.setWidth( 2 * lineWidth ); + p.setColor( selected ? connectionStyle.selectedHaloColor() + : connectionStyle.hoveredColor() ); + + painter->setPen( p ); + painter->setBrush( Qt::NoBrush ); + + // cubic spline + auto cubic = cubicPath( geom ); + painter->drawPath( cubic ); + } +} + +static void drawNormalLine( QPainter* painter, Connection const& connection ) { + using QtNodes::ConnectionState; + + ConnectionState const& state = connection.connectionState(); + + if ( state.requiresPort() ) return; + + // colors + + auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); + + QColor normalColorOut = connectionStyle.normalColor(); + QColor normalColorIn = connectionStyle.normalColor(); + QColor selectedColor = connectionStyle.selectedColor(); + + bool gradientColor = false; + + if ( connectionStyle.useDataDefinedColors() ) { + using QtNodes::PortType; + + auto dataTypeOut = connection.dataType( PortType::Out ); + auto dataTypeIn = connection.dataType( PortType::In ); + + gradientColor = ( dataTypeOut.id != dataTypeIn.id ); + + normalColorOut = connectionStyle.normalColor( dataTypeOut.id ); + normalColorIn = connectionStyle.normalColor( dataTypeIn.id ); + selectedColor = normalColorOut.darker( 200 ); + } + + // geometry + + ConnectionGeometry const& geom = connection.connectionGeometry(); + + double const lineWidth = connectionStyle.lineWidth(); + + // draw normal line + QPen p; + + p.setWidth( lineWidth ); + + auto const& graphicsObject = connection.getConnectionGraphicsObject(); + bool const selected = graphicsObject.isSelected(); + + auto cubic = cubicPath( geom ); + if ( gradientColor ) { + painter->setBrush( Qt::NoBrush ); + + QColor c = normalColorOut; + if ( selected ) c = c.darker( 200 ); + p.setColor( c ); + painter->setPen( p ); + + unsigned int const segments = 60; + + for ( unsigned int i = 0ul; i < segments; ++i ) { + double ratioPrev = double( i ) / segments; + double ratio = double( i + 1 ) / segments; + + if ( i == segments / 2 ) { + QColor cn = normalColorIn; + if ( selected ) cn = cn.darker( 200 ); + + p.setColor( cn ); + painter->setPen( p ); + } + painter->drawLine( cubic.pointAtPercent( ratioPrev ), cubic.pointAtPercent( ratio ) ); + } + + { + QIcon icon( ":convert.png" ); + + QPixmap pixmap = icon.pixmap( QSize( 22, 22 ) ); + painter->drawPixmap( cubic.pointAtPercent( 0.50 ) - + QPoint( pixmap.width() / 2, pixmap.height() / 2 ), + pixmap ); + } + } + else { + p.setColor( normalColorOut ); + + if ( selected ) { p.setColor( selectedColor ); } + + painter->setPen( p ); + painter->setBrush( Qt::NoBrush ); + + painter->drawPath( cubic ); + } +} + +void ConnectionPainter::paint( QPainter* painter, Connection const& connection ) { + drawHoveredOrSelected( painter, connection ); + + drawSketchLine( painter, connection ); + + drawNormalLine( painter, connection ); + +#ifdef NODE_DEBUG_DRAWING + debugDrawing( painter, connection ); +#endif + + // draw end points + ConnectionGeometry const& geom = connection.connectionGeometry(); + + QPointF const& source = geom.source(); + QPointF const& sink = geom.sink(); + + auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); + + double const pointDiameter = connectionStyle.pointDiameter(); + + painter->setPen( connectionStyle.constructionColor() ); + painter->setBrush( connectionStyle.constructionColor() ); + double const pointRadius = pointDiameter / 2.0; + painter->drawEllipse( source, pointRadius, pointRadius ); + painter->drawEllipse( sink, pointRadius, pointRadius ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp new file mode 100644 index 00000000000..681391ae8f1 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace QtNodes { + +class ConnectionGeometry; +class ConnectionState; +class Connection; + +class ConnectionPainter +{ + public: + static void paint( QPainter* painter, Connection const& connection ); + + static QPainterPath getPainterStroke( ConnectionGeometry const& geom ); +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp new file mode 100644 index 00000000000..f9f86f85378 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp @@ -0,0 +1,32 @@ +#include "ConnectionState.hpp" + +#include + +#include + +#include "FlowScene.hpp" +#include "Node.hpp" + +using QtNodes::ConnectionState; +using QtNodes::Node; + +ConnectionState::~ConnectionState() { + resetLastHoveredNode(); +} + +void ConnectionState::interactWithNode( Node* node ) { + if ( node ) { _lastHoveredNode = node; } + else { + resetLastHoveredNode(); + } +} + +void ConnectionState::setLastHoveredNode( Node* node ) { + _lastHoveredNode = node; +} + +void ConnectionState::resetLastHoveredNode() { + if ( _lastHoveredNode ) _lastHoveredNode->resetReactionToConnection(); + + _lastHoveredNode = nullptr; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp new file mode 100644 index 00000000000..de66e99e486 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp @@ -0,0 +1,173 @@ +#include "ConnectionStyle.hpp" + +#include + +#include +#include +#include +#include +#include + +#include + +#include "StyleCollection.hpp" + +using QtNodes::ConnectionStyle; + +inline void initResources() { + Q_INIT_RESOURCE( resources ); +} + +ConnectionStyle::ConnectionStyle() { + // Explicit resources inialization for preventing the static initialization + // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order + initResources(); + + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile( ":DefaultStyle.json" ); +} + +ConnectionStyle::ConnectionStyle( QString jsonText ) { + loadJsonFile( ":DefaultStyle.json" ); + loadJsonText( jsonText ); +} + +void ConnectionStyle::setConnectionStyle( QString jsonText ) { + ConnectionStyle style( jsonText ); + + StyleCollection::setConnectionStyle( style ); +} + +#ifdef STYLE_DEBUG +# define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) \ + { \ + if ( v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null ) \ + qWarning() << "Undefined value for parameter:" << #variable; \ + } +#else +# define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) +#endif + +#define CONNECTION_VALUE_EXISTS( v ) \ + ( v.type() != QJsonValue::Undefined && v.type() != QJsonValue::Null ) + +#define CONNECTION_STYLE_READ_COLOR( values, variable ) \ + { \ + auto valueRef = values[#variable]; \ + CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ + if ( CONNECTION_VALUE_EXISTS( valueRef ) ) { \ + if ( valueRef.isArray() ) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve( 3 ); \ + for ( auto it = colorArray.begin(); it != colorArray.end(); ++it ) { \ + rgb.push_back( ( *it ).toInt() ); \ + } \ + variable = QColor( rgb[0], rgb[1], rgb[2] ); \ + } \ + else { \ + variable = QColor( valueRef.toString() ); \ + } \ + } \ + } + +#define CONNECTION_STYLE_READ_FLOAT( values, variable ) \ + { \ + auto valueRef = values[#variable]; \ + CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ + if ( CONNECTION_VALUE_EXISTS( valueRef ) ) variable = valueRef.toDouble(); \ + } + +#define CONNECTION_STYLE_READ_BOOL( values, variable ) \ + { \ + auto valueRef = values[#variable]; \ + CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ + if ( CONNECTION_VALUE_EXISTS( valueRef ) ) variable = valueRef.toBool(); \ + } + +void ConnectionStyle::loadJsonFile( QString styleFile ) { + QFile file( styleFile ); + + if ( !file.open( QIODevice::ReadOnly ) ) { + qWarning() << "Couldn't open file " << styleFile; + + return; + } + + loadJsonFromByteArray( file.readAll() ); +} + +void ConnectionStyle::loadJsonText( QString jsonText ) { + loadJsonFromByteArray( jsonText.toUtf8() ); +} + +void ConnectionStyle::loadJsonFromByteArray( QByteArray const& byteArray ) { + QJsonDocument json( QJsonDocument::fromJson( byteArray ) ); + + QJsonObject topLevelObject = json.object(); + + QJsonValueRef nodeStyleValues = topLevelObject["ConnectionStyle"]; + + QJsonObject obj = nodeStyleValues.toObject(); + + CONNECTION_STYLE_READ_COLOR( obj, ConstructionColor ); + CONNECTION_STYLE_READ_COLOR( obj, NormalColor ); + CONNECTION_STYLE_READ_COLOR( obj, SelectedColor ); + CONNECTION_STYLE_READ_COLOR( obj, SelectedHaloColor ); + CONNECTION_STYLE_READ_COLOR( obj, HoveredColor ); + + CONNECTION_STYLE_READ_FLOAT( obj, LineWidth ); + CONNECTION_STYLE_READ_FLOAT( obj, ConstructionLineWidth ); + CONNECTION_STYLE_READ_FLOAT( obj, PointDiameter ); + + CONNECTION_STYLE_READ_BOOL( obj, UseDataDefinedColors ); +} + +QColor ConnectionStyle::constructionColor() const { + return ConstructionColor; +} + +QColor ConnectionStyle::normalColor() const { + return NormalColor; +} + +QColor ConnectionStyle::normalColor( QString typeId ) const { + std::size_t hash = qHash( typeId ); + + std::size_t const hue_range = 0xFF; + + std::srand( hash ); + std::size_t hue = std::rand() % hue_range; + + std::size_t sat = 120 + hash % 129; + + return QColor::fromHsl( hue, sat, 160 ); +} + +QColor ConnectionStyle::selectedColor() const { + return SelectedColor; +} + +QColor ConnectionStyle::selectedHaloColor() const { + return SelectedHaloColor; +} + +QColor ConnectionStyle::hoveredColor() const { + return HoveredColor; +} + +float ConnectionStyle::lineWidth() const { + return LineWidth; +} + +float ConnectionStyle::constructionLineWidth() const { + return ConstructionLineWidth; +} + +float ConnectionStyle::pointDiameter() const { + return PointDiameter; +} + +bool ConnectionStyle::useDataDefinedColors() const { + return UseDataDefinedColors; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp new file mode 100644 index 00000000000..6427712d846 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp @@ -0,0 +1,42 @@ +#include "DataModelRegistry.hpp" + +#include +#include + +using QtNodes::DataModelRegistry; +using QtNodes::NodeDataModel; +using QtNodes::NodeDataType; +using QtNodes::TypeConverter; + +std::unique_ptr DataModelRegistry::create( QString const& modelName ) { + auto it = _registeredItemCreators.find( modelName ); + + if ( it != _registeredItemCreators.end() ) { return it->second(); } + + return nullptr; +} + +DataModelRegistry::RegisteredModelCreatorsMap const& +DataModelRegistry::registeredModelCreators() const { + return _registeredItemCreators; +} + +DataModelRegistry::RegisteredModelsCategoryMap const& +DataModelRegistry::registeredModelsCategoryAssociation() const { + return _registeredModelsCategory; +} + +DataModelRegistry::CategoriesSet const& DataModelRegistry::categories() const { + return _categories; +} + +TypeConverter DataModelRegistry::getTypeConverter( NodeDataType const& d1, + NodeDataType const& d2 ) const { + TypeConverterId converterId = std::make_pair( d1, d2 ); + + auto it = _registeredTypeConverters.find( converterId ); + + if ( it != _registeredTypeConverters.end() ) { return it->second; } + + return TypeConverter {}; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp new file mode 100644 index 00000000000..7440646d443 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp @@ -0,0 +1,532 @@ +#include "FlowScene.hpp" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Node.hpp" +#include "NodeGraphicsObject.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "NodeGraphicsObject.hpp" + +#include "Connection.hpp" + +#include "DataModelRegistry.hpp" +#include "FlowView.hpp" + +using QtNodes::Connection; +using QtNodes::DataModelRegistry; +using QtNodes::FlowScene; +using QtNodes::Node; +using QtNodes::NodeDataModel; +using QtNodes::NodeGraphicsObject; +using QtNodes::PortIndex; +using QtNodes::PortType; +using QtNodes::TypeConverter; + +FlowScene::FlowScene( std::shared_ptr registry, QObject* parent ) : + QGraphicsScene( parent ), _registry( std::move( registry ) ) { + setItemIndexMethod( QGraphicsScene::NoIndex ); + + // This connection should come first + connect( this, &FlowScene::connectionCreated, this, &FlowScene::setupConnectionSignals ); + connect( this, &FlowScene::connectionCreated, this, &FlowScene::sendConnectionCreatedToNodes ); + connect( this, &FlowScene::connectionDeleted, this, &FlowScene::sendConnectionDeletedToNodes ); +} + +FlowScene::FlowScene( QObject* parent ) : + FlowScene( std::make_shared(), parent ) {} + +FlowScene::~FlowScene() { + clearScene(); +} + +//------------------------------------------------------------------------------ + +std::shared_ptr +FlowScene::createConnection( PortType connectedPort, Node& node, PortIndex portIndex ) { + auto connection = std::make_shared( connectedPort, node, portIndex ); + + auto cgo = detail::make_unique( *this, *connection ); + + // after this function connection points are set to node port + connection->setGraphicsObject( std::move( cgo ) ); + + _connections[connection->id()] = connection; + + // Note: this connection isn't truly created yet. It's only partially created. + // Thus, don't send the connectionCreated(...) signal. + + connect( connection.get(), + &Connection::connectionCompleted, + this, + [this]( Connection const& c ) { connectionCreated( c ); } ); + + return connection; +} + +std::shared_ptr FlowScene::createConnection( Node& nodeIn, + PortIndex portIndexIn, + Node& nodeOut, + PortIndex portIndexOut, + TypeConverter const& converter ) { + auto connection = + std::make_shared( nodeIn, portIndexIn, nodeOut, portIndexOut, converter ); + + auto cgo = detail::make_unique( *this, *connection ); + + nodeIn.nodeState().setConnection( PortType::In, portIndexIn, *connection ); + nodeOut.nodeState().setConnection( PortType::Out, portIndexOut, *connection ); + + // after this function connection points are set to node port + connection->setGraphicsObject( std::move( cgo ) ); + + // trigger data propagation + nodeOut.onDataUpdated( portIndexOut ); + + _connections[connection->id()] = connection; + + connectionCreated( *connection ); + + return connection; +} + +std::shared_ptr FlowScene::restoreConnection( QJsonObject const& connectionJson ) { + QUuid nodeInId = QUuid( connectionJson["in_id"].toString() ); + QUuid nodeOutId = QUuid( connectionJson["out_id"].toString() ); + + PortIndex portIndexIn = connectionJson["in_index"].toInt(); + PortIndex portIndexOut = connectionJson["out_index"].toInt(); + + auto nodeIn = _nodes[nodeInId].get(); + auto nodeOut = _nodes[nodeOutId].get(); + + auto getConverter = [&]() { + QJsonValue converterVal = connectionJson["converter"]; + + if ( !converterVal.isUndefined() ) { + QJsonObject converterJson = converterVal.toObject(); + + NodeDataType inType { converterJson["in"].toObject()["id"].toString(), + converterJson["in"].toObject()["name"].toString() }; + + NodeDataType outType { converterJson["out"].toObject()["id"].toString(), + converterJson["out"].toObject()["name"].toString() }; + + auto converter = registry().getTypeConverter( outType, inType ); + + if ( converter ) return converter; + } + + return TypeConverter {}; + }; + + std::shared_ptr connection = + createConnection( *nodeIn, portIndexIn, *nodeOut, portIndexOut, getConverter() ); + + // Note: the connectionCreated(...) signal has already been sent + // by createConnection(...) + + return connection; +} + +void FlowScene::importConnection( const QString& fromNodeId, + int fromNodePort, + const QString& toNodeId, + int toNodePort ) { + QUuid nodeInId = QUuid( toNodeId ); + QUuid nodeOutId = QUuid( fromNodeId ); + auto nodeIn = _nodes[nodeInId].get(); + auto nodeOut = _nodes[nodeOutId].get(); + + auto connection = std::make_shared( + *nodeIn, toNodePort, *nodeOut, fromNodePort, TypeConverter {} ); + + auto cgo = detail::make_unique( *this, *connection ); + + nodeIn->nodeState().setConnection( PortType::In, toNodePort, *connection ); + nodeOut->nodeState().setConnection( PortType::Out, fromNodePort, *connection ); + + nodeIn->nodeDataModel()->updateState(); + + // after this function connection points are set to node port + connection->setGraphicsObject( std::move( cgo ) ); + + _connections[connection->id()] = connection; +} + +void FlowScene::deleteConnection( Connection& connection ) { + auto it = _connections.find( connection.id() ); + if ( it != _connections.end() ) { + connection.removeFromNodes(); + _connections.erase( it ); + } +} + +Node& FlowScene::createNode( std::unique_ptr&& dataModel ) { + auto node = detail::make_unique( std::move( dataModel ) ); + auto ngo = detail::make_unique( *this, *node ); + + node->setGraphicsObject( std::move( ngo ) ); + + // generates the uuid of the node (delegated to nodeDataModel implementation) + node->_uid = QUuid( node->nodeDataModel()->uuid() ); + + auto nodePtr = node.get(); + _nodes[node->id()] = std::move( node ); + + nodeCreated( *nodePtr ); + return *nodePtr; +} + +Node& FlowScene::importNode( std::unique_ptr&& dataModel ) { + auto node = detail::make_unique( std::move( dataModel ) ); + auto ngo = detail::make_unique( *this, *node ); + node->setGraphicsObject( std::move( ngo ) ); + node->import(); + + auto nodePtr = node.get(); + _nodes[node->id()] = std::move( node ); + + nodePlaced( *nodePtr ); + nodeCreated( *nodePtr ); + return *nodePtr; +} + +Node& FlowScene::restoreNode( QJsonObject const& nodeJson ) { + QString modelName = nodeJson["model"].toObject()["name"].toString(); + + auto dataModel = registry().create( modelName ); + + if ( !dataModel ) + throw std::logic_error( std::string( "No registered model with name " ) + + modelName.toLocal8Bit().data() ); + + auto node = detail::make_unique( std::move( dataModel ) ); + auto ngo = detail::make_unique( *this, *node ); + node->setGraphicsObject( std::move( ngo ) ); + + node->restore( nodeJson ); + + auto nodePtr = node.get(); + _nodes[node->id()] = std::move( node ); + + nodePlaced( *nodePtr ); + nodeCreated( *nodePtr ); + return *nodePtr; +} + +void FlowScene::removeNode( Node& node ) { + // call signal + nodeDeleted( node ); + + for ( auto portType : { PortType::In, PortType::Out } ) { + auto nodeState = node.nodeState(); + auto const& nodeEntries = nodeState.getEntries( portType ); + + for ( auto& connections : nodeEntries ) { + for ( auto const& pair : connections ) + deleteConnection( *pair.second ); + } + } + + _nodes.erase( node.id() ); +} + +DataModelRegistry& FlowScene::registry() const { + return *_registry; +} + +void FlowScene::setRegistry( std::shared_ptr registry ) { + _registry = std::move( registry ); +} + +void FlowScene::iterateOverNodes( std::function const& visitor ) { + for ( const auto& _node : _nodes ) { + visitor( _node.second.get() ); + } +} + +void FlowScene::iterateOverNodeData( std::function const& visitor ) { + for ( const auto& _node : _nodes ) { + visitor( _node.second->nodeDataModel() ); + } +} + +void FlowScene::iterateOverNodeDataDependentOrder( + std::function const& visitor ) { + std::set visitedNodesSet; + + // A leaf node is a node with no input ports, or all possible input ports empty + auto isNodeLeaf = []( Node const& node, NodeDataModel const& model ) { + for ( unsigned int i = 0; i < model.nPorts( PortType::In ); ++i ) { + auto connections = node.nodeState().connections( PortType::In, i ); + if ( !connections.empty() ) { return false; } + } + + return true; + }; + + // Iterate over "leaf" nodes + for ( auto const& _node : _nodes ) { + auto const& node = _node.second; + auto model = node->nodeDataModel(); + + if ( isNodeLeaf( *node, *model ) ) { + visitor( model ); + visitedNodesSet.insert( node->id() ); + } + } + + auto areNodeInputsVisitedBefore = [&]( Node const& node, NodeDataModel const& model ) { + for ( size_t i = 0; i < model.nPorts( PortType::In ); ++i ) { + auto connections = node.nodeState().connections( PortType::In, i ); + + for ( auto& conn : connections ) { + if ( visitedNodesSet.find( conn.second->getNode( PortType::Out )->id() ) == + visitedNodesSet.end() ) { + return false; + } + } + } + + return true; + }; + + // Iterate over dependent nodes + while ( _nodes.size() != visitedNodesSet.size() ) { + for ( auto const& _node : _nodes ) { + auto const& node = _node.second; + if ( visitedNodesSet.find( node->id() ) != visitedNodesSet.end() ) continue; + + auto model = node->nodeDataModel(); + + if ( areNodeInputsVisitedBefore( *node, *model ) ) { + visitor( model ); + visitedNodesSet.insert( node->id() ); + } + } + } +} + +QPointF FlowScene::getNodePosition( const Node& node ) const { + return node.nodeGraphicsObject().pos(); +} + +void FlowScene::setNodePosition( Node& node, const QPointF& pos ) const { + node.nodeGraphicsObject().setPos( pos ); + node.nodeGraphicsObject().moveConnections(); + + QJsonObject obj; + obj["x"] = node.nodeGraphicsObject().pos().x(); + obj["y"] = node.nodeGraphicsObject().pos().y(); + QJsonObject nodeJson; + nodeJson["position"] = obj; + node.nodeDataModel()->addMetaData( nodeJson ); +} + +QSizeF FlowScene::getNodeSize( const Node& node ) const { + return QSizeF( node.nodeGeometry().width(), node.nodeGeometry().height() ); +} + +std::unordered_map> const& FlowScene::nodes() const { + return _nodes; +} + +std::unordered_map> const& FlowScene::connections() const { + return _connections; +} + +std::vector FlowScene::allNodes() const { + std::vector nodes; + + std::transform( + _nodes.begin(), + _nodes.end(), + std::back_inserter( nodes ), + []( std::pair> const& p ) { return p.second.get(); } ); + + return nodes; +} + +std::vector FlowScene::selectedNodes() const { + QList graphicsItems = selectedItems(); + + std::vector ret; + ret.reserve( graphicsItems.size() ); + + for ( QGraphicsItem* item : graphicsItems ) { + auto ngo = qgraphicsitem_cast( item ); + + if ( ngo != nullptr ) { ret.push_back( &ngo->node() ); } + } + + return ret; +} + +//------------------------------------------------------------------------------ + +void FlowScene::clearScene() { + // Manual node cleanup. Simply clearing the holding datastructures doesn't work, the code + // crashes when + // there are both nodes and connections in the scene. (The data propagation internal logic tries + // to propagate data through already freed connections.) + while ( _connections.size() > 0 ) { + deleteConnection( *_connections.begin()->second ); + } + + while ( _nodes.size() > 0 ) { + removeNode( *_nodes.begin()->second ); + } +} + +void FlowScene::save() const { + QString fileName = QFileDialog::getSaveFileName( + nullptr, tr( "Open Flow Scene" ), QDir::homePath(), tr( "Flow Scene Files (*.flow)" ) ); + + if ( !fileName.isEmpty() ) { + if ( !fileName.endsWith( "flow", Qt::CaseInsensitive ) ) fileName += ".flow"; + + QFile file( fileName ); + if ( file.open( QIODevice::WriteOnly ) ) { file.write( saveToMemory() ); } + } +} + +void FlowScene::load() { + QString fileName = QFileDialog::getOpenFileName( + nullptr, tr( "Open Flow Scene" ), QDir::homePath(), tr( "Flow Scene Files (*.flow)" ) ); + + if ( !QFileInfo::exists( fileName ) ) return; + + QFile file( fileName ); + + if ( !file.open( QIODevice::ReadOnly ) ) return; + + clearScene(); + + QByteArray wholeFile = file.readAll(); + + loadFromMemory( wholeFile ); +} + +QByteArray FlowScene::saveToMemory() const { + QJsonObject sceneJson; + + QJsonArray nodesJsonArray; + + for ( auto const& pair : _nodes ) { + auto const& node = pair.second; + + nodesJsonArray.append( node->save() ); + } + + sceneJson["nodes"] = nodesJsonArray; + + QJsonArray connectionJsonArray; + for ( auto const& pair : _connections ) { + auto const& connection = pair.second; + + QJsonObject connectionJson = connection->save(); + + if ( !connectionJson.isEmpty() ) connectionJsonArray.append( connectionJson ); + } + + sceneJson["connections"] = connectionJsonArray; + + QJsonDocument document( sceneJson ); + + return document.toJson(); +} + +void FlowScene::loadFromMemory( const QByteArray& data ) { + QJsonObject const jsonDocument = QJsonDocument::fromJson( data ).object(); + + QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); + + for ( QJsonValueRef node : nodesJsonArray ) { + restoreNode( node.toObject() ); + } + + QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); + + for ( QJsonValueRef connection : connectionJsonArray ) { + restoreConnection( connection.toObject() ); + } +} + +void FlowScene::setupConnectionSignals( Connection const& c ) { + connect( &c, + &Connection::connectionMadeIncomplete, + this, + &FlowScene::connectionDeleted, + Qt::UniqueConnection ); +} + +void FlowScene::sendConnectionCreatedToNodes( Connection const& c ) { + Node* from = c.getNode( PortType::Out ); + Node* to = c.getNode( PortType::In ); + + Q_ASSERT( from != nullptr ); + Q_ASSERT( to != nullptr ); + + from->nodeDataModel()->outputConnectionCreated( c ); + to->nodeDataModel()->inputConnectionCreated( c ); +} + +void FlowScene::sendConnectionDeletedToNodes( Connection const& c ) { + Node* from = c.getNode( PortType::Out ); + Node* to = c.getNode( PortType::In ); + + Q_ASSERT( from != nullptr ); + Q_ASSERT( to != nullptr ); + + from->nodeDataModel()->outputConnectionDeleted( c ); + to->nodeDataModel()->inputConnectionDeleted( c ); +} + +//------------------------------------------------------------------------------ +namespace QtNodes { + +Node* locateNodeAt( QPointF scenePoint, FlowScene& scene, QTransform const& viewTransform ) { + // items under cursor + QList items = + scene.items( scenePoint, Qt::IntersectsItemShape, Qt::DescendingOrder, viewTransform ); + + //// items convertable to NodeGraphicsObject + std::vector filteredItems; + + std::copy_if( + items.begin(), items.end(), std::back_inserter( filteredItems ), []( QGraphicsItem* item ) { +#if defined( __APPLE__ ) && defined( __GNUG__ ) + // Ugly workaround to prevent segfault in the following dynamic_cast + return ( typeid( *item ).hash_code() == typeid( NodeGraphicsObject ).hash_code() ); +#else + return (dynamic_cast(item) != nullptr); +#endif + } ); + + Node* resultNode = nullptr; + + if ( !filteredItems.empty() ) { + QGraphicsItem* graphicsItem = filteredItems.front(); + auto ngo = dynamic_cast( graphicsItem ); + + resultNode = &ngo->node(); + } + + return resultNode; +} +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp new file mode 100644 index 00000000000..05fbc88472e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp @@ -0,0 +1,316 @@ +#include "FlowView.hpp" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include "ConnectionGraphicsObject.hpp" +#include "DataModelRegistry.hpp" +#include "FlowScene.hpp" +#include "Node.hpp" +#include "NodeGraphicsObject.hpp" +#include "StyleCollection.hpp" + +using QtNodes::FlowScene; +using QtNodes::FlowView; + +FlowView::FlowView( QWidget* parent ) : + QGraphicsView( parent ), + _clearSelectionAction( Q_NULLPTR ), + _deleteSelectionAction( Q_NULLPTR ), + _scene( Q_NULLPTR ) { + setDragMode( QGraphicsView::ScrollHandDrag ); + setRenderHint( QPainter::Antialiasing ); + + auto const& flowViewStyle = StyleCollection::flowViewStyle(); + + setBackgroundBrush( flowViewStyle.BackgroundColor ); + + // setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + // setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); + setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + + setTransformationAnchor( QGraphicsView::AnchorUnderMouse ); + + setCacheMode( QGraphicsView::CacheBackground ); + + // setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); +} + +FlowView::FlowView( FlowScene* scene, QWidget* parent ) : FlowView( parent ) { + setScene( scene ); +} + +QAction* FlowView::clearSelectionAction() const { + return _clearSelectionAction; +} + +QAction* FlowView::deleteSelectionAction() const { + return _deleteSelectionAction; +} + +void FlowView::setScene( FlowScene* scene ) { + _scene = scene; + QGraphicsView::setScene( _scene ); + + // setup actions + delete _clearSelectionAction; + _clearSelectionAction = new QAction( QStringLiteral( "Clear Selection" ), this ); + _clearSelectionAction->setShortcut( Qt::Key_Escape ); + connect( _clearSelectionAction, &QAction::triggered, _scene, &QGraphicsScene::clearSelection ); + addAction( _clearSelectionAction ); + + delete _deleteSelectionAction; + _deleteSelectionAction = new QAction( QStringLiteral( "Delete Selection" ), this ); + _deleteSelectionAction->setShortcut( Qt::Key_Delete ); + connect( _deleteSelectionAction, &QAction::triggered, this, &FlowView::deleteSelectedNodes ); + addAction( _deleteSelectionAction ); +} + +void FlowView::contextMenuEvent( QContextMenuEvent* event ) { + if ( itemAt( event->pos() ) ) { + QGraphicsView::contextMenuEvent( event ); + return; + } + + QMenu modelMenu; + + auto skipText = QStringLiteral( "skip me" ); + + // Add filterbox to the context menu + auto* txtBox = new QLineEdit( &modelMenu ); + + txtBox->setPlaceholderText( QStringLiteral( "Filter" ) ); + txtBox->setClearButtonEnabled( true ); + + auto* txtBoxAction = new QWidgetAction( &modelMenu ); + txtBoxAction->setDefaultWidget( txtBox ); + + modelMenu.addAction( txtBoxAction ); + + // Add result treeview to the context menu + auto* treeView = new QTreeWidget( &modelMenu ); + treeView->header()->close(); + + auto* treeViewAction = new QWidgetAction( &modelMenu ); + treeViewAction->setDefaultWidget( treeView ); + + modelMenu.addAction( treeViewAction ); + + QMap topLevelItems; + for ( auto const& cat : _scene->registry().categories() ) { + auto item = new QTreeWidgetItem( treeView ); + item->setText( 0, cat ); + item->setData( 0, Qt::UserRole, skipText ); + topLevelItems[cat] = item; + } + + for ( auto const& assoc : _scene->registry().registeredModelsCategoryAssociation() ) { + auto parent = topLevelItems[assoc.second]; + auto item = new QTreeWidgetItem( parent ); + item->setText( 0, assoc.first ); + item->setData( 0, Qt::UserRole, assoc.first ); + } + + treeView->expandAll(); + + connect( treeView, &QTreeWidget::itemClicked, [&]( QTreeWidgetItem* item, int ) { + QString modelName = item->data( 0, Qt::UserRole ).toString(); + + if ( modelName == skipText ) { return; } + + auto type = _scene->registry().create( modelName ); + + if ( type ) { + auto& node = _scene->createNode( std::move( type ) ); + + QPoint pos = event->pos(); + + QPointF posView = this->mapToScene( pos ); + + node.nodeGraphicsObject().setPos( posView ); + + _scene->nodePlaced( node ); + } + else { + qDebug() << "Model not found"; + } + + modelMenu.close(); + } ); + + // Setup filtering + connect( txtBox, &QLineEdit::textChanged, [&]( const QString& text ) { + for ( auto& topLvlItem : topLevelItems ) { + for ( int i = 0; i < topLvlItem->childCount(); ++i ) { + auto child = topLvlItem->child( i ); + auto modelName = child->data( 0, Qt::UserRole ).toString(); + const bool match = ( modelName.contains( text, Qt::CaseInsensitive ) ); + child->setHidden( !match ); + } + } + } ); + + // make sure the text box gets focus so the user doesn't have to click on it + txtBox->setFocus(); + + modelMenu.exec( event->globalPos() ); +} + +void FlowView::wheelEvent( QWheelEvent* event ) { + QPoint delta = event->angleDelta(); + + if ( delta.y() == 0 ) { + event->ignore(); + return; + } + + double const d = delta.y() / std::abs( delta.y() ); + + if ( d > 0.0 ) + scaleUp(); + else + scaleDown(); +} + +void FlowView::scaleUp() { + double const step = 1.2; + double const factor = std::pow( step, 1.0 ); + + QTransform t = transform(); + + if ( t.m11() > 2.0 ) return; + + scale( factor, factor ); +} + +void FlowView::scaleDown() { + double const step = 1.2; + double const factor = std::pow( step, -1.0 ); + + scale( factor, factor ); +} + +void FlowView::deleteSelectedNodes() { + // Delete the selected connections first, ensuring that they won't be + // automatically deleted when selected nodes are deleted (deleting a node + // deletes some connections as well) + for ( QGraphicsItem* item : _scene->selectedItems() ) { + if ( auto c = qgraphicsitem_cast( item ) ) + _scene->deleteConnection( c->connection() ); + } + + // Delete the nodes; this will delete many of the connections. + // Selected connections were already deleted prior to this loop, otherwise + // qgraphicsitem_cast(item) could be a use-after-free + // when a selected connection is deleted by deleting the node. + for ( QGraphicsItem* item : _scene->selectedItems() ) { + if ( auto n = qgraphicsitem_cast( item ) ) + _scene->removeNode( n->node() ); + } +} + +void FlowView::keyPressEvent( QKeyEvent* event ) { + switch ( event->key() ) { + case Qt::Key_Shift: + setDragMode( QGraphicsView::RubberBandDrag ); + break; + + default: + break; + } + + QGraphicsView::keyPressEvent( event ); +} + +void FlowView::keyReleaseEvent( QKeyEvent* event ) { + switch ( event->key() ) { + case Qt::Key_Shift: + setDragMode( QGraphicsView::ScrollHandDrag ); + break; + + default: + break; + } + QGraphicsView::keyReleaseEvent( event ); +} + +void FlowView::mousePressEvent( QMouseEvent* event ) { + QGraphicsView::mousePressEvent( event ); + if ( event->button() == Qt::LeftButton ) { _clickPos = mapToScene( event->pos() ); } +} + +void FlowView::mouseMoveEvent( QMouseEvent* event ) { + QGraphicsView::mouseMoveEvent( event ); + if ( scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton ) { + // Make sure shift is not being pressed + if ( ( event->modifiers() & Qt::ShiftModifier ) == 0 ) { + QPointF difference = _clickPos - mapToScene( event->pos() ); + setSceneRect( sceneRect().translated( difference.x(), difference.y() ) ); + } + } +} + +void FlowView::drawBackground( QPainter* painter, const QRectF& r ) { + QGraphicsView::drawBackground( painter, r ); + + auto drawGrid = [&]( double gridStep ) { + QRect windowRect = rect(); + QPointF tl = mapToScene( windowRect.topLeft() ); + QPointF br = mapToScene( windowRect.bottomRight() ); + + double left = std::floor( tl.x() / gridStep - 0.5 ); + double right = std::floor( br.x() / gridStep + 1.0 ); + double bottom = std::floor( tl.y() / gridStep - 0.5 ); + double top = std::floor( br.y() / gridStep + 1.0 ); + + // vertical lines + for ( int xi = int( left ); xi <= int( right ); ++xi ) { + QLineF line( xi * gridStep, bottom * gridStep, xi * gridStep, top * gridStep ); + + painter->drawLine( line ); + } + + // horizontal lines + for ( int yi = int( bottom ); yi <= int( top ); ++yi ) { + QLineF line( left * gridStep, yi * gridStep, right * gridStep, yi * gridStep ); + painter->drawLine( line ); + } + }; + + auto const& flowViewStyle = StyleCollection::flowViewStyle(); + + QBrush bBrush = backgroundBrush(); + + QPen pfine( flowViewStyle.FineGridColor, 1.0 ); + + painter->setPen( pfine ); + drawGrid( 15 ); + + QPen p( flowViewStyle.CoarseGridColor, 1.0 ); + + painter->setPen( p ); + drawGrid( 150 ); +} + +void FlowView::showEvent( QShowEvent* event ) { + _scene->setSceneRect( this->rect() ); + QGraphicsView::showEvent( event ); +} + +FlowScene* FlowView::scene() { + return _scene; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp new file mode 100644 index 00000000000..63e0a62d78a --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp @@ -0,0 +1,94 @@ +#include "FlowViewStyle.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "StyleCollection.hpp" + +using QtNodes::FlowViewStyle; + +inline void initResources() { + Q_INIT_RESOURCE( resources ); +} + +FlowViewStyle::FlowViewStyle() { + // Explicit resources inialization for preventing the static initialization + // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order + initResources(); + + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile( ":DefaultStyle.json" ); +} + +FlowViewStyle::FlowViewStyle( QString jsonText ) { + loadJsonText( jsonText ); +} + +void FlowViewStyle::setStyle( QString jsonText ) { + FlowViewStyle style( jsonText ); + + StyleCollection::setFlowViewStyle( style ); +} + +#ifdef STYLE_DEBUG +# define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) \ + { \ + if ( v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null ) \ + qWarning() << "Undefined value for parameter:" << #variable; \ + } +#else +# define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) +#endif + +#define FLOW_VIEW_STYLE_READ_COLOR( values, variable ) \ + { \ + auto valueRef = values[#variable]; \ + FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ + if ( valueRef.isArray() ) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve( 3 ); \ + for ( auto it = colorArray.begin(); it != colorArray.end(); ++it ) { \ + rgb.push_back( ( *it ).toInt() ); \ + } \ + variable = QColor( rgb[0], rgb[1], rgb[2] ); \ + } \ + else { \ + variable = QColor( valueRef.toString() ); \ + } \ + } + +void FlowViewStyle::loadJsonFile( QString styleFile ) { + QFile file( styleFile ); + + if ( !file.open( QIODevice::ReadOnly ) ) { + qWarning() << "Couldn't open file " << styleFile; + + return; + } + + loadJsonFromByteArray( file.readAll() ); +} + +void FlowViewStyle::loadJsonText( QString jsonText ) { + loadJsonFromByteArray( jsonText.toUtf8() ); +} + +void FlowViewStyle::loadJsonFromByteArray( QByteArray const& byteArray ) { + QJsonDocument json( QJsonDocument::fromJson( byteArray ) ); + + QJsonObject topLevelObject = json.object(); + + QJsonValueRef nodeStyleValues = topLevelObject["FlowViewStyle"]; + + QJsonObject obj = nodeStyleValues.toObject(); + + FLOW_VIEW_STYLE_READ_COLOR( obj, BackgroundColor ); + FLOW_VIEW_STYLE_READ_COLOR( obj, FineGridColor ); + FLOW_VIEW_STYLE_READ_COLOR( obj, CoarseGridColor ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp new file mode 100644 index 00000000000..9831e55117e --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp @@ -0,0 +1,179 @@ +#include "Node.hpp" + +#include + +#include +#include + +#include "FlowScene.hpp" + +#include "NodeDataModel.hpp" +#include "NodeGraphicsObject.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" + +using QtNodes::Node; +using QtNodes::NodeData; +using QtNodes::NodeDataModel; +using QtNodes::NodeDataType; +using QtNodes::NodeGeometry; +using QtNodes::NodeGraphicsObject; +using QtNodes::NodeState; +using QtNodes::PortIndex; +using QtNodes::PortType; + +// TODO MTHS -- get uuid from datamodel instead of generating one +Node::Node( std::unique_ptr&& dataModel ) : + _uid( QUuid::createUuid() ), + _nodeDataModel( std::move( dataModel ) ), + _nodeState( _nodeDataModel ), + _nodeGeometry( _nodeDataModel ), + _nodeGraphicsObject( nullptr ) { + _nodeGeometry.recalculateSize(); + + // propagate data: model => node + connect( _nodeDataModel.get(), &NodeDataModel::dataUpdated, this, &Node::onDataUpdated ); + + connect( _nodeDataModel.get(), + &NodeDataModel::embeddedWidgetSizeUpdated, + this, + &Node::onNodeSizeUpdated ); +} + +Node::~Node() { + // Do not delete nodeDataModel whos content is non deletable + if ( !_nodeDataModel->isDeletable() ) { _nodeDataModel.release(); } +} + +QJsonObject Node::save() const { + QJsonObject nodeJson; +#ifdef ORIGINAL_NODEEDITOR + nodeJson["id"] = _uid.toString(); + + nodeJson["model"] = _nodeDataModel->save(); +#else + nodeJson = _nodeDataModel->save(); +#endif + QJsonObject obj; + obj["x"] = _nodeGraphicsObject->pos().x(); + obj["y"] = _nodeGraphicsObject->pos().y(); + nodeJson["position"] = obj; + + return nodeJson; +} + +void Node::import() { + QJsonObject json = _nodeDataModel->save(); + _uid = QUuid( _nodeDataModel->uuid() ); + QJsonObject positionJson = json["position"].toObject(); + QPointF point( positionJson["x"].toDouble(), positionJson["y"].toDouble() ); + _nodeGraphicsObject->setPos( point ); +} + +void Node::restore( QJsonObject const& json ) { +#ifdef ORIGINAL_NODEEDITOR + _uid = QUuid( json["id"].toString() ); + QJsonObject positionJson = json["position"].toObject(); + QPointF point( positionJson["x"].toDouble(), positionJson["y"].toDouble() ); + _nodeGraphicsObject->setPos( point ); + _nodeDataModel->restore( json["model"].toObject() ); +#else + _nodeDataModel->restore( json ); + _uid = QUuid( _nodeDataModel->uuid() ); + + QJsonObject positionJson = json["position"].toObject(); + QPointF point( positionJson["x"].toDouble(), positionJson["y"].toDouble() ); + _nodeGraphicsObject->setPos( point ); +#endif +} + +QUuid Node::id() const { + return _uid; +} + +void Node::reactToPossibleConnection( PortType reactingPortType, + NodeDataType const& reactingDataType, + QPointF const& scenePoint ) { + QTransform const t = _nodeGraphicsObject->sceneTransform(); + + QPointF p = t.inverted().map( scenePoint ); + + _nodeGeometry.setDraggingPosition( p ); + + _nodeGraphicsObject->update(); + + _nodeState.setReaction( NodeState::REACTING, reactingPortType, reactingDataType ); +} + +void Node::resetReactionToConnection() { + _nodeState.setReaction( NodeState::NOT_REACTING ); + _nodeGraphicsObject->update(); +} + +NodeGraphicsObject const& Node::nodeGraphicsObject() const { + return *_nodeGraphicsObject.get(); +} + +NodeGraphicsObject& Node::nodeGraphicsObject() { + return *_nodeGraphicsObject.get(); +} + +void Node::setGraphicsObject( std::unique_ptr&& graphics ) { + _nodeGraphicsObject = std::move( graphics ); + + _nodeGeometry.recalculateSize(); +} + +NodeGeometry& Node::nodeGeometry() { + return _nodeGeometry; +} + +NodeGeometry const& Node::nodeGeometry() const { + return _nodeGeometry; +} + +NodeState const& Node::nodeState() const { + return _nodeState; +} + +NodeState& Node::nodeState() { + return _nodeState; +} + +NodeDataModel* Node::nodeDataModel() const { + return _nodeDataModel.get(); +} + +void Node::propagateData( std::shared_ptr nodeData, PortIndex inPortIndex ) const { + _nodeDataModel->setInData( std::move( nodeData ), inPortIndex ); + + // Recalculate the nodes visuals. A data change can result in the node taking more space than + // before, so this forces a recalculate+repaint on the affected node + _nodeGraphicsObject->setGeometryChanged(); + _nodeGeometry.recalculateSize(); + _nodeGraphicsObject->update(); + _nodeGraphicsObject->moveConnections(); +} + +void Node::onDataUpdated( PortIndex index ) { + auto nodeData = _nodeDataModel->outData( index ); + + auto connections = _nodeState.connections( PortType::Out, index ); + + for ( auto const& c : connections ) + c.second->propagateData( nodeData ); +} + +void Node::onNodeSizeUpdated() { + if ( nodeDataModel()->embeddedWidget() ) { nodeDataModel()->embeddedWidget()->adjustSize(); } + nodeGeometry().recalculateSize(); + for ( PortType type : { PortType::In, PortType::Out } ) { + for ( auto& conn_set : nodeState().getEntries( type ) ) { + for ( auto& pair : conn_set ) { + Connection* conn = pair.second; + conn->getConnectionGraphicsObject().move(); + } + } + } +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp new file mode 100644 index 00000000000..71656044031 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp @@ -0,0 +1,180 @@ +#include "NodeConnectionInteraction.hpp" + +#include "ConnectionGraphicsObject.hpp" +#include "DataModelRegistry.hpp" +#include "FlowScene.hpp" +#include "NodeDataModel.hpp" +#include "NodeGraphicsObject.hpp" + +using QtNodes::Connection; +using QtNodes::FlowScene; +using QtNodes::Node; +using QtNodes::NodeConnectionInteraction; +using QtNodes::NodeDataModel; +using QtNodes::PortIndex; +using QtNodes::PortType; +using QtNodes::TypeConverter; + +NodeConnectionInteraction::NodeConnectionInteraction( Node& node, + Connection& connection, + FlowScene& scene ) : + _node( &node ), _connection( &connection ), _scene( &scene ) {} + +bool NodeConnectionInteraction::canConnect( PortIndex& portIndex, TypeConverter& converter ) const { + // 1) Connection requires a port + + PortType requiredPort = connectionRequiredPort(); + + if ( requiredPort == PortType::None ) { return false; } + + // 1.5) Forbid connecting the node to itself + Node* node = _connection->getNode( oppositePort( requiredPort ) ); + + if ( node == _node ) return false; + + // 2) connection point is on top of the node port + + QPointF connectionPoint = connectionEndScenePosition( requiredPort ); + + portIndex = nodePortIndexUnderScenePoint( requiredPort, connectionPoint ); + + if ( portIndex == INVALID ) { return false; } + + // 3) Node port is vacant + + // port should be empty + if ( !nodePortIsEmpty( requiredPort, portIndex ) ) return false; + + // 4) Connection type equals node port type, or there is a registered type conversion that can + // translate between the two + + auto connectionDataType = _connection->dataType( oppositePort( requiredPort ) ); + + auto const& modelTarget = _node->nodeDataModel(); + NodeDataType candidateNodeDataType = modelTarget->dataType( requiredPort, portIndex ); + + if ( connectionDataType.id != candidateNodeDataType.id ) { + if ( requiredPort == PortType::In ) { + converter = + _scene->registry().getTypeConverter( connectionDataType, candidateNodeDataType ); + } + else if ( requiredPort == PortType::Out ) { + converter = + _scene->registry().getTypeConverter( candidateNodeDataType, connectionDataType ); + } + + return ( converter != nullptr ); + } + + return true; +} + +bool NodeConnectionInteraction::tryConnect() const { + // 1) Check conditions from 'canConnect' + PortIndex portIndex = INVALID; + + TypeConverter converter; + + if ( !canConnect( portIndex, converter ) ) { return false; } + + // 1.5) If the connection is possible but a type conversion is needed, + // assign a convertor to connection + if ( converter ) { _connection->setTypeConverter( converter ); } + + // 2) Assign node to required port in Connection + PortType requiredPort = connectionRequiredPort(); + _node->nodeState().setConnection( requiredPort, portIndex, *_connection ); + + // 3) Assign Connection to empty port in NodeState + // The port is not longer required after this function + _connection->setNodeToPort( *_node, requiredPort, portIndex ); + + // 4) Adjust Connection geometry + + _node->nodeGraphicsObject().moveConnections(); + + // 5) Poke model to intiate data transfer + + auto outNode = _connection->getNode( PortType::Out ); + if ( outNode ) { + PortIndex outPortIndex = _connection->getPortIndex( PortType::Out ); + outNode->onDataUpdated( outPortIndex ); + } + + return true; +} + +/// 1) Node and Connection should be already connected +/// 2) If so, clear Connection entry in the NodeState +/// 3) Set Connection end to 'requiring a port' +bool NodeConnectionInteraction::disconnect( PortType portToDisconnect ) const { + PortIndex portIndex = _connection->getPortIndex( portToDisconnect ); + + NodeState& state = _node->nodeState(); + + // clear pointer to Connection in the NodeState + state.getEntries( portToDisconnect )[portIndex].clear(); + + // 4) Propagate invalid data to IN node + _connection->propagateEmptyData(); + + // clear Connection side + _connection->clearNode( portToDisconnect ); + + _connection->setRequiredPort( portToDisconnect ); + + _connection->getConnectionGraphicsObject().grabMouse(); + + return true; +} + +// ------------------ util functions below + +PortType NodeConnectionInteraction::connectionRequiredPort() const { + auto const& state = _connection->connectionState(); + + return state.requiredPort(); +} + +QPointF NodeConnectionInteraction::connectionEndScenePosition( PortType portType ) const { + auto& go = _connection->getConnectionGraphicsObject(); + + ConnectionGeometry& geometry = _connection->connectionGeometry(); + + QPointF endPoint = geometry.getEndPoint( portType ); + + return go.mapToScene( endPoint ); +} + +QPointF NodeConnectionInteraction::nodePortScenePosition( PortType portType, + PortIndex portIndex ) const { + NodeGeometry const& geom = _node->nodeGeometry(); + + QPointF p = geom.portScenePosition( portIndex, portType ); + + NodeGraphicsObject& ngo = _node->nodeGraphicsObject(); + + return ngo.sceneTransform().map( p ); +} + +PortIndex +NodeConnectionInteraction::nodePortIndexUnderScenePoint( PortType portType, + QPointF const& scenePoint ) const { + NodeGeometry const& nodeGeom = _node->nodeGeometry(); + + QTransform sceneTransform = _node->nodeGraphicsObject().sceneTransform(); + + PortIndex portIndex = nodeGeom.checkHitScenePoint( portType, scenePoint, sceneTransform ); + return portIndex; +} + +bool NodeConnectionInteraction::nodePortIsEmpty( PortType portType, PortIndex portIndex ) const { + NodeState const& nodeState = _node->nodeState(); + + auto const& entries = nodeState.getEntries( portType ); + + if ( entries[portIndex].empty() ) return true; + + const auto outPolicy = _node->nodeDataModel()->portOutConnectionPolicy( portIndex ); + return ( portType == PortType::Out && outPolicy == NodeDataModel::ConnectionPolicy::Many ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp new file mode 100644 index 00000000000..89f43c3e0d0 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include "Connection.hpp" +#include "Node.hpp" + +namespace QtNodes { + +class DataModelRegistry; +class FlowScene; +class NodeDataModel; + +/// Class performs various operations on the Node and Connection pair. +/// An instance should be created on the stack and destroyed when +/// the operation is completed +class NodeConnectionInteraction +{ + public: + NodeConnectionInteraction( Node& node, Connection& connection, FlowScene& scene ); + + /// Can connect when following conditions are met: + /// 1) Connection 'requires' a port + /// 2) Connection's vacant end is above the node port + /// 3) Node port is vacant + /// 4) Connection type equals node port type, or there is a registered type conversion that can + /// translate between the two + bool canConnect( PortIndex& portIndex, TypeConverter& converter ) const; + + /// 1) Check conditions from 'canConnect' + /// 1.5) If the connection is possible but a type conversion is needed, add a converter node to + /// the scene, and connect it properly 2) Assign node to required port in Connection 3) Assign + /// Connection to empty port in NodeState 4) Adjust Connection geometry 5) Poke model to + /// initiate data transfer + bool tryConnect() const; + + /// 1) Node and Connection should be already connected + /// 2) If so, clear Connection entry in the NodeState + /// 3) Propagate invalid data to IN node + /// 4) Set Connection end to 'requiring a port' + bool disconnect( PortType portToDisconnect ) const; + + private: + PortType connectionRequiredPort() const; + + QPointF connectionEndScenePosition( PortType ) const; + + QPointF nodePortScenePosition( PortType portType, PortIndex portIndex ) const; + + PortIndex nodePortIndexUnderScenePoint( PortType portType, QPointF const& p ) const; + + bool nodePortIsEmpty( PortType portType, PortIndex portIndex ) const; + + private: + Node* _node; + + Connection* _connection; + + FlowScene* _scene; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp new file mode 100644 index 00000000000..3b6742cc8ec --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp @@ -0,0 +1,26 @@ +#include "NodeDataModel.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::NodeDataModel; +using QtNodes::NodeStyle; + +NodeDataModel::NodeDataModel() : _nodeStyle( StyleCollection::nodeStyle() ) { + // Derived classes can initialize specific style here +} + +QJsonObject NodeDataModel::save() const { + QJsonObject modelJson; + + modelJson["name"] = name(); + + return modelJson; +} + +NodeStyle const& NodeDataModel::nodeStyle() const { + return _nodeStyle; +} + +void NodeDataModel::setNodeStyle( NodeStyle const& style ) { + _nodeStyle = style; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp new file mode 100644 index 00000000000..2fd453fb5ff --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp @@ -0,0 +1,279 @@ +#include "NodeGeometry.hpp" + +#include +#include + +#include "Node.hpp" +#include "NodeDataModel.hpp" +#include "NodeGraphicsObject.hpp" +#include "NodeState.hpp" +#include "PortType.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::Node; +using QtNodes::NodeDataModel; +using QtNodes::NodeGeometry; +using QtNodes::PortIndex; +using QtNodes::PortType; + +NodeGeometry::NodeGeometry( std::unique_ptr const& dataModel ) : + _width( 100 ), + _height( 150 ), + _inputPortWidth( 70 ), + _outputPortWidth( 70 ), + _entryHeight( 20 ), + _spacing( 20 ), + _hovered( false ), + _nSources( dataModel->nPorts( PortType::Out ) ), + _nSinks( dataModel->nPorts( PortType::In ) ), + _draggingPos( -1000, -1000 ), + _dataModel( dataModel ), + _fontMetrics( QFont() ), + _boldFontMetrics( QFont() ) { + QFont f; + f.setBold( true ); + + _boldFontMetrics = QFontMetrics( f ); +} + +unsigned int NodeGeometry::nSources() const { + return _dataModel->nPorts( PortType::Out ); +} + +unsigned int NodeGeometry::nSinks() const { + return _dataModel->nPorts( PortType::In ); +} + +QRectF NodeGeometry::entryBoundingRect() const { + double const addon = 0.0; + + return QRectF( 0 - addon, 0 - addon, _entryWidth + 2 * addon, _entryHeight + 2 * addon ); +} + +QRectF NodeGeometry::boundingRect() const { + auto const& nodeStyle = StyleCollection::nodeStyle(); + + double addon = 4 * nodeStyle.ConnectionPointDiameter; + + return QRectF( 0 - addon, 0 - addon, _width + 2 * addon, _height + 2 * addon ); +} + +void NodeGeometry::recalculateSize() const { + _entryHeight = _fontMetrics.height(); + + { + unsigned int maxNumOfEntries = std::max( _nSinks, _nSources ); + unsigned int step = _entryHeight + _spacing; + _height = step * maxNumOfEntries; + } + + if ( auto w = _dataModel->embeddedWidget() ) { + _height = std::max( _height, static_cast( w->height() ) ); + } + + _height += captionHeight(); + + _inputPortWidth = portWidth( PortType::In ); + _outputPortWidth = portWidth( PortType::Out ); + + _width = _inputPortWidth + _outputPortWidth + 2 * _spacing; + + if ( auto w = _dataModel->embeddedWidget() ) { _width += w->width(); } + + _width = std::max( _width, captionWidth() ); + + if ( _dataModel->validationState() != NodeValidationState::Valid ) { + _width = std::max( _width, validationWidth() ); + _height += validationHeight() + _spacing; + } +} + +void NodeGeometry::recalculateSize( QFont const& font ) const { + QFontMetrics fontMetrics( font ); + QFont boldFont = font; + + boldFont.setBold( true ); + + QFontMetrics boldFontMetrics( boldFont ); + + if ( _boldFontMetrics != boldFontMetrics ) { + _fontMetrics = fontMetrics; + _boldFontMetrics = boldFontMetrics; + + recalculateSize(); + } +} + +QPointF +NodeGeometry::portScenePosition( PortIndex index, PortType portType, QTransform const& t ) const { + auto const& nodeStyle = StyleCollection::nodeStyle(); + + unsigned int step = _entryHeight + _spacing; + + QPointF result; + + double totalHeight = 0.0; + + totalHeight += captionHeight(); + + totalHeight += step * index; + + // TODO: why? + totalHeight += step / 2.0; + + switch ( portType ) { + case PortType::Out: { + double x = _width + nodeStyle.ConnectionPointDiameter; + + result = QPointF( x, totalHeight ); + break; + } + + case PortType::In: { + double x = 0.0 - nodeStyle.ConnectionPointDiameter; + + result = QPointF( x, totalHeight ); + break; + } + + default: + break; + } + + return t.map( result ); +} + +PortIndex NodeGeometry::checkHitScenePoint( PortType portType, + QPointF const scenePoint, + QTransform const& sceneTransform ) const { + auto const& nodeStyle = StyleCollection::nodeStyle(); + + PortIndex result = INVALID; + + if ( portType == PortType::None ) return result; + + double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; + + unsigned int const nItems = _dataModel->nPorts( portType ); + + for ( unsigned int i = 0; i < nItems; ++i ) { + auto pp = portScenePosition( i, portType, sceneTransform ); + + QPointF p = pp - scenePoint; + auto distance = std::sqrt( QPointF::dotProduct( p, p ) ); + + if ( distance < tolerance ) { + result = PortIndex( i ); + break; + } + } + + return result; +} + +QRect NodeGeometry::resizeRect() const { + unsigned int rectSize = 7; + + return QRect( _width - rectSize, _height - rectSize, rectSize, rectSize ); +} + +QPointF NodeGeometry::widgetPosition() const { + if ( auto w = _dataModel->embeddedWidget() ) { + if ( w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag ) { + // If the widget wants to use as much vertical space as possible, place it immediately + // after the caption. + return QPointF( _spacing + portWidth( PortType::In ), captionHeight() ); + } + else { + if ( _dataModel->validationState() != NodeValidationState::Valid ) { + return QPointF( + _spacing + portWidth( PortType::In ), + ( captionHeight() + _height - validationHeight() - _spacing - w->height() ) / + 2.0 ); + } + + return QPointF( _spacing + portWidth( PortType::In ), + ( captionHeight() + _height - w->height() ) / 2.0 ); + } + } + return QPointF(); +} + +int NodeGeometry::equivalentWidgetHeight() const { + if ( _dataModel->validationState() != NodeValidationState::Valid ) { + return height() - captionHeight() + validationHeight(); + } + + return height() - captionHeight(); +} + +unsigned int NodeGeometry::captionHeight() const { + if ( !_dataModel->captionVisible() ) return 0; + + QString name = _dataModel->caption(); + + return _boldFontMetrics.boundingRect( name ).height(); +} + +unsigned int NodeGeometry::captionWidth() const { + if ( !_dataModel->captionVisible() ) return 0; + + QString name = _dataModel->caption(); + + return _boldFontMetrics.boundingRect( name ).width(); +} + +unsigned int NodeGeometry::validationHeight() const { + QString msg = _dataModel->validationMessage(); + + return _boldFontMetrics.boundingRect( msg ).height(); +} + +unsigned int NodeGeometry::validationWidth() const { + QString msg = _dataModel->validationMessage(); + + return _boldFontMetrics.boundingRect( msg ).width(); +} + +QPointF NodeGeometry::calculateNodePositionBetweenNodePorts( PortIndex targetPortIndex, + PortType targetPort, + Node* targetNode, + PortIndex sourcePortIndex, + PortType sourcePort, + Node* sourceNode, + Node& newNode ) { + // Calculating the nodes position in the scene. It'll be positioned half way between the two + // ports that it "connects". The first line calculates the halfway point between the ports (node + // position + port position on the node for both nodes averaged). The second line offsets this + // coordinate with the size of the new node, so that the new nodes center falls on the + // originally calculated coordinate, instead of it's upper left corner. + auto converterNodePos = + ( sourceNode->nodeGraphicsObject().pos() + + sourceNode->nodeGeometry().portScenePosition( sourcePortIndex, sourcePort ) + + targetNode->nodeGraphicsObject().pos() + + targetNode->nodeGeometry().portScenePosition( targetPortIndex, targetPort ) ) / + 2.0f; + converterNodePos.setX( converterNodePos.x() - newNode.nodeGeometry().width() / 2.0f ); + converterNodePos.setY( converterNodePos.y() - newNode.nodeGeometry().height() / 2.0f ); + return converterNodePos; +} + +unsigned int NodeGeometry::portWidth( PortType portType ) const { + unsigned width = 0; + + for ( auto i = 0ul; i < _dataModel->nPorts( portType ); ++i ) { + QString name; + + if ( _dataModel->portCaptionVisible( portType, i ) ) { + name = _dataModel->portCaption( portType, i ); + } + else { + name = _dataModel->dataType( portType, i ).name; + } + + width = std::max( unsigned( _fontMetrics.horizontalAdvance( name ) ), width ); + } + + return width; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp new file mode 100644 index 00000000000..97498dea8c2 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp @@ -0,0 +1,305 @@ +#include "NodeGraphicsObject.hpp" + +#include +#include + +#include +#include + +#include "ConnectionGraphicsObject.hpp" +#include "ConnectionState.hpp" + +#include "FlowScene.hpp" +#include "NodePainter.hpp" + +#include "Node.hpp" +#include "NodeConnectionInteraction.hpp" +#include "NodeDataModel.hpp" + +#include "StyleCollection.hpp" + +using QtNodes::FlowScene; +using QtNodes::Node; +using QtNodes::NodeGraphicsObject; + +NodeGraphicsObject::NodeGraphicsObject( FlowScene& scene, Node& node ) : + _scene( scene ), _node( node ), _locked( false ), _proxyWidget( nullptr ) { + _scene.addItem( this ); + + setFlag( QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true ); + setFlag( QGraphicsItem::ItemIsMovable, true ); + setFlag( QGraphicsItem::ItemIsFocusable, true ); + setFlag( QGraphicsItem::ItemIsSelectable, true ); + setFlag( QGraphicsItem::ItemSendsScenePositionChanges, true ); + + setCacheMode( QGraphicsItem::DeviceCoordinateCache ); + + auto const& nodeStyle = node.nodeDataModel()->nodeStyle(); + + { + auto effect = new QGraphicsDropShadowEffect; + effect->setOffset( 4, 4 ); + effect->setBlurRadius( 20 ); + effect->setColor( nodeStyle.ShadowColor ); + + setGraphicsEffect( effect ); + } + + setOpacity( nodeStyle.Opacity ); + + setAcceptHoverEvents( true ); + + setZValue( 0 ); + + embedQWidget(); + + // connect to the move signals to emit the move signals in FlowScene + auto onMoveSlot = [this] { _scene.nodeMoved( _node, pos() ); }; + connect( this, &QGraphicsObject::xChanged, this, onMoveSlot ); + connect( this, &QGraphicsObject::yChanged, this, onMoveSlot ); +} + +NodeGraphicsObject::~NodeGraphicsObject() { + _scene.removeItem( this ); +} + +Node& NodeGraphicsObject::node() { + return _node; +} + +Node const& NodeGraphicsObject::node() const { + return _node; +} + +void NodeGraphicsObject::embedQWidget() { + NodeGeometry& geom = _node.nodeGeometry(); + + if ( auto w = _node.nodeDataModel()->embeddedWidget() ) { + _proxyWidget = new QGraphicsProxyWidget( this ); + + _proxyWidget->setWidget( w ); + + _proxyWidget->setPreferredWidth( 5 ); + + geom.recalculateSize(); + + if ( w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag ) { + // If the widget wants to use as much vertical space as possible, set it to have the + // geom's equivalentWidgetHeight. + _proxyWidget->setMinimumHeight( geom.equivalentWidgetHeight() ); + } + + _proxyWidget->setPos( geom.widgetPosition() ); + + update(); + + _proxyWidget->setOpacity( 1.0 ); + _proxyWidget->setFlag( QGraphicsItem::ItemIgnoresParentOpacity ); + } +} + +QRectF NodeGraphicsObject::boundingRect() const { + return _node.nodeGeometry().boundingRect(); +} + +void NodeGraphicsObject::setGeometryChanged() { + prepareGeometryChange(); +} + +void NodeGraphicsObject::moveConnections() const { + NodeState const& nodeState = _node.nodeState(); + + for ( PortType portType : { PortType::In, PortType::Out } ) { + auto const& connectionEntries = nodeState.getEntries( portType ); + + for ( auto const& connections : connectionEntries ) { + for ( auto& con : connections ) + con.second->getConnectionGraphicsObject().move(); + } + } +} + +void NodeGraphicsObject::lock( bool locked ) { + _locked = locked; + + setFlag( QGraphicsItem::ItemIsMovable, !locked ); + setFlag( QGraphicsItem::ItemIsFocusable, !locked ); + setFlag( QGraphicsItem::ItemIsSelectable, !locked ); +} + +void NodeGraphicsObject::paint( QPainter* painter, + QStyleOptionGraphicsItem const* option, + QWidget* ) { + painter->setClipRect( option->exposedRect ); + + NodePainter::paint( painter, _node, _scene ); +} + +QVariant NodeGraphicsObject::itemChange( GraphicsItemChange change, const QVariant& value ) { + if ( change == ItemPositionChange && scene() ) { moveConnections(); } + + return QGraphicsItem::itemChange( change, value ); +} + +void NodeGraphicsObject::mousePressEvent( QGraphicsSceneMouseEvent* event ) { + if ( _locked ) return; + + // deselect all other items after this one is selected + if ( !isSelected() && !( event->modifiers() & Qt::ControlModifier ) ) { + _scene.clearSelection(); + } + + for ( PortType portToCheck : { PortType::In, PortType::Out } ) { + NodeGeometry const& nodeGeometry = _node.nodeGeometry(); + + // TODO do not pass sceneTransform + int const portIndex = + nodeGeometry.checkHitScenePoint( portToCheck, event->scenePos(), sceneTransform() ); + + if ( portIndex != INVALID ) { + NodeState const& nodeState = _node.nodeState(); + + std::unordered_map connections = + nodeState.connections( portToCheck, portIndex ); + + // start dragging existing connection + if ( !connections.empty() && portToCheck == PortType::In ) { + auto con = connections.begin()->second; + + NodeConnectionInteraction interaction( _node, *con, _scene ); + + interaction.disconnect( portToCheck ); + } + else // initialize new Connection + { + if ( portToCheck == PortType::Out ) { + auto const outPolicy = + _node.nodeDataModel()->portOutConnectionPolicy( portIndex ); + if ( !connections.empty() && + outPolicy == NodeDataModel::ConnectionPolicy::One ) { + _scene.deleteConnection( *connections.begin()->second ); + } + } + + // todo add to FlowScene + auto connection = _scene.createConnection( portToCheck, _node, portIndex ); + + _node.nodeState().setConnection( portToCheck, portIndex, *connection ); + + connection->getConnectionGraphicsObject().grabMouse(); + } + } + } + + auto pos = event->pos(); + auto& geom = _node.nodeGeometry(); + auto& state = _node.nodeState(); + + if ( _node.nodeDataModel()->resizable() && + geom.resizeRect().contains( QPoint( pos.x(), pos.y() ) ) ) { + state.setResizing( true ); + } +} + +void NodeGraphicsObject::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) { + auto& geom = _node.nodeGeometry(); + auto& state = _node.nodeState(); + + if ( state.resizing() ) { + auto diff = event->pos() - event->lastPos(); + + if ( auto w = _node.nodeDataModel()->embeddedWidget() ) { + prepareGeometryChange(); + + auto oldSize = w->size(); + + oldSize += QSize( diff.x(), diff.y() ); + + w->setFixedSize( oldSize ); + + _proxyWidget->setMinimumSize( oldSize ); + _proxyWidget->setMaximumSize( oldSize ); + _proxyWidget->setPos( geom.widgetPosition() ); + + geom.recalculateSize(); + update(); + + moveConnections(); + + event->accept(); + } + } + else { + QGraphicsObject::mouseMoveEvent( event ); + + if ( event->lastPos() != event->pos() ) moveConnections(); + + event->ignore(); + } + + QRectF r = scene()->sceneRect(); + + r = r.united( mapToScene( boundingRect() ).boundingRect() ); + + scene()->setSceneRect( r ); +} + +void NodeGraphicsObject::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) { + auto& state = _node.nodeState(); + + state.setResizing( false ); + + QGraphicsObject::mouseReleaseEvent( event ); + + // position connections precisely after fast node move + moveConnections(); +} + +void NodeGraphicsObject::hoverEnterEvent( QGraphicsSceneHoverEvent* event ) { + // bring all the colliding nodes to background + QList overlapItems = collidingItems(); + + for ( QGraphicsItem* item : overlapItems ) { + if ( item->zValue() > 0.0 ) { item->setZValue( 0.0 ); } + } + + // bring this node forward + setZValue( 1.0 ); + + _node.nodeGeometry().setHovered( true ); + update(); + _scene.nodeHovered( node(), event->screenPos() ); + event->accept(); +} + +void NodeGraphicsObject::hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) { + _node.nodeGeometry().setHovered( false ); + update(); + _scene.nodeHoverLeft( node() ); + event->accept(); +} + +void NodeGraphicsObject::hoverMoveEvent( QGraphicsSceneHoverEvent* event ) { + auto pos = event->pos(); + auto& geom = _node.nodeGeometry(); + + if ( _node.nodeDataModel()->resizable() && + geom.resizeRect().contains( QPoint( pos.x(), pos.y() ) ) ) { + setCursor( QCursor( Qt::SizeFDiagCursor ) ); + } + else { + setCursor( QCursor() ); + } + + event->accept(); +} + +void NodeGraphicsObject::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) { + QGraphicsItem::mouseDoubleClickEvent( event ); + + _scene.nodeDoubleClicked( node() ); +} + +void NodeGraphicsObject::contextMenuEvent( QGraphicsSceneContextMenuEvent* event ) { + _scene.nodeContextMenu( node(), mapToScene( event->pos() ) ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp new file mode 100644 index 00000000000..550466eea34 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp @@ -0,0 +1,341 @@ +#include "NodePainter.hpp" + +#include + +#include + +#include "FlowScene.hpp" +#include "Node.hpp" +#include "NodeDataModel.hpp" +#include "NodeGeometry.hpp" +#include "NodeGraphicsObject.hpp" +#include "NodeState.hpp" +#include "PortType.hpp" +#include "StyleCollection.hpp" + +using QtNodes::FlowScene; +using QtNodes::Node; +using QtNodes::NodeDataModel; +using QtNodes::NodeGeometry; +using QtNodes::NodeGraphicsObject; +using QtNodes::NodePainter; +using QtNodes::NodeState; + +void NodePainter::paint( QPainter* painter, Node& node, FlowScene const& scene ) { + NodeGeometry const& geom = node.nodeGeometry(); + + NodeState const& state = node.nodeState(); + + NodeGraphicsObject const& graphicsObject = node.nodeGraphicsObject(); + + geom.recalculateSize( painter->font() ); + + //-------------------------------------------- + NodeDataModel const* model = node.nodeDataModel(); + + drawNodeRect( painter, geom, model, graphicsObject ); + + drawConnectionPoints( painter, geom, state, model, scene ); + + drawFilledConnectionPoints( painter, geom, state, model ); + + drawModelName( painter, geom, state, model ); + + drawEntryLabels( painter, geom, state, model ); + + drawResizeRect( painter, geom, model ); + + drawValidationRect( painter, geom, model, graphicsObject ); + + /// call custom painter + if ( auto painterDelegate = model->painterDelegate() ) { + painterDelegate->paint( painter, geom, model ); + } +} + +void NodePainter::drawNodeRect( QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject ) { + NodeStyle const& nodeStyle = model->nodeStyle(); + + auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor + : nodeStyle.NormalBoundaryColor; + + if ( geom.hovered() ) { + QPen p( color, nodeStyle.HoveredPenWidth ); + painter->setPen( p ); + } + else { + QPen p( color, nodeStyle.PenWidth ); + painter->setPen( p ); + } + + QLinearGradient gradient( QPointF( 0.0, 0.0 ), QPointF( 2.0, geom.height() ) ); + + gradient.setColorAt( 0.0, nodeStyle.GradientColor0 ); + gradient.setColorAt( 0.03, nodeStyle.GradientColor1 ); + gradient.setColorAt( 0.97, nodeStyle.GradientColor2 ); + gradient.setColorAt( 1.0, nodeStyle.GradientColor3 ); + + painter->setBrush( gradient ); + + float diam = nodeStyle.ConnectionPointDiameter; + + QRectF boundary( -diam, -diam, 2.0 * diam + geom.width(), 2.0 * diam + geom.height() ); + + double const radius = 3.0; + + painter->drawRoundedRect( boundary, radius, radius ); +} + +void NodePainter::drawConnectionPoints( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model, + FlowScene const& scene ) { + NodeStyle const& nodeStyle = model->nodeStyle(); + auto const& connectionStyle = StyleCollection::connectionStyle(); + + float diameter = nodeStyle.ConnectionPointDiameter; + auto reducedDiameter = diameter * 0.6; + + for ( PortType portType : { PortType::Out, PortType::In } ) { + size_t n = state.getEntries( portType ).size(); + + for ( unsigned int i = 0; i < n; ++i ) { + QPointF p = geom.portScenePosition( i, portType ); + + auto const& dataType = model->dataType( portType, i ); + + bool canConnect = + ( state.getEntries( portType )[i].empty() || + ( portType == PortType::Out && model->portOutConnectionPolicy( i ) == + NodeDataModel::ConnectionPolicy::Many ) ); + + double r = 1.0; + if ( state.isReacting() && canConnect && portType == state.reactingPortType() ) { + + auto diff = geom.draggingPos() - p; + double dist = std::sqrt( QPointF::dotProduct( diff, diff ) ); + bool typeConvertable = false; + + { + if ( portType == PortType::In ) { + typeConvertable = scene.registry().getTypeConverter( + state.reactingDataType(), dataType ) != nullptr; + } + else { + typeConvertable = scene.registry().getTypeConverter( + dataType, state.reactingDataType() ) != nullptr; + } + } + + if ( state.reactingDataType().id == dataType.id || typeConvertable ) { + double const thres = 40.0; + r = ( dist < thres ) ? ( 2.0 - dist / thres ) : 1.0; + } + else { + double const thres = 80.0; + r = ( dist < thres ) ? ( dist / thres ) : 1.0; + } + } + + if ( connectionStyle.useDataDefinedColors() ) { + painter->setBrush( connectionStyle.normalColor( dataType.id ) ); + } + else { + painter->setBrush( nodeStyle.ConnectionPointColor ); + } + + painter->drawEllipse( p, reducedDiameter * r, reducedDiameter * r ); + } + }; +} + +void NodePainter::drawFilledConnectionPoints( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model ) { + NodeStyle const& nodeStyle = model->nodeStyle(); + auto const& connectionStyle = StyleCollection::connectionStyle(); + + auto diameter = nodeStyle.ConnectionPointDiameter; + + for ( PortType portType : { PortType::Out, PortType::In } ) { + size_t n = state.getEntries( portType ).size(); + + for ( size_t i = 0; i < n; ++i ) { + QPointF p = geom.portScenePosition( i, portType ); + + if ( !state.getEntries( portType )[i].empty() ) { + auto const& dataType = model->dataType( portType, i ); + + if ( connectionStyle.useDataDefinedColors() ) { + QColor const c = connectionStyle.normalColor( dataType.id ); + painter->setPen( c ); + painter->setBrush( c ); + } + else { + painter->setPen( nodeStyle.FilledConnectionPointColor ); + painter->setBrush( nodeStyle.FilledConnectionPointColor ); + } + + painter->drawEllipse( p, diameter * 0.4, diameter * 0.4 ); + } + } + } +} + +void NodePainter::drawModelName( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model ) { + NodeStyle const& nodeStyle = model->nodeStyle(); + + Q_UNUSED( state ); + + if ( !model->captionVisible() ) return; + + QString const& name = model->caption(); + + QFont f = painter->font(); + + f.setBold( true ); + + QFontMetrics metrics( f ); + + auto rect = metrics.boundingRect( name ); + + QPointF position( ( geom.width() - rect.width() ) / 2.0, + ( geom.spacing() + geom.entryHeight() ) / 3.0 ); + + painter->setFont( f ); + painter->setPen( nodeStyle.FontColor ); + painter->drawText( position, name ); + + f.setBold( false ); + painter->setFont( f ); +} + +void NodePainter::drawEntryLabels( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model ) { + QFontMetrics const& metrics = painter->fontMetrics(); + + for ( PortType portType : { PortType::Out, PortType::In } ) { + auto const& nodeStyle = model->nodeStyle(); + + auto& entries = state.getEntries( portType ); + + size_t n = entries.size(); + + for ( size_t i = 0; i < n; ++i ) { + QPointF p = geom.portScenePosition( i, portType ); + + if ( entries[i].empty() ) + painter->setPen( nodeStyle.FontColorFaded ); + else + painter->setPen( nodeStyle.FontColor ); + + QString s; + + if ( model->portCaptionVisible( portType, i ) ) { + s = model->portCaption( portType, i ); + } + else { + s = model->dataType( portType, i ).name; + } + + auto rect = metrics.boundingRect( s ); + + p.setY( p.y() + rect.height() / 4.0 ); + + switch ( portType ) { + case PortType::In: + p.setX( 5.0 ); + break; + + case PortType::Out: + p.setX( geom.width() - 5.0 - rect.width() ); + break; + + default: + break; + } + + painter->drawText( p, s ); + } + } +} + +void NodePainter::drawResizeRect( QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model ) { + if ( model->resizable() ) { + painter->setBrush( Qt::gray ); + + painter->drawEllipse( geom.resizeRect() ); + } +} + +void NodePainter::drawValidationRect( QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject ) { + auto modelValidationState = model->validationState(); + + if ( modelValidationState != NodeValidationState::Valid ) { + NodeStyle const& nodeStyle = model->nodeStyle(); + + auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor + : nodeStyle.NormalBoundaryColor; + + if ( geom.hovered() ) { + QPen p( color, nodeStyle.HoveredPenWidth ); + painter->setPen( p ); + } + else { + QPen p( color, nodeStyle.PenWidth ); + painter->setPen( p ); + } + + // Drawing the validation message background + if ( modelValidationState == NodeValidationState::Error ) { + painter->setBrush( nodeStyle.ErrorColor ); + } + else { + painter->setBrush( nodeStyle.WarningColor ); + } + + double const radius = 3.0; + + float diam = nodeStyle.ConnectionPointDiameter; + + QRectF boundary( -diam, + -diam + geom.height() - geom.validationHeight(), + 2.0 * diam + geom.width(), + 2.0 * diam + geom.validationHeight() ); + + painter->drawRoundedRect( boundary, radius, radius ); + + painter->setBrush( Qt::gray ); + + // Drawing the validation message itself + QString const& errorMsg = model->validationMessage(); + + QFont f = painter->font(); + + QFontMetrics metrics( f ); + + auto rect = metrics.boundingRect( errorMsg ); + + QPointF position( ( geom.width() - rect.width() ) / 2.0, + geom.height() - ( geom.validationHeight() - diam ) / 2.0 ); + + painter->setFont( f ); + painter->setPen( nodeStyle.FontColor ); + painter->drawText( position, errorMsg ); + } +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp new file mode 100644 index 00000000000..6923a660e4b --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include + +namespace QtNodes { + +class Node; +class NodeState; +class NodeGeometry; +class NodeGraphicsObject; +class NodeDataModel; +class FlowItemEntry; +class FlowScene; + +class NodePainter +{ + public: + NodePainter(); + + public: + static void paint( QPainter* painter, Node& node, FlowScene const& scene ); + + static void drawNodeRect( QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject ); + + static void drawModelName( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model ); + + static void drawEntryLabels( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model ); + + static void drawConnectionPoints( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model, + FlowScene const& scene ); + + static void drawFilledConnectionPoints( QPainter* painter, + NodeGeometry const& geom, + NodeState const& state, + NodeDataModel const* model ); + + static void + drawResizeRect( QPainter* painter, NodeGeometry const& geom, NodeDataModel const* model ); + + static void drawValidationRect( QPainter* painter, + NodeGeometry const& geom, + NodeDataModel const* model, + NodeGraphicsObject const& graphicsObject ); +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp new file mode 100644 index 00000000000..f41c08f6d97 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp @@ -0,0 +1,83 @@ +#include "NodeState.hpp" + +#include "NodeDataModel.hpp" + +#include "Connection.hpp" + +using QtNodes::Connection; +using QtNodes::NodeDataModel; +using QtNodes::NodeDataType; +using QtNodes::NodeState; +using QtNodes::PortIndex; +using QtNodes::PortType; + +NodeState::NodeState( std::unique_ptr const& model ) : + _inConnections( model->nPorts( PortType::In ) ), + _outConnections( model->nPorts( PortType::Out ) ), + _reaction( NOT_REACTING ), + _reactingPortType( PortType::None ), + _resizing( false ) {} + +std::vector const& NodeState::getEntries( PortType portType ) const { + if ( portType == PortType::In ) + return _inConnections; + else + return _outConnections; +} + +std::vector& NodeState::getEntries( PortType portType ) { + if ( portType == PortType::In ) + return _inConnections; + else + return _outConnections; +} + +NodeState::ConnectionPtrSet NodeState::connections( PortType portType, PortIndex portIndex ) const { + auto const& connections = getEntries( portType ); + + return connections[portIndex]; +} + +void NodeState::setConnection( PortType portType, PortIndex portIndex, Connection& connection ) { + auto& connections = getEntries( portType ); + + connections.at( portIndex ).insert( std::make_pair( connection.id(), &connection ) ); +} + +void NodeState::eraseConnection( PortType portType, PortIndex portIndex, QUuid id ) { + getEntries( portType )[portIndex].erase( id ); +} + +NodeState::ReactToConnectionState NodeState::reaction() const { + return _reaction; +} + +PortType NodeState::reactingPortType() const { + return _reactingPortType; +} + +NodeDataType NodeState::reactingDataType() const { + return _reactingDataType; +} + +void NodeState::setReaction( ReactToConnectionState reaction, + PortType reactingPortType, + NodeDataType reactingDataType ) { + _reaction = reaction; + + _reactingPortType = reactingPortType; + + _reactingDataType = std::move( reactingDataType ); +} + +bool NodeState::isReacting() const { + return _reaction == REACTING; +} + +void NodeState::setResizing( bool resizing ) { + _resizing = resizing; +} + +bool NodeState::resizing() const { + return _resizing; +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp new file mode 100644 index 00000000000..2fc9ef460f8 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp @@ -0,0 +1,119 @@ +#include "NodeStyle.hpp" + +#include + +#include +#include +#include +#include +#include + +#include + +#include "StyleCollection.hpp" + +using QtNodes::NodeStyle; + +inline void initResources() { + Q_INIT_RESOURCE( resources ); +} + +NodeStyle::NodeStyle() { + // Explicit resources inialization for preventing the static initialization + // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order + initResources(); + + // This configuration is stored inside the compiled unit and is loaded statically + loadJsonFile( ":DefaultStyle.json" ); +} + +NodeStyle::NodeStyle( QString jsonText ) { + loadJsonText( jsonText ); +} + +void NodeStyle::setNodeStyle( QString jsonText ) { + NodeStyle style( jsonText ); + + StyleCollection::setNodeStyle( style ); +} + +#ifdef STYLE_DEBUG +# define NODE_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) \ + { \ + if ( v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null ) \ + qWarning() << "Undefined value for parameter:" << #variable; \ + } +#else +# define NODE_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) +#endif + +#define NODE_STYLE_READ_COLOR( values, variable ) \ + { \ + auto valueRef = values[#variable]; \ + NODE_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ + if ( valueRef.isArray() ) { \ + auto colorArray = valueRef.toArray(); \ + std::vector rgb; \ + rgb.reserve( 3 ); \ + for ( auto it = colorArray.begin(); it != colorArray.end(); ++it ) { \ + rgb.push_back( ( *it ).toInt() ); \ + } \ + variable = QColor( rgb[0], rgb[1], rgb[2] ); \ + } \ + else { \ + variable = QColor( valueRef.toString() ); \ + } \ + } + +#define NODE_STYLE_READ_FLOAT( values, variable ) \ + { \ + auto valueRef = values[#variable]; \ + NODE_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ + variable = valueRef.toDouble(); \ + } + +void NodeStyle::loadJsonFile( QString styleFile ) { + QFile file( styleFile ); + + if ( !file.open( QIODevice::ReadOnly ) ) { + qWarning() << "Couldn't open file " << styleFile; + + return; + } + + loadJsonFromByteArray( file.readAll() ); +} + +void NodeStyle::loadJsonText( QString jsonText ) { + loadJsonFromByteArray( jsonText.toUtf8() ); +} + +void NodeStyle::loadJsonFromByteArray( QByteArray const& byteArray ) { + QJsonDocument json( QJsonDocument::fromJson( byteArray ) ); + + QJsonObject topLevelObject = json.object(); + + QJsonValueRef nodeStyleValues = topLevelObject["NodeStyle"]; + + QJsonObject obj = nodeStyleValues.toObject(); + + NODE_STYLE_READ_COLOR( obj, NormalBoundaryColor ); + NODE_STYLE_READ_COLOR( obj, SelectedBoundaryColor ); + NODE_STYLE_READ_COLOR( obj, GradientColor0 ); + NODE_STYLE_READ_COLOR( obj, GradientColor1 ); + NODE_STYLE_READ_COLOR( obj, GradientColor2 ); + NODE_STYLE_READ_COLOR( obj, GradientColor3 ); + NODE_STYLE_READ_COLOR( obj, ShadowColor ); + NODE_STYLE_READ_COLOR( obj, FontColor ); + NODE_STYLE_READ_COLOR( obj, FontColorFaded ); + NODE_STYLE_READ_COLOR( obj, ConnectionPointColor ); + NODE_STYLE_READ_COLOR( obj, FilledConnectionPointColor ); + NODE_STYLE_READ_COLOR( obj, WarningColor ); + NODE_STYLE_READ_COLOR( obj, ErrorColor ); + + NODE_STYLE_READ_FLOAT( obj, PenWidth ); + NODE_STYLE_READ_FLOAT( obj, HoveredPenWidth ); + NODE_STYLE_READ_FLOAT( obj, ConnectionPointDiameter ); + + NODE_STYLE_READ_FLOAT( obj, Opacity ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp new file mode 100644 index 00000000000..a4461ad1d59 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp @@ -0,0 +1,7 @@ +#include "Properties.hpp" + +using QtNodes::Properties; + +void Properties::put( QString const& name, QVariant const& v ) { + _values.insert( name, v ); +} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp new file mode 100644 index 00000000000..6aa44730393 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include + +#include "Export.hpp" + +namespace QtNodes { + +class NODE_EDITOR_PUBLIC Properties +{ + public: + void put( QString const& name, QVariant const& v ); + + template + bool get( QString name, T* v ) const { + QVariant const& var = _values[name]; + + if ( var.canConvert() ) { + *v = _values[name].value(); + + return true; + } + + return false; + } + + QVariantMap const& values() const { return _values; } + + QVariantMap& values() { return _values; } + + private: + QVariantMap _values; +}; +} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp new file mode 100644 index 00000000000..f7aacf32f28 --- /dev/null +++ b/src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp @@ -0,0 +1,36 @@ +#include "StyleCollection.hpp" + +using QtNodes::ConnectionStyle; +using QtNodes::FlowViewStyle; +using QtNodes::NodeStyle; +using QtNodes::StyleCollection; + +NodeStyle const& StyleCollection::nodeStyle() { + return instance()._nodeStyle; +} + +ConnectionStyle const& StyleCollection::connectionStyle() { + return instance()._connectionStyle; +} + +FlowViewStyle const& StyleCollection::flowViewStyle() { + return instance()._flowViewStyle; +} + +void StyleCollection::setNodeStyle( NodeStyle nodeStyle ) { + instance()._nodeStyle = nodeStyle; +} + +void StyleCollection::setConnectionStyle( ConnectionStyle connectionStyle ) { + instance()._connectionStyle = connectionStyle; +} + +void StyleCollection::setFlowViewStyle( FlowViewStyle flowViewStyle ) { + instance()._flowViewStyle = flowViewStyle; +} + +StyleCollection& StyleCollection::instance() { + static StyleCollection collection; + + return collection; +} diff --git a/src/Dataflow/QtGui/filelist.cmake b/src/Dataflow/QtGui/filelist.cmake new file mode 100644 index 00000000000..d36ae55e95e --- /dev/null +++ b/src/Dataflow/QtGui/filelist.cmake @@ -0,0 +1,13 @@ +# ---------------------------------------------------- +# This file should not be generated by the radium script +# ---------------------------------------------------- + +set(dataflow_qtgui_sources GraphEditor/GraphEditorView.cpp GraphEditor/NodeAdapterModel.cpp + GraphEditor/WidgetFactory.cpp +) + +set(dataflow_qtgui_headers GraphEditor/ConnectionStatusData.hpp GraphEditor/GraphEditorView.hpp + GraphEditor/NodeAdapterModel.hpp GraphEditor/WidgetFactory.hpp +) + +set(dataflow_qtgui_inlines) From 09826ae8ebf76e7283b99f3d309d11d5de0df754 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 17 Oct 2022 15:21:24 +0200 Subject: [PATCH 011/239] [examples][dataflow] add dataflow graph editor example --- examples/DataflowExamples/CMakeLists.txt | 2 +- .../GraphEditor/CMakeLists.txt | 61 ++++ .../GraphEditor/GraphEditor.qrc | 10 + .../GraphEditor/MainWindow.cpp | 260 ++++++++++++++++++ .../GraphEditor/MainWindow.hpp | 46 ++++ .../GraphEditor/images/copy.png | Bin 0 -> 1338 bytes .../GraphEditor/images/cut.png | Bin 0 -> 1323 bytes .../GraphEditor/images/new.png | Bin 0 -> 852 bytes .../GraphEditor/images/open.png | Bin 0 -> 2073 bytes .../GraphEditor/images/paste.png | Bin 0 -> 1645 bytes .../GraphEditor/images/save.png | Bin 0 -> 2699 bytes .../DataflowExamples/GraphEditor/main.cpp | 47 ++++ 12 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 examples/DataflowExamples/GraphEditor/CMakeLists.txt create mode 100644 examples/DataflowExamples/GraphEditor/GraphEditor.qrc create mode 100644 examples/DataflowExamples/GraphEditor/MainWindow.cpp create mode 100644 examples/DataflowExamples/GraphEditor/MainWindow.hpp create mode 100644 examples/DataflowExamples/GraphEditor/images/copy.png create mode 100644 examples/DataflowExamples/GraphEditor/images/cut.png create mode 100644 examples/DataflowExamples/GraphEditor/images/new.png create mode 100644 examples/DataflowExamples/GraphEditor/images/open.png create mode 100644 examples/DataflowExamples/GraphEditor/images/paste.png create mode 100644 examples/DataflowExamples/GraphEditor/images/save.png create mode 100644 examples/DataflowExamples/GraphEditor/main.cpp diff --git a/examples/DataflowExamples/CMakeLists.txt b/examples/DataflowExamples/CMakeLists.txt index 8f199a8063f..5d087fa6c61 100644 --- a/examples/DataflowExamples/CMakeLists.txt +++ b/examples/DataflowExamples/CMakeLists.txt @@ -5,7 +5,7 @@ project(DataflowExamples VERSION 1.0.0) add_custom_target(${PROJECT_NAME}) add_custom_target(Install_${PROJECT_NAME}) -foreach(APP GraphSerialization HelloGraph) +foreach(APP GraphSerialization HelloGraph GraphEditor) add_subdirectory(${APP}) add_dependencies(${PROJECT_NAME} ${APP}) add_dependencies(Install_${PROJECT_NAME} Install_${APP}) diff --git a/examples/DataflowExamples/GraphEditor/CMakeLists.txt b/examples/DataflowExamples/GraphEditor/CMakeLists.txt new file mode 100644 index 00000000000..2cf917f12fb --- /dev/null +++ b/examples/DataflowExamples/GraphEditor/CMakeLists.txt @@ -0,0 +1,61 @@ +cmake_minimum_required(VERSION 3.6) +if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") + cmake_policy(SET CMP0071 NEW) +endif() +if(APPLE) + cmake_policy(SET CMP0042 NEW) +endif(APPLE) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(GraphEditor VERSION 0.0.1) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Set default install location to installed- folder in build dir we do not want to +# install to /usr by default +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX + "${CMAKE_CURRENT_BINARY_DIR}/installed-${CMAKE_CXX_COMPILER_ID}-${CMAKE_BUILD_TYPE}" + CACHE PATH "Install path prefix, prepended onto install directories." FORCE + ) + message(STATUS "Set install prefix to ${CMAKE_INSTALL_PREFIX}") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# /////////////////////////////// +find_package(Radium REQUIRED COMPONENTS DataflowQtGui) + +# Qt utility functions +include(QtFunctions) + +# Find Qt packages +find_qt_package(COMPONENTS Core Widgets REQUIRED) +set(QT_DEFAULT_MAJOR_VERSION ${QT_DEFAULT_MAJOR_VERSION} PARENT_SCOPE) +set(Qt_LIBRARIES Qt::Core Qt::Widgets) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +qt_wrap_ui(gui_uis ${gui_uis}) + +# -------------------------------------------------------------------------------------------------- + +set(app_sources main.cpp MainWindow.cpp) +set(app_resources GraphEditor.qrc) + +# set(app_headers) + +# delete ${app_headers} +add_executable(${PROJECT_NAME} ${app_sources} ${app_resources}) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + +target_link_libraries(${PROJECT_NAME} PUBLIC ${Qt_LIBRARIES} Radium::DataflowQtGui) + +configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/DataflowExamples/GraphEditor/GraphEditor.qrc b/examples/DataflowExamples/GraphEditor/GraphEditor.qrc new file mode 100644 index 00000000000..b34fa0ffaa2 --- /dev/null +++ b/examples/DataflowExamples/GraphEditor/GraphEditor.qrc @@ -0,0 +1,10 @@ + + + images/copy.png + images/cut.png + images/new.png + images/open.png + images/paste.png + images/save.png + + diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/examples/DataflowExamples/GraphEditor/MainWindow.cpp new file mode 100644 index 00000000000..fada6544658 --- /dev/null +++ b/examples/DataflowExamples/GraphEditor/MainWindow.cpp @@ -0,0 +1,260 @@ +#include "MainWindow.hpp" + +using namespace Ra::Dataflow::Core; +MainWindow::MainWindow() { + graphEdit = new GraphEditorView( nullptr ); + setCentralWidget( graphEdit ); + graphEdit->setFocusPolicy( Qt::StrongFocus ); + + createActions(); + createStatusBar(); + + readSettings(); + + connect( graphEdit, &GraphEditorView::needUpdate, this, &MainWindow::documentWasModified ); + + setCurrentFile( QString() ); + setUnifiedTitleAndToolBarOnMac( true ); + newFile(); + graphEdit->show(); +} + +void MainWindow::closeEvent( QCloseEvent* event ) { + if ( maybeSave() ) { + writeSettings(); + event->accept(); + } + else { + event->ignore(); + } +} + +void MainWindow::newFile() { + if ( maybeSave() ) { + // Currently edited graph must be deleted only after it is no more used by the editor + graphEdit->editGraph( nullptr ); + delete graph; + + setCurrentFile( "" ); + graph = new DataflowGraph( "untitled.flow" ); + auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); + graph->addFactory( defaultFactory->getName(), defaultFactory ); + graphEdit->editGraph( graph ); + } +} + +void MainWindow::open() { + if ( maybeSave() ) { + QString fileName = QFileDialog::getOpenFileName( this ); + if ( !fileName.isEmpty() ) loadFile( fileName ); + } +} + +bool MainWindow::save() { + if ( curFile.isEmpty() ) { return saveAs(); } + else { + return saveFile( curFile ); + } +} + +bool MainWindow::saveAs() { + QFileDialog dialog( this ); + dialog.setWindowModality( Qt::WindowModal ); + dialog.setAcceptMode( QFileDialog::AcceptSave ); + if ( dialog.exec() != QDialog::Accepted ) return false; + return saveFile( dialog.selectedFiles().first() ); +} + +void MainWindow::about() { + QMessageBox::about( this, + tr( "About Application" ), + tr( "The Application example demonstrates how to " + "write modern GUI applications using Qt, with a menu bar, " + "toolbars, and a status bar." ) ); +} + +void MainWindow::documentWasModified() { + setWindowModified( graph->m_recompile ); +} + +void MainWindow::createActions() { + + QMenu* fileMenu = menuBar()->addMenu( tr( "&File" ) ); + QToolBar* fileToolBar = addToolBar( tr( "File" ) ); + const QIcon newIcon = QIcon::fromTheme( "document-new", QIcon( ":/images/new.png" ) ); + QAction* newAct = new QAction( newIcon, tr( "&New" ), this ); + newAct->setShortcuts( QKeySequence::New ); + newAct->setStatusTip( tr( "Create a new file" ) ); + connect( newAct, &QAction::triggered, this, &MainWindow::newFile ); + fileMenu->addAction( newAct ); + fileToolBar->addAction( newAct ); + + const QIcon openIcon = QIcon::fromTheme( "document-open", QIcon( ":/images/open.png" ) ); + QAction* openAct = new QAction( openIcon, tr( "&Open..." ), this ); + openAct->setShortcuts( QKeySequence::Open ); + openAct->setStatusTip( tr( "Open an existing file" ) ); + connect( openAct, &QAction::triggered, this, &MainWindow::open ); + fileMenu->addAction( openAct ); + fileToolBar->addAction( openAct ); + + const QIcon saveIcon = QIcon::fromTheme( "document-save", QIcon( ":/images/save.png" ) ); + QAction* saveAct = new QAction( saveIcon, tr( "&Save" ), this ); + saveAct->setShortcuts( QKeySequence::Save ); + saveAct->setStatusTip( tr( "Save the document to disk" ) ); + connect( saveAct, &QAction::triggered, this, &MainWindow::save ); + fileMenu->addAction( saveAct ); + fileToolBar->addAction( saveAct ); + + const QIcon saveAsIcon = QIcon::fromTheme( "document-save-as" ); + QAction* saveAsAct = + fileMenu->addAction( saveAsIcon, tr( "Save &As..." ), this, &MainWindow::saveAs ); + saveAsAct->setShortcuts( QKeySequence::SaveAs ); + saveAsAct->setStatusTip( tr( "Save the document under a new name" ) ); + + fileMenu->addSeparator(); + + const QIcon exitIcon = QIcon::fromTheme( "application-exit" ); + QAction* exitAct = fileMenu->addAction( exitIcon, tr( "E&xit" ), this, &QWidget::close ); + exitAct->setShortcuts( QKeySequence::Quit ); + exitAct->setStatusTip( tr( "Exit the application" ) ); + + QMenu* editMenu = menuBar()->addMenu( tr( "&Edit" ) ); + QToolBar* editToolBar = addToolBar( tr( "Edit" ) ); + + QMenu* helpMenu = menuBar()->addMenu( tr( "&Help" ) ); + QAction* aboutAct = helpMenu->addAction( tr( "&About" ), this, &MainWindow::about ); + aboutAct->setStatusTip( tr( "Show the application's About box" ) ); + + QAction* aboutQtAct = helpMenu->addAction( tr( "About &Qt" ), qApp, &QApplication::aboutQt ); + aboutQtAct->setStatusTip( tr( "Show the Qt library's About box" ) ); +} + +void MainWindow::createStatusBar() { + statusBar()->showMessage( tr( "Ready" ) ); +} + +void MainWindow::readSettings() { + QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() ); + const QByteArray geometry = settings.value( "geometry", QByteArray() ).toByteArray(); + if ( geometry.isEmpty() ) { + const QRect availableGeometry = screen()->availableGeometry(); + resize( availableGeometry.width() / 3, availableGeometry.height() / 2 ); + move( ( availableGeometry.width() - width() ) / 2, + ( availableGeometry.height() - height() ) / 2 ); + } + else { + restoreGeometry( geometry ); + } + const QByteArray graphGeometry = settings.value( "graph", QByteArray() ).toByteArray(); + if ( graphGeometry.isEmpty() ) { graphEdit->resize( 800, 600 ); } + else { + graphEdit->restoreGeometry( graphGeometry ); + } +} + +void MainWindow::writeSettings() { + QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() ); + settings.setValue( "geometry", saveGeometry() ); + settings.setValue( "graph", graphEdit->saveGeometry() ); +} + +bool MainWindow::maybeSave() { +#if 0 + if (!textEdit->document()->isModified()) + return true; + const QMessageBox::StandardButton ret + = QMessageBox::warning(this, tr("Application"), + tr("The document has been modified.\n" + "Do you want to save your changes?"), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + switch (ret) { + case QMessageBox::Save: + return save(); + case QMessageBox::Cancel: + return false; + default: + break; + } +#endif + return true; +} + +void MainWindow::loadFile( const QString& fileName ) + +{ + QGuiApplication::setOverrideCursor( Qt::WaitCursor ); + { + QFile file( fileName ); + if ( !file.open( QFile::ReadOnly | QFile::Text ) ) { + QMessageBox::warning( + this, + tr( "Application" ), + tr( "Cannot read file %1:\n%2." ) + .arg( QDir::toNativeSeparators( fileName ), file.errorString() ) ); + return; + } + } + + graphEdit->editGraph( nullptr ); + + graph = new DataflowGraph( fileName.toStdString() ); + graph->loadFromJson( fileName.toStdString() ); + + // Todo, always embed default factory in the graph + auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); + graph->addFactory( defaultFactory->getName(), defaultFactory ); + + graphEdit->editGraph( graph ); + + QGuiApplication::restoreOverrideCursor(); + setCurrentFile( fileName ); + statusBar()->showMessage( tr( "File loaded" ), 2000 ); +} + +bool MainWindow::saveFile( const QString& fileName ) { + QString errorMessage; + + QGuiApplication::setOverrideCursor( Qt::WaitCursor ); + // TODO, if graph do not compile, tell it to the user ? + graph->compile(); + graph->saveToJson( fileName.toStdString() ); +#if 0 + QSaveFile file(fileName); + + if (file.open(QFile::WriteOnly | QFile::Text)) { + QTextStream out(&file); + out << textEdit->toPlainText(); + if (!file.commit()) { + errorMessage = tr("Cannot write file %1:\n%2.") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + } + } else { + errorMessage = tr("Cannot open file %1 for writing:\n%2.") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + } +#endif + QGuiApplication::restoreOverrideCursor(); + + if ( !errorMessage.isEmpty() ) { + QMessageBox::warning( this, tr( "Application" ), errorMessage ); + return false; + } + + setCurrentFile( fileName ); + statusBar()->showMessage( tr( "File saved" ), 2000 ); + return true; +} + +void MainWindow::setCurrentFile( const QString& fileName ) { + curFile = fileName; + // textEdit->document()->setModified(false); + setWindowModified( false ); + + QString shownName = curFile; + if ( curFile.isEmpty() ) shownName = "untitled.flow"; + setWindowFilePath( shownName ); +} + +QString MainWindow::strippedName( const QString& fullFileName ) { + return QFileInfo( fullFileName ).fileName(); +} diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.hpp b/examples/DataflowExamples/GraphEditor/MainWindow.hpp new file mode 100644 index 00000000000..760c41679e0 --- /dev/null +++ b/examples/DataflowExamples/GraphEditor/MainWindow.hpp @@ -0,0 +1,46 @@ +#pragma once +#include +#include + +#include + +#include + +using namespace Ra::Dataflow::QtGui::GraphEditor; + +/// TODO : transform this example in a grapheWidget allowing to edit graph or subgraphs +class MainWindow : public QMainWindow +{ + Q_OBJECT + + public: + MainWindow(); + + void loadFile( const QString& fileName ); + + protected: + void closeEvent( QCloseEvent* event ) override; + + private slots: + void newFile(); + void open(); + bool save(); + bool saveAs(); + void about(); + void documentWasModified(); + + private: + void createActions(); + void createStatusBar(); + void readSettings(); + void writeSettings(); + bool maybeSave(); + bool saveFile( const QString& fileName ); + void setCurrentFile( const QString& fileName ); + QString strippedName( const QString& fullFileName ); + + GraphEditorView* graphEdit; + QString curFile; + + DataflowGraph* graph { nullptr }; +}; diff --git a/examples/DataflowExamples/GraphEditor/images/copy.png b/examples/DataflowExamples/GraphEditor/images/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..2aeb28288f58ddffdd1d75115f170c5bf2773814 GIT binary patch literal 1338 zcmV-A1;zS_P)3P|jKV4ArrNQZsr&q&3Fam_48Lh`MiYI|sB6GiaYuZZ? zasnKC=d9Ws*vOa?K!llv;~9}LDH=;*C6q)snnH7j#=aB8{{oN_lX)cZ$XJrkAB0_u z!sQ7f5=*1>!|zQrby)$h>)1Bcke)jH%(>@ZE)hRgo<&7f4Jw)5udTzKv5Ch3thOcm zx#)*$6jZ zxjE=2CQwjNfFaFqeBMc>`320FTpYmRPeJsB@I$Z8%>Zil0#HL{*yW1HLMx&9BQ>hew>g`42C!b-4K{%G& zPNKpdi`TSeSQ5jt zz}D6l-wA<00HP?u@AqSEZ4IXsmC*dI=c&nj!3&r@K%3kE+d(!rH~Ds?RumQ0==)XR zsU3kw0Ovao0D#OTfFv_dBoZhqui^lIxB!co0iZTo)dw&I|ClF}JUvZG$*D>XptrLF zi9N;m7cj8^d~x*v0Ho^4m=ue}P^PGYdPu;S8Mtc&=A4=P=T~F|kkV6X0*FSV+&mNt zp}4dH*9HaLW{2dU0UTt+1SmUX21q@ea}t@;xj-PrWmUM?EueooW3GLWY2c%FnE?J> zJ*6hCuC8*3w@NB-xmUnF8@PU+0q)ELXD;m%K&qWYi;j}haZKLWGrnrG9>`fBl6@D%o`^aXzvlAyk=W}Pc`B^ahku5Y4`j}CIOJCBx$u% zLNX5qgK)WA+>l;M=(ES;f!poI@e{8lpM9a6Ogos0nT|fmct-%0O2q-B6)w#;8B$U@ z_bIi*;ou_U#l=Mq_2TiDT=X8flj*JiEiEl>>0qJM(w7a5mzS5}^Z8)2+4y~FX^BG+ z0F4U^3(c|s(4vx3lM#*SPTzP`J2hanT6y)<3jNMtFeLKw^6s-|9N8B@Q&W>$nrBK+ zA`_~b#ukg2H$b09M@J)VZEfEC{CpGp+d-zXy_)|gKy!05p8`rp5L7=^KR-Xu>(*#A zk-EA%=fkvJOdl{E&Bk~~0Hsptb~>HBVoFMHK9sb+zTTOWlOvfHXH#`n0F8}}Zi~f& wfq{WYZEdYHH#b*S)7=30e@0BgL78sX-$OZ87=>`i1ONa407*qoM6N<$g6NH5LI3~& literal 0 HcmV?d00001 diff --git a/examples/DataflowExamples/GraphEditor/images/cut.png b/examples/DataflowExamples/GraphEditor/images/cut.png new file mode 100644 index 0000000000000000000000000000000000000000..54638e9386dc8af40dcc9a3ee2f57c62e248e406 GIT binary patch literal 1323 zcmV+`1=RY9P)J0(1!|L=@Q+aTEb7Mu4u}WZlMQB24&|#EVDBq0{%sj+Z+)GGQx0 zAcdU1Fi0-lfEhqCt9ecIzWZ_$Xk-SF1YV<2^ak#QNA=LXgUd}Io1OMRbT7RYGCd%P zKS-r48v%?ldBMA#h)c%T#wHG+5V`$A{>3XcMvM|%P6Cssr>70f(>b}AV>T2D(&VEn zK_HANo+{rrN9){swakg0Ktz^QgDWfxHbzJY6ZK>&doEN10r&$qzrYSODpcQMh*H%o zSOat&76=4-MOF34plx)*)N}VCzH?EPkgku=6VK(8gNL*Np3E)-)CS$Jnai-RudhD8 z(qOgQ?SS}sjv4h}*IqNacG`39XxPWcN1v;!unrFm!4QYC z_Acj^H}`jQVc5OsUVtxVnxKZ?sWw9Q@B`QxpVZH#=NgKbEq?xOmsA>SUtb@%(9v@T zVsaH$PQ-_o)Qm6`=@qvgI2;Z@q}CZRo`stQer>5G8e5my4CXEh^CT4CHjaC}9?{K0 z$O&($1U%8bR;v{dcYhm%^O9V6PFgxKporEb{=Djv}7NhUQZ#8TNvL0_NhN zpdjG!cwn(uKwDk``_Co}Ev8e>XOzzgOoQTEd*;Gmnz9rW3I&M8V#v(QgrcG%P^;C@ z+1bfp?EcBQ$=j!OF1OE1fUdXLDR2(JYL~2dh{}J0;wgy^K9?&YTb2(8W8+7JA12t6 za%7Hg8ovjl(FiTAE%3|0ARIXHz6G;l!311sgoTBKM%*UnGOR4mGqn@!JL9_3Gp~Z> z{(6Z9p5FXo8}~etjeBORPg}}0?N~!YJv1~lfTgz=G8Dzb^Xb54Jd))oo;F?OoGLPS zee{DaT5J+A3>zitarZCFC+`J^U;fkNL<>x4^vOPTl^zTR1GKld!`H@^F|L6LX7M&{ zIA}acIyz>{0a00^F4~fx%15b|k+|p_1^WSy+Zxk*z z((l+8X-U2;oygOcJEspZj3vh3pSpKnaW$YpY_$lC`IPbC@H0!Ad}tF|z0RZAYfnh` z3=gmm{_}^(`7iFdNp1l4dp_;=2p%dvUFBG+@kd(qwpj}i9kWBpkvIuC`Kgv6`dk_f h&$IvkH~dc%_#Nkm8M#Em0nY#c002ovPDHLkV1hsWYZL$g literal 0 HcmV?d00001 diff --git a/examples/DataflowExamples/GraphEditor/images/new.png b/examples/DataflowExamples/GraphEditor/images/new.png new file mode 100644 index 0000000000000000000000000000000000000000..12131b01008a3ec29ec69f8b3f65c4b3c15b60d6 GIT binary patch literal 852 zcmV-a1FQUrP)IE2xvpcKl890$E8eWxeF8o0mWjmIG{M&7eWO9CVRojsy7@;&dpQ!a9t0J7gPX(5I|>76wd9YM;~Wq>Ghkp6rY@= zsi|pt_x=M#qtRtve?bMn?-1>_C5DBX;{}vm&C%raD|#~il%B<2&`e?uix*V@+JPAL z6CgbQkZR2~6*sGtFK$zbV~w|k*M3m}@OjM5T^_Z^d)w}xJHF(IUAVpO;*`oW;C1B2 zbys585IO4RGepz@rv~8kI;}Y(n3!mCfS7AgQ=I?+DlNG&;!abH5Df(KZTl#;d_@48 zJKJjfX^uQ>h#Iar6M#C8w^So4*Tn<|1^_iEoENLSCA-QKz)fKe6)JLW%zjz|XlmW9 z@suY3Q`{VJZCgNKnyVajp6h`@6-N=kYKbun)^`LT3}hx^wrO>XsHzE2w#B^A1Ngov zV}~hV3>btQ09#E(0CWRWn5~q>9Op*n90161U8$i6fc^Yd5_2;F2sIc&=-PaaDX2~W zTL1&dm}fQw05ax<-uM>_{4Cgt02pUFPqb7NBbWjJ0t`O;#ZxHoL%3#1_iOo5hu6MT z0mxO4x=yW24v?>f-nYV%Jx`{y-lMT`>)O!u6_~SbsQ_#=q-z-e;1PdX7gN*|3t4aE z?!Drr!OKg0ZH#q?HK_Wx9NlHvJdYn4{+`bsH@(TacqVT~0BG|6fti$^=|8_20YcOC e{Jor>rG5iZY$c(Rl?rzN0000RP)CWtKz1rEEwa&Ubd$6s{da=&VIP1;2`BM0`Wb;D z{M-!)vd0-ix#k=pmKgFxQgYk(eOi$0QlJD2NbOI5d5567avIIj< zv@<}9dj`!tE;Q@xs8{wNhf{?zUNu9!u=J3D!C+h_ze{}K1V|htI6?j0>T@UYxhDij zhnKMl|M#(rT!AcZ}eF)wgP_d zAX10jekLr}U#(CrCA<(opAbNs2#S{v>vbjZBPMG2>Dlh2!oC#Mk@9m9Zc z2%^^3CBjkLC=!RpAFqNhVw6pZB#Q!^oqj*T-F(Vw$mLX*nO_ zNX-3DeeTpEomGUYnz~IsN92z9JUkMF)7=lf!HB}*Vw`?29z6pA^e+N~guKQ7E$%u) zC{dC3_66}y+`oN81yr%;K@{IH8|1O-3y*riL6}qGd@SEo}8X#Q_kq zZ|eG3QMpHgF(8Pc07FLLD$kpMaaUZZl&Qk=tr>Kq76&g@g}Kbh`}mD}bLg zSob6#9DIaI(-=7VVTPPm{Z+FbtPVT4Iv?2~9(pg}6TG@z0w%t}gOqRBIG>z~1AjdP zW3QR%ZYsA%*Mp?X$w>b?i;UGgQ?+sSsoXX8X`%zES@sOGBngFV;lVB@d3_10@@V;lg>dY2BN>v^RZ2^?j@k8NdQ$X;_ z_a$H}ZfeJ=gha#~If~qTHdcoJ4Lhqwh-3dbkKgY5JsSA}oWGEOlkpdDqpb%S#7{~T>2A7M zsP8QLl$cN`w?OF*Jo#7bDJpj@!Dj^fwz=d)jlRsXTKwbW85}(HH?VT^u<$rb~rBzaiB|lB=W)A?(%51dO$!?mU^ ze8O#IHd*n50bG}w!BKl4^90wps7K`#)>4RXJ_(`usyx1v$_9j`-;Pq{6K%jV&lNRO%>FPb&x$A5bBEmwG~ zsNAo;tlZJ!2;Bu-bsyf#uSL0b05WG_^}?r@soeJn(y81#_yhC8@e_i|9PXV^3ptdy z4Z_G4qZfz6FCzC2|JrSe-C>#48@AYMyIwNBFNyyFX1QPd5r93<00000NkvXXu0mjf DP*&js literal 0 HcmV?d00001 diff --git a/examples/DataflowExamples/GraphEditor/images/paste.png b/examples/DataflowExamples/GraphEditor/images/paste.png new file mode 100644 index 0000000000000000000000000000000000000000..c14425cad1ff1b2c5628be5769c9e9e52b78635f GIT binary patch literal 1645 zcmV-z29o)SP)3Gx zefpoBhy8x@pP6$JN-2UQgm?vbp2urQDfM^OC?_DeL%RWJmr}mQV`N#@2O9RfK(_`t z{}ccw{I~7(;$U4wL~hQBiYr&HP+e1vKmedn2xTZiDQ44YUa1!^ytGbiJMka6M}vG@ zLrgqP2loG9i`^_G_)0Q7JWOR}B`e8wT^_)0sjwm&qAGGfr|N;V0|+ezNTCh%UA#yx zm%}d>QA&}|=UH4_q);eO)MK@^wUhx^fcS#;fB^tDLmDAYR7=GY}hKA6kOPAcP zuC561)~X!<6GT-k!gJLz@}5tjD3MBG2|?Jgh=y$Das?8ijz6FOD-BH>0V2R=mW1#7 zc%Dafb+w*QOZ4j1tA~UTD5c(75x}xR0JbfV+O`xp2&58N!i*b_Ou6iP^*huzHW7R; z!Gvcr8OqDc&1OD!?ARu~e-17|ECJBj*{Kd4I;3nN2w4!eEgZ+jLQu5L8XEw|vWPgA zt^#0Lco4wc+#EGEH3lFWjdJA3kD9q>HlpG95}EZ=m1y-ptN)rjt$|Ez*CB`q|1$rgVVB? zrhTuowc{78+xt2VFSK*}&LsW){Unn~lv3vU`0?ZDx~wzj&Yk1v(W4wbe3;(eUJ{9f z1T3Ky+yU+X&gS^%luv8N&-msq-zN6;H<^5HH8r836d;2i zMi$(072p5qZ>Vh8NaE}}Opji}iIlOmc?X+Yw)07OC1VMf&t~%6oz60$?T8IbC+FC= zE6VyM05f|%YwGIi0L*nNl`;TID-fW$T5#c=V<;@j=Em6&k5ZJ9d&7MsWd+;Xe@so= z%X~bXX3}54&3G)#YcF^p0JU^FLZOfeE#2xLjsYt8K48JdxS&uRBOHrUx22Yncy@gS z>8!_S|DTwyyiCjXgSZP}yn>`ylz7@9$3iWtfa7VPmemyj0M^GtgvulQziMM4o~aCT z;opPo+4&R7qYk+;M*}DWVQT;uOChBkB`_)#q+A6+Sfwh=^&|RlY=-e!kMI5ZkKD@C z;6z-a5gX5w6g>yi0i}>q@=$770agVN2uuwG7@cUt@bD?; zK3j0A)Rg|7%OH8oYxFCwW^r%olu$Hy-NXMuhl?5?;8OW$_d zPbP%{KnOr#TKwMNV7j}zJEh->%p}lW?Q_jx?V5KpZV_N`@cndOUtjvnnKP!vrO=Of r0*SSrV$g&__6vQz4|I&^4Jxf00000NkvXXu0mjfT=)$w literal 0 HcmV?d00001 diff --git a/examples/DataflowExamples/GraphEditor/images/save.png b/examples/DataflowExamples/GraphEditor/images/save.png new file mode 100644 index 0000000000000000000000000000000000000000..e65a29d5f1735d95d5e96fce818e251fbbc88ee1 GIT binary patch literal 2699 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 FV1kc9>)`+Z literal 0 HcmV?d00001 diff --git a/examples/DataflowExamples/GraphEditor/main.cpp b/examples/DataflowExamples/GraphEditor/main.cpp new file mode 100644 index 00000000000..c2acc43626c --- /dev/null +++ b/examples/DataflowExamples/GraphEditor/main.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include "MainWindow.hpp" + +#include +#include +#include + +using namespace Ra::Dataflow::Core; + +int main( int argc, char* argv[] ) { + Q_INIT_RESOURCE( GraphEditor ); + + QApplication app( argc, argv ); + QCoreApplication::setOrganizationName( "STORM-IRIT" ); + QCoreApplication::setApplicationName( "Radium flowgraph example" ); + QCoreApplication::setApplicationVersion( QT_VERSION_STR ); + QCommandLineParser parser; + parser.setApplicationDescription( QCoreApplication::applicationName() ); + parser.addHelpOption(); + parser.addVersionOption(); + parser.addPositionalArgument( "file", "The file to open." ); + parser.process( app ); + + // create the custom node factory + NodeFactorySet::mapped_type customFactory { + new NodeFactorySet::mapped_type::element_type( "ExampleCustomFactory" ) }; + + // add node creators to the factory + customFactory->registerNodeCreator>>( + Sources::SingleDataSourceNode>::getTypename() + "_", "Source" ); + customFactory->registerNodeCreator>( + Filters::FilterNode::getTypename() + "_", "Filters" ); + customFactory->registerNodeCreator>>( + Sinks::SinkNode>::getTypename() + "_", "Sink" ); + + // register the factory into the system to enable loading any graph that use these nodes + NodeFactoriesManager::registerFactory( customFactory ); + + MainWindow mainWin; + if ( !parser.positionalArguments().isEmpty() ) + mainWin.loadFile( parser.positionalArguments().first() ); + mainWin.show(); + return app.exec(); +} From 4f5201c4724e88325d67ab8539a4758f27f7fa42 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 21 Sep 2022 20:13:35 +0200 Subject: [PATCH 012/239] [dataflow-cmake] fix install of dataflow components --- src/Dataflow/Config.cmake.in | 23 -------- src/Dataflow/Core/Config.cmake.in | 4 +- src/Dataflow/QtGui/Config.cmake.in | 14 ++--- .../QtGui/QtNodeEditor/CMakeLists.txt | 5 +- .../cmake/NodeEditorConfig.cmake.in | 2 +- .../QtNodeEditor/cmake/QtFunctions.cmake | 59 ------------------- 6 files changed, 11 insertions(+), 96 deletions(-) delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in index 35e71aef4de..1abc48cd08c 100644 --- a/src/Dataflow/Config.cmake.in +++ b/src/Dataflow/Config.cmake.in @@ -25,29 +25,6 @@ if (Dataflow_FOUND AND NOT TARGET Dataflow) endif() endif() - - if (OFF) - if(@RADIUM_GENERATE_LIB_ENGINE@ AND NOT Engine_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake") - set(Engine_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency Engine not found") - set(Configure_Dataflow OFF) - endif() - endif() - if(@RADIUM_GENERATE_LIB_GUI@ AND NOT Gui_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake") - set(Engine_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency Gui not found") - set(Configure_Dataflow OFF) - endif() - endif() - endif() endif() # configure Dataflow component diff --git a/src/Dataflow/Core/Config.cmake.in b/src/Dataflow/Core/Config.cmake.in index 9d8b0df9996..7cd8b9dacfa 100644 --- a/src/Dataflow/Core/Config.cmake.in +++ b/src/Dataflow/Core/Config.cmake.in @@ -10,8 +10,8 @@ if (DataflowCore_FOUND AND NOT TARGET DataflowCore) include(${CMAKE_CURRENT_LIST_DIR}/../Core/RadiumCoreConfig.cmake) else() set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Engine: dependency Core not found") - set(Configure_Engine OFF) + set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowCore: dependency Core not found") + set(Configure_DataflowCore OFF) endif() endif() endif() diff --git a/src/Dataflow/QtGui/Config.cmake.in b/src/Dataflow/QtGui/Config.cmake.in index 22554130efa..8729b0003d2 100644 --- a/src/Dataflow/QtGui/Config.cmake.in +++ b/src/Dataflow/QtGui/Config.cmake.in @@ -7,17 +7,17 @@ if (DataflowQtGui_FOUND AND NOT TARGET DataflowQtGui) if(NOT DataflowCore_FOUND) # if in source dir if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake") - set(Core_FOUND TRUE) + set(DataflowCore_FOUND TRUE) include(${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake) else() # if in install dir if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake") - set(Core_FOUND TRUE) + set(DataflowCore_FOUND TRUE) include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) else() set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Engine: dependency DataflowCore not found") - set(Configure_Engine OFF) + set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowQtGui: dependency DataflowCore not found") + set(Configure_DataflowQtGui OFF) endif() endif() endif() @@ -27,15 +27,15 @@ if (DataflowQtGui_FOUND AND NOT TARGET DataflowQtGui) include(${CMAKE_CURRENT_LIST_DIR}/../Gui/RadiumGuiConfig.cmake) else() set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::PluginBase: dependency Gui not found") - set(Configure_PluginBase OFF) + set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowQtGui: dependency Gui not found") + set(Configure_DataflowQtGui OFF) endif() endif() endif() if(Configure_DataflowQtGui) if(NOT TARGET NodeEditor) - find_dependency(NodeEditor REQUIRED) + find_dependency(NodeEditor REQUIRED PATHS ${CMAKE_CURRENT_LIST_DIR}/NodeEditor) endif() include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/QtGui/DataflowQtGuiTargets.cmake") endif() diff --git a/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt b/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt index 7274a1b55f2..843e3b89027 100644 --- a/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt +++ b/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt @@ -35,7 +35,6 @@ if(BUILD_DEBUG_POSTFIX_D) set(CMAKE_DEBUG_POSTFIX d) endif() -# include(${CMAKE_CURRENT_LIST_DIR}/../../../../cmake/QtFunctions.cmake) Qt utility functions include(QtFunctions) # Find the QtWidgets library @@ -127,7 +126,7 @@ target_sources(NodeEditor PRIVATE ${nodes_moc}) include(GNUInstallDirs) -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/NodeEditor) +set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/Radium/DataflowQtGui/NodeEditor) install(TARGETS NodeEditor EXPORT NodeEditorTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -148,6 +147,4 @@ configure_package_config_file( ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake INSTALL_DESTINATION ${INSTALL_CONFIGDIR} ) -install(FILES ${CMAKE_CURRENT_LIST_DIR}/cmake/QtFunctions.cmake DESTINATION ${INSTALL_CONFIGDIR}) - install(FILES ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake DESTINATION ${INSTALL_CONFIGDIR}) diff --git a/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in b/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in index f3081994a0e..15670832a28 100644 --- a/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in +++ b/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in @@ -1,7 +1,7 @@ get_filename_component(NodeEditor_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) if(NOT TARGET NodeEditor::NodeEditor) - include(${CMAKE_CURRENT_LIST_DIR}/../QtFunctions.cmake) + include(${CMAKE_CURRENT_LIST_DIR}/../../QtFunctions.cmake) check_and_set_qt_version("@QT_DEFAULT_MAJOR_VERSION@") find_qt_dependency(COMPONENTS Core Widgets OpenGL Gui REQUIRED) diff --git a/src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake b/src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake deleted file mode 100644 index ae9d0e99be1..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/cmake/QtFunctions.cmake +++ /dev/null @@ -1,59 +0,0 @@ -cmake_minimum_required(VERSION 3.12) - -# Find Qt5 or Qt6 packages Parameters: COMPONENTS : optional parameter listing the -# Qt packages (e.g. Core, Widgets REQUIRED: optional parameter propagated to find_package -# -# Usage: find_qt_package(COMPONENTS Core Widgets OpenGL Xml REQUIRED) which is equivalent to: -# find_package(Qt6 COMPONENTS Core Widgets OpenGL Xml REQUIRED) if Qt6 is available, or: -# find_package(Qt5 COMPONENTS Core Widgets OpenGL Xml REQUIRED) otherwise. -# -# Qt5 and Qt6 can be retrieved using versionless targets introduced in Qt5.15: -# https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#versionless-targets -macro(find_qt_package) - set(options REQUIRED) - set(oneValueArgs "") - set(multiValueArgs COMPONENTS) - - cmake_parse_arguments(MY_OPTIONS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT MY_OPTIONS_COMPONENTS) # User didn't enter any component - set(MY_OPTIONS_COMPONENTS "") - endif() - - find_package(Qt6 COMPONENTS ${MY_OPTIONS_COMPONENTS} QUIET) - if(NOT Qt6_FOUND) - if(${MY_OPTIONS_REQUIRED}) - find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS} REQUIRED) - else() - find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS}) - endif() - endif() -endmacro() - -# Find Qt5 or Qt6 dependency Parameters: COMPONENTS : optional parameter listing the -# Qt packages (e.g. Core, Widgets REQUIRED: optional parameter propagated to find_package -# -# Usage example: find_package(Qt5 COMPONENTS Core Widgets OpenGL Xml REQUIRED) -# -# Qt5 and Qt6 can be retrieved using versionless targets introduced in Qt5.15: -# https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#versionless-targets -macro(find_qt_dependency) - set(options REQUIRED) - set(oneValueArgs "") - set(multiValueArgs COMPONENTS) - - cmake_parse_arguments(MY_OPTIONS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT MY_OPTIONS_COMPONENTS) # User didn't enter any component - set(MY_OPTIONS_COMPONENTS "") - endif() - - find_package(Qt6 COMPONENTS ${MY_OPTIONS_COMPONENTS} QUIET) - if(NOT Qt6_FOUND) - if(${MY_OPTIONS_REQUIRED}) - find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS} REQUIRED) - else() - find_package(Qt5 5.15 COMPONENTS ${MY_OPTIONS_COMPONENTS}) - endif() - endif() -endmacro() From a34278955aa113154dbd15c1fc4dd18fefd47930 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 29 Sep 2022 15:21:01 +0200 Subject: [PATCH 013/239] [dataflow-cmake] add missing precompilation header --- src/Dataflow/Core/CMakeLists.txt | 4 ++-- src/Dataflow/QtGui/CMakeLists.txt | 4 ++-- src/Dataflow/QtGui/pch.hpp | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 src/Dataflow/QtGui/pch.hpp diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt index be728741155..b4d1063c7c1 100644 --- a/src/Dataflow/Core/CMakeLists.txt +++ b/src/Dataflow/Core/CMakeLists.txt @@ -14,8 +14,8 @@ add_library( populate_local_dependencies(NAME "stduuid_DIR") find_package(stduuid REQUIRED NO_DEFAULT_PATH) -# This one should be extracted directly from external project properties. -target_compile_definitions(${ra_dataflowcore_target} PUBLIC THIS_A_REMINDER) +# This one should be extracted directly from parent project properties. +target_compile_definitions(${ra_dataflowcore_target} PRIVATE RA_DATAFLOW_EXPORTS) target_compile_options(${ra_dataflowcore_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt index f73933d6ad7..189ba53d445 100644 --- a/src/Dataflow/QtGui/CMakeLists.txt +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -28,8 +28,8 @@ add_library( ${dataflow_qtgui_inlines} ) -# This one should be extracted directly from external project properties. -target_compile_definitions(${ra_dataflowqtgui_target} PUBLIC THIS_A_REMINDER) +# This one should be extracted directly from parent project properties. +target_compile_definitions(${ra_dataflowqtgui_target} PRIVATE RA_DATAFLOW_EXPORTS) target_compile_options(${ra_dataflowqtgui_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) diff --git a/src/Dataflow/QtGui/pch.hpp b/src/Dataflow/QtGui/pch.hpp new file mode 100644 index 00000000000..6f70f09beec --- /dev/null +++ b/src/Dataflow/QtGui/pch.hpp @@ -0,0 +1 @@ +#pragma once From 74f576852dbaa5d6e3301fda4cb8277a71fc8a42 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 29 Sep 2022 17:41:57 +0200 Subject: [PATCH 014/239] [dataflow-core] fix source nodes --- src/Dataflow/Core/EditableParameter.inl | 2 +- src/Dataflow/Core/Node.hpp | 2 +- src/Dataflow/Core/Node.inl | 2 +- src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp | 6 +++--- src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl | 2 +- src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp | 2 +- src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Dataflow/Core/EditableParameter.inl b/src/Dataflow/Core/EditableParameter.inl index afd8bac33b1..e14d7e9fdcf 100644 --- a/src/Dataflow/Core/EditableParameter.inl +++ b/src/Dataflow/Core/EditableParameter.inl @@ -10,7 +10,7 @@ inline EditableParameterBase::EditableParameterBase( std::string& name, size_t h template EditableParameter::EditableParameter( std::string name, T& data ) : - EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {}; + EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {} template void EditableParameter::addAdditionalData( T newData ) { diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 2704c05eb51..273b7b74967 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -165,7 +165,7 @@ class RA_DATAFLOW_API Node /// \param name The name of the editable parameter to remove. /// \return true if the editable parameter is found and removed. template - bool removeEditableParameter( const std::string name ); + bool removeEditableParameter( const std::string& name ); /// The uuid of the node (TODO, use https://github.com/mariusbancila/stduuid instead of a /// string) diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index bf537cf4e75..6d78a72f89b 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -156,7 +156,7 @@ bool Node::addEditableParameter( EditableParameter* editableParameter ) { } template -bool Node::removeEditableParameter( const std::string name ) { +bool Node::removeEditableParameter( const std::string& name ) { bool found = false; auto it = m_editableParameters.begin(); while ( it != m_editableParameters.end() ) { diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index f3a3ce8d1fc..c1e3f88522e 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -14,7 +14,7 @@ namespace Sources { /** * Specialization of SingleDataSourceNode for boolean value */ -class BooleanValueSource : public SingleDataSourceNode +class RA_DATAFLOW_API BooleanValueSource : public SingleDataSourceNode { public: explicit BooleanValueSource( const std::string& name ); @@ -30,7 +30,7 @@ class BooleanValueSource : public SingleDataSourceNode /** * Specialization of SingleDataSourceNode for scalar value */ -class ScalarValueSource : public SingleDataSourceNode +class RA_DATAFLOW_API ScalarValueSource : public SingleDataSourceNode { public: explicit ScalarValueSource( const std::string& name ); @@ -46,7 +46,7 @@ class ScalarValueSource : public SingleDataSourceNode /** * Specialization of SingleDataSourceNode for Core::Utils::Color value */ -class ColorSourceNode : public SingleDataSourceNode +class RA_DATAFLOW_API ColorSourceNode : public SingleDataSourceNode { public: explicit ColorSourceNode( const std::string& name ); diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl index abe8d5abffb..16505466498 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl @@ -62,7 +62,7 @@ inline ColorSourceNode::ColorSourceNode( const std::string& name ) : inline void ColorSourceNode::toJsonInternal( nlohmann::json& data ) const { auto c = Ra::Core::Utils::Color::linearRGBTosRGB( *getData() ); - std::array color { c.x(), c.y(), c.z() }; + std::array color { { c.x(), c.y(), c.z() } }; data["color"] = color; } diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index e490d28111b..de6df3589bf 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -54,7 +54,7 @@ class SingleDataSourceNode : public Node * \brief Remove the delivered data from being editable * @param name Name of the data given when calling setEditable */ - void removeEditable( const std::string name = "Data" ); + void removeEditable( const std::string& name = "Data" ); protected: void fromJsonInternal( const nlohmann::json& ) override; diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 84c15d2b4eb..c74ac48188b 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -60,12 +60,12 @@ T* SingleDataSourceNode::getData() const { template void SingleDataSourceNode::setEditable( const std::string name ) { - addEditableParameter( new EditableParameter( name, m_localData ) ); + Node::addEditableParameter( new EditableParameter( name, m_localData ) ); } template -void SingleDataSourceNode::removeEditable( const std::string name ) { - removeEditableParameter( name ); +void SingleDataSourceNode::removeEditable( const std::string& name ) { + Node::removeEditableParameter( name ); } template From 486d7bb983fe4ed868d0f7ceb6dc3bcc80b22329 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 29 Sep 2022 18:46:45 +0200 Subject: [PATCH 015/239] [dataflow-rendering] add rendergraph subcomponent --- src/Dataflow/CMakeLists.txt | 10 +- src/Dataflow/Config.cmake.in | 10 + src/Dataflow/Rendering/CMakeLists.txt | 41 ++ src/Dataflow/Rendering/Config.cmake.in | 38 ++ .../Rendering/Nodes/RenderingNode.hpp | 75 ++ .../Rendering/Renderer/DataflowRenderer.cpp | 640 ++++++++++++++++++ .../Rendering/Renderer/DataflowRenderer.hpp | 202 ++++++ .../Rendering/Renderer/RenderingGraph.cpp | 122 ++++ .../Rendering/Renderer/RenderingGraph.hpp | 82 +++ .../Rendering/Renderer/RenderingGraph.inl | 46 ++ src/Dataflow/Rendering/filelist.cmake | 11 + src/Dataflow/Rendering/pch.hpp | 1 + 12 files changed, 1272 insertions(+), 6 deletions(-) create mode 100644 src/Dataflow/Rendering/CMakeLists.txt create mode 100644 src/Dataflow/Rendering/Config.cmake.in create mode 100644 src/Dataflow/Rendering/Nodes/RenderingNode.hpp create mode 100644 src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp create mode 100644 src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp create mode 100644 src/Dataflow/Rendering/Renderer/RenderingGraph.cpp create mode 100644 src/Dataflow/Rendering/Renderer/RenderingGraph.hpp create mode 100644 src/Dataflow/Rendering/Renderer/RenderingGraph.inl create mode 100644 src/Dataflow/Rendering/filelist.cmake create mode 100644 src/Dataflow/Rendering/pch.hpp diff --git a/src/Dataflow/CMakeLists.txt b/src/Dataflow/CMakeLists.txt index 6c7026c3014..2360159307a 100644 --- a/src/Dataflow/CMakeLists.txt +++ b/src/Dataflow/CMakeLists.txt @@ -12,12 +12,10 @@ if(RADIUM_GENERATE_LIB_CORE) target_link_libraries(${ra_dataflow_target} INTERFACE DataflowCore) endif() -if(OFF) - if(RADIUM_GENERATE_LIB_ENGINE) - add_subdirectory(Rendering) - add_dependencies(${ra_dataflow_target} DataflowRendering) - target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) - endif() +if(RADIUM_GENERATE_LIB_ENGINE) + add_subdirectory(Rendering) + add_dependencies(${ra_dataflow_target} DataflowRendering) + target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) endif() if(RADIUM_GENERATE_LIB_GUI) diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in index 1abc48cd08c..008198ee679 100644 --- a/src/Dataflow/Config.cmake.in +++ b/src/Dataflow/Config.cmake.in @@ -25,6 +25,16 @@ if (Dataflow_FOUND AND NOT TARGET Dataflow) endif() endif() + if(NOT DataflowRendering_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake") + set(DataflowRendering_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowRendering not found") + set(Configure_Dataflow OFF) + endif() + endif() endif() # configure Dataflow component diff --git a/src/Dataflow/Rendering/CMakeLists.txt b/src/Dataflow/Rendering/CMakeLists.txt new file mode 100644 index 00000000000..ee2a6f5d739 --- /dev/null +++ b/src/Dataflow/Rendering/CMakeLists.txt @@ -0,0 +1,41 @@ +set(ra_dataflowrendering_target DataflowRendering) +list(APPEND CMAKE_MESSAGE_INDENT "[${ra_dataflowrendering_target}] ") + +project(${ra_dataflowrendering_target} LANGUAGES CXX VERSION ${Radium_VERSION}) + +include(filelist.cmake) + +# configure library +add_library( + ${ra_dataflowrendering_target} SHARED + ${dataflow_rendering_sources} ${dataflow_rendering_headers} ${dataflow_rendering_inlines} +) + +# This one should be extracted directly from parent project properties. +target_compile_definitions(${ra_dataflowrendering_target} PRIVATE RA_DATAFLOW_EXPORTS) + +target_compile_options(${ra_dataflowrendering_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) + +add_dependencies(${ra_dataflowrendering_target} DataflowCore Engine) +target_link_libraries(${ra_dataflowrendering_target} PUBLIC DataflowCore Engine) + +message(STATUS "Configuring library ${ra_dataflowrendering_target} with standard settings") +configure_radium_target(${ra_dataflowrendering_target}) +# configure the library only. The package is a sub-package and should be configured independently +configure_radium_library( + TARGET ${ra_dataflowrendering_target} COMPONENT TARGET_DIR "Dataflow/Rendering" + FILES "${dataflow_rendering_headers};${dataflow_rendering_inlines}" +) +# Generate cmake package +configure_radium_package( + NAME ${ra_dataflowrendering_target} PACKAGE_CONFIG ${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in + PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowrendering_target}" NAME_PREFIX "Radium" +) + +set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowrendering_target} PARENT_SCOPE) + +if(RADIUM_ENABLE_PCH) + target_precompile_headers(${ra_dataflowrendering_target} PRIVATE pch.hpp) +endif() + +list(REMOVE_AT CMAKE_MESSAGE_INDENT -1) diff --git a/src/Dataflow/Rendering/Config.cmake.in b/src/Dataflow/Rendering/Config.cmake.in new file mode 100644 index 00000000000..23bd179f1ab --- /dev/null +++ b/src/Dataflow/Rendering/Config.cmake.in @@ -0,0 +1,38 @@ +# -------------- Configuration of the Radium DataflowCore targets and definitions ----------------------- +# Setup Engine and check for dependencies + +if (DataflowRendering_FOUND AND NOT TARGET DataflowRendering) + set(Configure_DataflowRendering ON) + # verify dependencies + if(NOT DataflowCore_FOUND) + # if in source dir + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake") + set(DataflowCore_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/RadiumDataflowCoreConfig.cmake) + else() + # if in install dir + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake") + set(DataflowCore_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowQtGui: dependency DataflowCore not found") + set(Configure_DataflowRendering OFF) + endif() + endif() + endif() + if(NOT Engine_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake") + set(Gui_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowRendering: dependency Engine not found") + set(Configure_DataflowRendering OFF) + endif() + endif() +endif() + +if(Configure_DataflowRendering) + include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Rendering/DataflowRenderingTargets.cmake") +endif() diff --git a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp new file mode 100644 index 00000000000..9c893c8cfc9 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp @@ -0,0 +1,75 @@ +#pragma once +#include + +#include + +#include +#include +#include +#include +#include + +namespace Ra::Engine::Data { +class ShaderProgramManager; +} + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +/** + * Defining some useful aliases for data type + * + */ + +using RenderObjectType = std::shared_ptr; +using LightType = const Ra::Engine::Scene::Light*; +using CameraType = Ra::Engine::Data::ViewingParameters; +using ColorType = Ra::Core::Utils::Color; +using TextureType = Ra::Engine::Data::Texture; + +/** + * Base class for Rendering nodes. + * Rendering nodes are nodes with some interface needed to render the scene. + */ +class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, + public Ra::Core::Utils::IndexedObject +{ + public: + using Dataflow::Core::Node::Node; + + /// The resize(uint32_t width, uint32_t height) function is called when the application gets + /// resized. Its goal is to resize the potential internal textures if needed. + /// @param width The new width of the surface. + /// @param height The new height of the surface. + virtual void resize( uint32_t width, uint32_t height ) = 0; + + /// Build a render technic per material. + /// @param ro The render object to get the material from + /// @param rt The render technic to build + virtual void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject*, + Ra::Engine::Rendering::RenderTechnique& ) const {}; + + /// Indicate if the nod needs to setup a rendertechnique on RenderObjects + virtual bool hasRenderTechnique() { return false; } + + /// Sets the shader program manager + void setShaderProgramManager( Ra::Engine::Data::ShaderProgramManager* shaderMngr ) { + m_shaderMngr = shaderMngr; + } + + static const std::string getTypename() { return "RenderingNode"; } + + protected: + void toJsonInternal( nlohmann::json& ) const override {} + void fromJsonInternal( const nlohmann::json& ) override {} + + /// The renderer's shader program manager + Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp new file mode 100644 index 00000000000..39e94ae2375 --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp @@ -0,0 +1,640 @@ +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +using namespace Ra::Engine; +using namespace Ra::Engine::Scene; +using namespace Ra::Engine::Data; +using namespace Ra::Engine::Rendering; +using namespace gl; + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { +using namespace Ra::Dataflow::Core; + +DataflowRenderer::RenderGraphController::RenderGraphController() : + Ra::Core::Resources::ObservableVoid() {} + +void DataflowRenderer::RenderGraphController::configure( DataflowRenderer* renderer, + int w, + int h ) { + m_shaderMngr = renderer->m_shaderProgramManager; + m_width = w; + m_height = h; + if ( !m_graphToLoad.empty() ) { + loadGraph( m_graphToLoad ); + m_graphToLoad = ""; + } +} + +void DataflowRenderer::RenderGraphController::resize( int w, int h ) { + m_width = w; + m_height = h; + if ( m_renderGraph ) { m_renderGraph->resize( m_width, m_height ); } +} + +void DataflowRenderer::RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { + + if ( m_renderGraph && m_renderGraph->m_recompile ) { + // compile the model + m_renderGraph->init(); + // notify the view the model changes + notify(); + // notify the model the view may have changed + m_renderGraph->resize( m_width, m_height ); + } +} + +void DataflowRenderer::RenderGraphController::loadGraph( const std::string filename ) { + m_renderGraph.release(); + auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); + m_renderGraph = std::make_unique( graphName ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); + m_renderGraph->loadFromJson( filename ); + notify(); +} + +void DataflowRenderer::RenderGraphController::defferedLoadGraph( const std::string filename ) { + m_graphToLoad = filename; +} + +void DataflowRenderer::RenderGraphController::saveGraph( const std::string filename ) { + if ( m_renderGraph ) { + auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); + m_renderGraph->saveToJson( filename ); + m_renderGraph->setInstanceName( graphName ); + } +} + +void DataflowRenderer::RenderGraphController::resetGraph() { + m_renderGraph.release(); + m_renderGraph = std::make_unique( "untitled" ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); +} + +// ------------------------------------------------------------------------------------------ +// Interface with Radium renderer ... +// ------------------------------------------------------------------------------------------ + +DataflowRenderer::DataflowRenderer( RenderGraphController& controller ) : + Renderer(), m_controller { controller }, m_name { m_controller.getRendererName() } { + m_controller.attachMember( this, &DataflowRenderer::graphChanged ); +} + +DataflowRenderer::~DataflowRenderer() = default; + +void DataflowRenderer::graphChanged() { + m_graphChanged = true; +} + +bool DataflowRenderer::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { + if ( m_controller.m_renderGraph ) { + if ( m_controller.m_renderGraph->m_recompile ) { + m_controller.m_renderGraph->init(); + m_controller.resize( m_width, m_height ); + } + m_controller.m_renderGraph->buildRenderTechnique( ro ); + return true; + } + else { + return false; + } +} + +void DataflowRenderer::initResources() { + // uses several resources from the Radium engine + auto resourcesRootDir { RadiumEngine::getInstance()->getResourcesDir() + "Shaders/" }; + + m_shaderProgramManager->addShaderProgram( + { { "Hdr2Ldr" }, + resourcesRootDir + "2DShaders/Basic2D.vert.glsl", + resourcesRootDir + "2DShaders/Hdr2Ldr.frag.glsl" } ); + + m_postprocessFbo = std::make_unique(); +} + +void DataflowRenderer::initializeInternal() { + auto cmngr = dynamic_cast( + Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultCameraManager" ) ); + if ( cmngr == nullptr ) { + cmngr = new DefaultCameraManager(); + Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultCameraManager", cmngr ); + } + auto lmngr = dynamic_cast( + Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultLightManager" ) ); + if ( lmngr == nullptr ) { + lmngr = new DefaultLightManager(); + Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultLightManager", lmngr ); + } + m_lightmanagers.push_back( lmngr ); + + // Initialize renderer resources + initResources(); + m_controller.configure( this, m_width, m_height ); + + // TODO update shared textures as the rengerGraphe modify its output : observe the renderGraph + for ( const auto& t : m_sharedTextures ) { + m_secondaryTextures.insert( { t.first, t.second.get() } ); + } +} + +void DataflowRenderer::resizeInternal() { + // Resize the graph resources + m_controller.resize( m_width, m_height ); + + // Resize the internal resources + m_postprocessFbo->bind(); + m_postprocessFbo->attachTexture( GL_COLOR_ATTACHMENT0, m_fancyTexture->texture() ); +#ifdef PASSES_LOG + if ( m_postprocessFbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE ) { + LOG( Ra::Core::Utils::logERROR ) << "FBO Error (NodeBasedRenderer::m_postprocessFbo) : " + << m_postprocessFbo->statusString(); + } +#endif + // finished with fbo, unbind to bind default + globjects::Framebuffer::unbind(); +} + +void DataflowRenderer::updateStepInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { + // std::cout << "DataflowRenderer::updateStepInternal() : calling update on graph controller." + // << std::endl; + m_controller.update( renderData ); + if ( m_controller.m_renderGraph ) { + // Update renderTechnique if needed + if ( m_graphChanged ) { + buildAllRenderTechniques(); + m_graphChanged = false; + } + // TODO, improve light and camera management to prevent multiple alloc/copy ... + auto lights = getLights(); + lights->clear(); + lights->reserve( getLightManager()->count() ); + for ( size_t i = 0; i < getLightManager()->count(); i++ ) { + lights->push_back( getLightManager()->getLight( i ) ); + } + // The graph will take ownership of the light pointer ... + m_controller.m_renderGraph->setDataSources( allRenderObjects(), lights ); + } +} + +void DataflowRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { + // std::cout << "DataflowRenderer::renderInternal() : executing the graph." << std::endl; + // TODO, replace this kind of test by a call to a controller method + if ( m_controller.m_renderGraph && m_controller.m_renderGraph->m_ready ) { + // Cameras + // set input data + m_cameras.clear(); + m_cameras.push_back( renderData ); + m_controller.m_renderGraph->setCameras( &m_cameras ); + // execute the graph + m_controller.m_renderGraph->execute(); + // TODO : get all the resulting images (not only the "Beauty" channel + const auto& images = m_controller.m_renderGraph->getImagesOutput(); + // The first image is the "beauty" channel, set the color texture to this + m_colorTexture = images[0]; + } + else { + m_colorTexture = nullptr; + } +} + +void DataflowRenderer::postProcessInternal( const Ra::Engine::Data::ViewingParameters& ) { + if ( m_colorTexture ) { + m_postprocessFbo->bind(); + + // GL_ASSERT( glDrawBuffers( 1, buffers ) ); + GL_ASSERT( glDrawBuffer( GL_COLOR_ATTACHMENT0 ) ); + GL_ASSERT( glDisable( GL_DEPTH_TEST ) ); + GL_ASSERT( glDepthMask( GL_FALSE ) ); + + auto shader = m_postProcessEnabled + ? m_shaderProgramManager->getShaderProgram( "Hdr2Ldr" ) + : m_shaderProgramManager->getShaderProgram( "DrawScreen" ); + shader->bind(); + shader->setUniform( "screenTexture", m_colorTexture, 0 ); + m_quadMesh->render( shader ); + + GL_ASSERT( glDepthMask( GL_TRUE ) ); + GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); + + m_postprocessFbo->unbind(); + } +} + +void DataflowRenderer::debugInternal( const Ra::Engine::Data::ViewingParameters& ) {} + +void DataflowRenderer::uiInternal( const Ra::Engine::Data::ViewingParameters& ) {} + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra + +#if 0 +# include + +# include + +# ifdef PASSES_LOG +# include +using namespace Ra::Core::Utils; // log +# endif + +# include +# include +# include + +# include +# include +# include + +# include + +# include +# include + +using namespace Ra::Engine; +using namespace Ra::Engine::Scene; +using namespace Ra::Engine::Data; +using namespace Ra::Engine::Rendering; +namespace RadiumNBR { +using namespace gl; + +int NodeBasedRendererMagic = 0xFF0F00F0; + +static const GLenum buffers[] = { GL_COLOR_ATTACHMENT0 }; + +static NodeBasedRenderer::RenderControlFunctor noOpController; + +NodeBasedRenderer::NodeBasedRenderer() : Renderer(), m_controller{ noOpController } {} + +NodeBasedRenderer::NodeBasedRenderer( NodeBasedRenderer::RenderControlFunctor& controller ) : + Renderer(), m_controller{ controller }, m_name{ m_controller.getRendererName() } { + setDisplayNode( m_originalRenderGraph.getDisplayNode() ); +} + +NodeBasedRenderer::~NodeBasedRenderer() { + m_displaySinkNode->detach( m_displayObserverId ); + m_originalRenderGraph.destroy(); +} + +bool NodeBasedRenderer::buildRenderTechnique( RenderObject* ro ) const { + auto rt = Ra::Core::make_shared(); + for ( size_t level = 0; level < m_originalRenderGraph.getNodesByLevel()->size(); level++ ) + { + for ( size_t node = 0; node < m_originalRenderGraph.getNodesByLevel()->at( level ).size(); + node++ ) + { + m_originalRenderGraph.getNodesByLevel()->at( level ).at( node )->buildRenderTechnique( + ro, *rt ); + } + } + rt->updateGL(); + ro->setRenderTechnique( rt ); + return true; +} + +void NodeBasedRenderer::initResources() { + // uses several resources from the Radium engine + auto resourcesRootDir{ RadiumEngine::getInstance()->getResourcesDir() + "Shaders/" }; + + m_shaderProgramManager->addShaderProgram( + { { "Hdr2Ldr" }, + resourcesRootDir + "2DShaders/Basic2D.vert.glsl", + resourcesRootDir + "2DShaders/Hdr2Ldr.frag.glsl" } ); + + m_postprocessFbo = std::make_unique(); +} + +void NodeBasedRenderer::loadFromJson( const std::string& jsonFilePath ) { + m_jsonFilePath = jsonFilePath; + + if ( m_jsonFilePath != "" ) + { + m_originalRenderGraph.loadFromJson( jsonFilePath ); + m_originalRenderGraph.init(); + } + else + { std::cerr << "No Json was given to load a render graph." << std::endl; } +} + +void NodeBasedRenderer::compileRenderGraph() { + m_originalRenderGraph.init(); + m_originalRenderGraph.resize( m_width, m_height ); + buildAllRenderTechniques(); + m_displayedTexture = m_fancyTexture.get(); +} + +void NodeBasedRenderer::reloadRenderGraphFromJson() { + if ( m_jsonFilePath != "" ) + { + std::cout << "Reloading Render Graph from Json..." << std::endl; + // Destroy the resources used by the nodes + m_originalRenderGraph.destroy(); + + // Clear the nodes + m_originalRenderGraph.clearNodes(); + + // Reload + m_originalRenderGraph.loadFromJson( m_jsonFilePath ); + m_originalRenderGraph.init(); + m_originalRenderGraph.resize( m_width, m_height ); + buildAllRenderTechniques(); + + // Reset displayed texture + m_displayedTexture = m_fancyTexture.get(); + + std::cout << "Render Graph Reloaded!" << std::endl; + } + else + { std::cerr << "No Json was given to reload a render graph." << std::endl; } +} + +void NodeBasedRenderer::initializeInternal() { + + // TODO : this must be done only once, see register system ... + auto cmngr = dynamic_cast( + Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultCameraManager" ) ); + if ( cmngr == nullptr ) + { + cmngr = new DefaultCameraManager(); + Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultCameraManager", cmngr ); + } + auto lmngr = dynamic_cast( + Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultLightManager" ) ); + if ( lmngr == nullptr ) + { + lmngr = new DefaultLightManager(); + Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultLightManager", lmngr ); + } + m_lightmanagers.push_back( lmngr ); + + // Initialize renderer resources + initResources(); + m_controller.configure( this, m_width, m_height ); + + for ( const auto& t : m_sharedTextures ) + { m_secondaryTextures.insert( { t.first, t.second.get() } ); } + + // Todo cache this in an attribute ? + auto resourcesCheck = Ra::Core::Resources::getResourcesPath( + reinterpret_cast( &RadiumNBR::NodeBasedRendererMagic ), { "Resources/RadiumNBR" } ); + if ( !resourcesCheck ) + { + LOG( Ra::Core::Utils::logERROR ) << "Unable to find resources for NodeBasedRenderer!"; + return; + } + auto resourcesPath{ *resourcesCheck }; +} + +void NodeBasedRenderer::resizeInternal() { + // Resize each internal resources + m_controller.resize( m_width, m_height ); + m_originalRenderGraph.resize( m_width, m_height ); + + m_postprocessFbo->bind(); + m_postprocessFbo->attachTexture( GL_COLOR_ATTACHMENT0, m_fancyTexture->texture() ); +# ifdef PASSES_LOG + if ( m_postprocessFbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE ) + { + LOG( Ra::Core::Utils::logERROR ) << "FBO Error (NodeBasedRenderer::m_postprocessFbo) : " + << m_postprocessFbo->checkStatus(); + } +# endif + // finished with fbo, unbind to bind default + globjects::Framebuffer::unbind(); +} + +void NodeBasedRenderer::renderInternal( const ViewingParameters& renderData ) { + // Run the render graph + m_originalRenderGraph.execute(); +} + +// Draw debug stuff, do not overwrite depth map but do depth testing +void NodeBasedRenderer::debugInternal( const ViewingParameters& renderData ) { +# if 0 + if ( m_drawDebug ) + { + const ShaderProgram* shader; + + m_postprocessFbo->bind(); + GL_ASSERT( glDisable( GL_BLEND ) ); + GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); + GL_ASSERT( glDepthMask( GL_FALSE ) ); + GL_ASSERT( glDepthFunc( GL_LESS ) ); + + glDrawBuffers( 1, buffers ); + + for ( const auto& ro : m_debugRenderObjects ) + { + ro->render( RenderParameters{}, renderData ); + } + + DebugRender::getInstance()->render( renderData.viewMatrix, renderData.projMatrix ); + + m_postprocessFbo->unbind(); + + m_uiXrayFbo->bind(); + // Draw X rayed objects always on top of normal objects + GL_ASSERT( glDepthMask( GL_TRUE ) ); + GL_ASSERT( glClear( GL_DEPTH_BUFFER_BIT ) ); + for ( const auto& ro : m_xrayRenderObjects ) + { + if ( ro->isVisible() ) + { + shader = ro->getRenderTechnique()->getShader(); + + // bind data + shader->bind(); + // lighting for Xray : fixed + shader->setUniform( "light.color", Ra::Core::Utils::Color::Grey( 5.0 ) ); + shader->setUniform( "light.type", Light::LightType::DIRECTIONAL ); + shader->setUniform( "light.directional.direction", Ra::Core::Vector3( 0, -1, 0 ) ); + + Ra::Core::Matrix4 M = ro->getTransformAsMatrix(); + shader->setUniform( "transform.proj", renderData.projMatrix ); + shader->setUniform( "transform.view", renderData.viewMatrix ); + shader->setUniform( "transform.model", M ); + + ro->getRenderTechnique()->getMaterial()->bind( shader ); + + // render + ro->getMesh()->render(); + } + } + m_uiXrayFbo->unbind(); + } +# endif +} + +// Draw UI stuff, always drawn on top of everything else + clear ZMask +// TODO: NODEGRAPH! Unused ? +void NodeBasedRenderer::uiInternal( const ViewingParameters& renderData ) { +# if 0 + const ShaderProgram* shader; + + m_uiXrayFbo->bind(); + glDrawBuffers( 1, buffers ); + // Enable z-test + GL_ASSERT( glDepthMask( GL_TRUE ) ); + GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); + GL_ASSERT( glDepthFunc( GL_LESS ) ); + GL_ASSERT( glClear( GL_DEPTH_BUFFER_BIT ) ); + for ( const auto& ro : m_uiRenderObjects ) + { + if ( ro->isVisible() ) + { + shader = ro->getRenderTechnique()->getShader(); + + // bind data + shader->bind(); + + Ra::Core::Matrix4 M = ro->getTransformAsMatrix(); + Ra::Core::Matrix4 MV = renderData.viewMatrix * M; + Ra::Core::Vector3 V = MV.block<3, 1>( 0, 3 ); + Scalar d = V.norm(); + + Ra::Core::Matrix4 S = Ra::Core::Matrix4::Identity(); + S.coeffRef( 0, 0 ) = S.coeffRef( 1, 1 ) = S.coeffRef( 2, 2 ) = d; + + M = M * S; + + shader->setUniform( "transform.proj", renderData.projMatrix ); + shader->setUniform( "transform.view", renderData.viewMatrix ); + shader->setUniform( "transform.model", M ); + + ro->getRenderTechnique()->getMaterial()->bind( shader ); + + // render + ro->getMesh()->render(); + } + } + m_uiXrayFbo->unbind(); +# endif +} + +void NodeBasedRenderer::postProcessInternal( const ViewingParameters& /* renderData */ ) { + if ( m_colorTexture ) + { + m_postprocessFbo->bind(); + + // GL_ASSERT( glDrawBuffers( 1, buffers ) ); + GL_ASSERT( glDrawBuffer( GL_COLOR_ATTACHMENT0 ) ); + GL_ASSERT( glDisable( GL_DEPTH_TEST ) ); + GL_ASSERT( glDepthMask( GL_FALSE ) ); + + auto shader = m_postProcessEnabled + ? m_shaderProgramManager->getShaderProgram( "Hdr2Ldr" ) + : m_shaderProgramManager->getShaderProgram( "DrawScreen" ); + shader->bind(); + shader->setUniform( "screenTexture", m_colorTexture, 0 ); + m_quadMesh->render( shader ); + + GL_ASSERT( glDepthMask( GL_TRUE ) ); + GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); + + m_postprocessFbo->unbind(); + } +} + +void NodeBasedRenderer::updateStepInternal( const ViewingParameters& renderData ) { + if ( m_reloadJson ) + { + reloadRenderGraphFromJson(); + if ( m_resetPath ) + { + m_jsonFilePath = m_jsonFilePath.substr( 0, m_jsonFilePath.rfind( '/' ) + 1 ); + m_resetPath = false; + } + m_reloadJson = false; + } + + if ( m_originalRenderGraph.m_recompile ) + { + std::cerr << "NodeBasedRenderer::updateStepInternal :Recompiling Graph\n"; + compileRenderGraph(); + m_originalRenderGraph.m_recompile = false; + } + + // Render objects + m_originalRenderGraph.getDataNode()->setElements( *allRenderObjects() ); + // Lights + std::vector lights; + for ( size_t i = 0; i < getLightManager()->count(); i++ ) + { lights.push_back( getLightManager()->getLight( i ) ); } + m_originalRenderGraph.getDataNode()->setElements( lights ); + // Cameras + std::vector cameras; + cameras.push_back( renderData ); + m_originalRenderGraph.getDataNode()->setElements( cameras ); + // Update the render graph + + m_originalRenderGraph.update(); +} + +void NodeBasedRenderer::setDisplayNode( DisplaySinkNode* displayNode ) { + m_displaySinkNode = displayNode; + m_displayObserverId = + m_displaySinkNode->attachMember( this, &NodeBasedRenderer::observeDisplaySink ); +} + +void NodeBasedRenderer::observeDisplaySink( const std::vector& graphOutput ) { + // TODO : find a way to make the renderer observable to manage ouput texture in the applicaiton + // gui if needed + std::cout << "NodeBasedRenderer::observeDisplaySink - connected textures (" + << graphOutput.size() << ") : \n"; + /* + for ( const auto t : graphOutput ) + { + if ( t ) { std::cout << "\t" << t->getName() << "\n"; } + else + { std::cout << "\tName not available yet. Must run the graph a first time\n"; } + } + */ + // Add display nodes' linked textures to secondary textures + m_secondaryTextures.clear(); + for ( const auto& t : m_sharedTextures ) + { m_secondaryTextures.insert( { t.first, t.second.get() } ); } + + bool colorTextureSet = false; + if ( m_displaySinkNode ) + { + auto textures = m_displaySinkNode->getTextures(); + for ( const auto t : textures ) + { +# ifdef GRAPH_CALL_TRACE + std::cout << t->getName() << std::endl; +# endif + if ( t ) + { + if ( !colorTextureSet ) + { + m_colorTexture = t; + colorTextureSet = true; + } + else + { m_secondaryTextures.insert( { t->getName(), t } ); } + } + } + } + + if ( !colorTextureSet ) + { + m_colorTexture = m_fancyTexture.get(); + colorTextureSet = true; + } +} + +} // namespace RadiumNBR +#endif diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp new file mode 100644 index 00000000000..853a0440ec5 --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp @@ -0,0 +1,202 @@ +#pragma once +#include + +/** + * Right now a renderer own a graph with some specific nodes available that gives acces to the + * renderobjects, the camera and the lights. + * These nodes are put in a specific factory SceneAccessors which contains nodes to access the + * renderobject, the camera and the light of the scene. + * + */ + +#include +#include + +#include + +namespace globjects { +class Framebuffer; +} + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { + +/// Todo, put this somewhere else. This is needed to locate resources by client applications +/// Todo (bis), remove this requirement +extern int DataflowRendererMagic; + +/** Dataflow renderer for the Radium Engine + * This Renderer is fully configurable, either dynamically or programmatically. + * It implements the Ra::Engine::Rendering/Renderer interface. + * + * A NodeBasedRenderer is configured by using the RenderControlFunctor given at construction. + * A RenderControlFunctor offers the following services : + * - configure() : add to the renderer as many RadiumNBR::RenderPass as needed. This method is + * called once when initializing the renderer. This method could also initialize internal + * resources into the controller that could be used to control the rendering. + * - resize() : called each time the renderer output is resized. This will allow modify controller + * resources that depends on the size of the output (e.g. internal textures ...) + * - update() : Called once before each frame to update the internal state of the renderer. + * + * A NodeBasedRenderer defines two textures that might be shared between passes : + * - a depth buffer attachable texture, stored with the key "Depth (RadiumNBR)" into the shared + * textures collection + * - a Linear space RGBA color texture, stored with the key "Linear RGB (RadiumNBR)" into the + * shared textures collection + * + * If requested on the base Ra::Engine::Rendering::Renderer, a NodeBasedRenderer apply a + * post-process step on the "Linear RGB (RadiumNBR)" that convert colors from linearRGB to sRGB + * color space before displaying the image. + * + * + * @see rendering.md for description of the renderer + */ +class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer +{ + + public: + /** + * RenderGraph controller + */ + struct RA_DATAFLOW_API RenderGraphController : Ra::Core::Resources::ObservableVoid { + + RenderGraphController(); + virtual ~RenderGraphController() = default; + RenderGraphController( const RenderGraphController& ) = delete; + RenderGraphController( const RenderGraphController&& ) = delete; + RenderGraphController& operator=( RenderGraphController&& ) = delete; + RenderGraphController& operator=( const RenderGraphController& ) = delete; + + /// Configuration function. + /// Called once at the configuration of the renderer + virtual void configure( DataflowRenderer* renderer, int w, int h ); + + /// Resize function + /// Called each time the renderer is resized + virtual void resize( int w, int h ); + + /// Update function + /// Called once before each frame to update the internal state of the renderer + virtual void update( const Ra::Engine::Data::ViewingParameters& renderData ); + + [[nodiscard]] virtual std::string getRendererName() const { return "Dataflow Renderer"; } + + void loadGraph( const std::string filename ); + void saveGraph( const std::string filename ); + void resetGraph(); + /// Call this to set a graph to load before OpenGL is OK + void defferedLoadGraph( const std::string filename ); + + /// The controlled graph. + /// The controller own the graph and manage loading/saving of the renderer + std::unique_ptr m_renderGraph { nullptr }; + + private: + int m_width { -1 }; + int m_height { -1 }; + Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; + std::string m_graphToLoad; + }; + + /// Construct a renderer configured and managed through the controller + explicit DataflowRenderer( RenderGraphController& controller ); + + /// The destructor is used to destroy the render graph + ~DataflowRenderer() override; + + [[nodiscard]] std::string getRendererName() const override { return m_name; } + + bool buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const override; + + /// Access the default light manager + Ra::Engine::Scene::LightManager* getLightManager() { return m_lightmanagers[0]; } + + /// Access the controller + RenderGraphController& getController() { return m_controller; } + + /// Sets the display sink node + // void setDisplayNode( DisplaySinkNode* displayNode ); + + /// Loads the render graph from a Json file. + // void loadFromJson( const std::string& jsonFilePath ); + + /// Gets the Json file path + // const std::string& getJsonFilePath() { return m_jsonFilePath; } + + /// Sets the Json file path + // void setJsonFilePath( const std::string& jsonFilePath ) { m_jsonFilePath = jsonFilePath; } + + /// Reloads the render graph + // void compileRenderGraph(); + + /// Reloads the render graph according to the current Json file + // void reloadRenderGraphFromJson(); + + /// Raises the flag to reload the Json + /* + void signalReloadJson( bool resetPath = false ) { + m_reloadJson = true; + m_resetPath = resetPath; + } + */ + + protected: + void initializeInternal() override; + void resizeInternal() override; + void updateStepInternal( const Ra::Engine::Data::ViewingParameters& renderData ) override; + void renderInternal( const Ra::Engine::Data::ViewingParameters& renderData ) override; + void postProcessInternal( const Ra::Engine::Data::ViewingParameters& renderData ) override; + void debugInternal( const Ra::Engine::Data::ViewingParameters& renderData ) override; + void uiInternal( const Ra::Engine::Data::ViewingParameters& renderData ) override; + + /** Initialize internal resources for the renderer. + * The base function creates the depth and final color texture to be shared by the rendering + * passes that need them. + */ + virtual void initResources(); + + public: + inline std::map>& sharedTextures() { + return m_sharedTextures; + } + + inline globjects::Framebuffer* postprocessFbo() { return m_postprocessFbo.get(); } + + inline std::vector* allRenderObjects() { return &m_fancyRenderObjects; } + + inline std::vector* getLights() { return &m_lights; } + + private: + /// Controler observer method + void graphChanged(); + + bool m_graphChanged { false }; + + /// textures own by the Renderer but shared across passes + std::map> m_sharedTextures; + + /// internal FBO used for post-processing + std::unique_ptr m_postprocessFbo; + + /// The configurator functor to use + RenderGraphController& m_controller; + + /// The name of the renderer + std::string m_name { "RenderGraph renderer" }; + + /// Texture to be read for postprocess according to the display node + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + + /// Vector of lights ... + std::vector m_lights; + + /// Vector of camera + std::vector m_cameras; +}; + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp new file mode 100644 index 00000000000..a01b7d898ac --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp @@ -0,0 +1,122 @@ +#include + +#include +#include + +using namespace Ra::Engine::Rendering; + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { +using namespace Ra::Dataflow::Rendering::Nodes; +using namespace Ra::Dataflow::Core; + +void RenderingGraph::init() { + DataflowGraph::init(); +} + +bool RenderingGraph::addNode( Node* newNode ) { + auto added = DataflowGraph::addNode( newNode ); + if ( added ) { + // Todo : is there something to do ? + } + return added; +} + +bool RenderingGraph::removeNode( Node* node ) { + auto removed = DataflowGraph::removeNode( node ); + if ( removed ) { + // Todo : is there something to do ? + } + return removed; +} + +bool RenderingGraph::postCompilationOperation() { +#if 0 + m_renderingNodes.clear(); + m_rtIndexedNodes.clear(); + m_dataProviders.clear(); + if ( m_displaySinkNode && m_displayObserverId != -1 ) { + m_displaySinkNode->detach( m_displayObserverId ); + m_displayObserverId = -1; + m_displaySinkNode = nullptr; + } + const auto& compiledNodes = getNodesByLevel(); + int idx = 1; // The renderTechnique id = 0 is reserved for ui/debug objects + for ( const auto& lvl : *compiledNodes ) { + for ( auto n : lvl ) { + auto renderNode = dynamic_cast( n ); + if ( renderNode != nullptr ) { + m_renderingNodes.push_back( renderNode ); + if ( renderNode->hasRenderTechnique() ) { + renderNode->setIndex( idx++ ); + m_rtIndexedNodes.push_back( renderNode ); + } + renderNode->setShaderProgramManager( m_shaderMngr ); + } + else { + auto sceneNode = dynamic_cast( n ); + if ( sceneNode ) { m_dataProviders.push_back( sceneNode ); } + else { + // Manage all sinks ... + auto displaySink = dynamic_cast( n ); + if ( displaySink ) { + m_displaySinkNode = displaySink; + // observe the displaySink + m_displayObserverId = + displaySink->attachMember( this, &RenderingGraph::observeSinks ); + } + } + } + } + } + /* + std::cout << "RenderingGraph::postCompilationOperation : got " << + m_renderingNodes.size() << " compiled rendering nodes with " << + m_rtIndexedNodes.size() << " render-passes, " << + m_dataProviders.size() << " scene nodes. \n"; + */ +#endif + return true; +} + +#if 0 +void RenderingGraph::observeSinks( const std::vector& graphOutput ) { + m_outputTextures = graphOutput; + /* + std::cout << "Available output textures are :" << std::endl; + int i = 0; + for (auto t : m_outputTextures ) { + std::cout << "\t tex " << i++ << " : "; + if (t) { + std::cout << t->getName() << std::endl; + } else { + std::cout << "nullptr" << std::endl; + } + } + */ +} +#endif +const std::vector& RenderingGraph::getImagesOutput() const { + return m_outputTextures; +} + +void RenderingGraph::clearNodes() { + DataflowGraph::clearNodes(); + m_renderingNodes.clear(); + m_rtIndexedNodes.clear(); +} + +void RenderingGraph::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { + auto rt = std::make_shared(); + for ( const auto& rn : m_rtIndexedNodes ) { + rn->buildRenderTechnique( ro, *rt ); + } + rt->updateGL(); + ro->setRenderTechnique( rt ); +} +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp new file mode 100644 index 00000000000..b87862d1298 --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -0,0 +1,82 @@ +#pragma once +#include + +#include + +#include + +#if 0 +# include +# include +#endif + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { +using namespace Ra::Dataflow::Rendering::Nodes; +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API RenderingGraph : public DataflowGraph +{ + public: + RenderingGraph( const std::string& name ); + ~RenderingGraph() override = default; + + void init() override; + bool addNode( Node* newNode ) override; + bool removeNode( Node* node ) override; + void clearNodes() override; + + /// Sets the shader program manager + void setShaderProgramManager( Ra::Engine::Data::ShaderProgramManager* shaderMngr ) { + m_shaderMngr = shaderMngr; + } + + /// Resize all the rendering output + void resize( uint32_t width, uint32_t height ); + + /// Set the scene accessors on the graph + void setDataSources( std::vector* ros, std::vector* lights ); + /// Set the viewpoint on the graph + void setCameras( std::vector* cameras ); + + /// get the computed texture vector + const std::vector& getImagesOutput() const; + + /// Set render techniques needed by the rendering nodes + void buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const; + /// Return the typename of the Graph + static const std::string getTypename(); + + protected: + bool postCompilationOperation() override; + void fromJsonInternal( const nlohmann::json& ) override; + void toJsonInternal( nlohmann::json& ) const override; + + private: + /// The renderer's shader program manager + Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; + /// List of nodes that requires some particular processing + std::vector m_renderingNodes; // to resize + std::vector m_rtIndexedNodes; // associate an index and buildRenderTechnique +#if 0 + /// List of nodes that serve as data provider + std::vector m_dataProviders; + + // DisplaySink observerMethod : right now, observe only displaySink node + void observeSinks( const std::vector& graphOutput ); + /// The display sink node used to get rendered images + DisplaySinkNode* m_displaySinkNode { nullptr }; +#endif + /// ObserverId for displaySink; + int m_displayObserverId { -1 }; + std::vector m_outputTextures; +}; + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl b/src/Dataflow/Rendering/Renderer/RenderingGraph.inl new file mode 100644 index 00000000000..cbccca6184d --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.inl @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { + +inline RenderingGraph::RenderingGraph( const std::string& name ) : + DataflowGraph( name, getTypename() ) {} + +inline const std::string RenderingGraph::getTypename() { + return "RenderingGraph"; +} + +inline void RenderingGraph::resize( uint32_t width, uint32_t height ) { + for ( auto rn : m_renderingNodes ) { + rn->resize( width, height ); + } +} + +inline void RenderingGraph::setDataSources( std::vector* ros, + std::vector* lights ) { +#if 0 + for(auto sn : m_dataProviders) { + sn->setScene(ros, lights); + } +#endif +} + +inline void RenderingGraph::setCameras( std::vector* cameras ) { +#if 0 + for(auto sn : m_dataProviders) { + sn->setCameras(cameras); + } +#endif +} + +inline void RenderingGraph::fromJsonInternal( const nlohmann::json& ) {} +inline void RenderingGraph::toJsonInternal( nlohmann::json& ) const {} + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake new file mode 100644 index 00000000000..79e584a43d0 --- /dev/null +++ b/src/Dataflow/Rendering/filelist.cmake @@ -0,0 +1,11 @@ +# ---------------------------------------------------- +# This file should not be generated by the radium script +# ---------------------------------------------------- + +set(dataflow_rendering_sources Renderer/DataflowRenderer.cpp Renderer/RenderingGraph.cpp) + +set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/DataflowRenderer.hpp + Renderer/RenderingGraph.hpp +) + +set(dataflow_rendering_inlines Renderer/RenderingGraph.inl) diff --git a/src/Dataflow/Rendering/pch.hpp b/src/Dataflow/Rendering/pch.hpp new file mode 100644 index 00000000000..6f70f09beec --- /dev/null +++ b/src/Dataflow/Rendering/pch.hpp @@ -0,0 +1 @@ +#pragma once From 5f993b8ab55ffcb33ce47cfc685e5961f98b66d8 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 2 Oct 2022 18:44:02 +0200 Subject: [PATCH 016/239] [dataflow-all] improve serialization --- .../GraphEditor/MainWindow.cpp | 10 +-- .../DataflowExamples/GraphEditor/main.cpp | 18 +++-- .../GraphSerialization/main.cpp | 32 ++++---- src/Dataflow/Core/DataflowGraph.cpp | 7 +- src/Dataflow/Core/DataflowGraph.hpp | 6 +- src/Dataflow/Core/DataflowGraph.inl | 10 ++- src/Dataflow/Core/Node.hpp | 6 +- src/Dataflow/Core/Node.inl | 51 ++++-------- src/Dataflow/Core/NodeFactory.cpp | 26 +++--- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 80 +++++++++++++++++++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 12 +++ .../Core/Nodes/Filters/CoreDataFilters.hpp | 21 +++++ .../Core/Nodes/Filters/FilterNode.hpp | 23 +++--- .../Core/Nodes/Filters/FilterNode.inl | 59 +++++++------- .../Core/Nodes/Sinks/CoreDataSinks.hpp | 29 +++++++ .../Core/Nodes/Sinks/CoreDataSinks.inl | 10 +++ src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 7 +- src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 13 +-- .../Core/Nodes/Sources/CoreDataSources.hpp | 52 ++++++++---- .../Core/Nodes/Sources/CoreDataSources.inl | 79 ++++++++++++------ .../Nodes/Sources/SingleDataSourceNode.hpp | 2 +- .../Nodes/Sources/SingleDataSourceNode.inl | 9 ++- src/Dataflow/Core/filelist.cmake | 5 +- .../QtGui/GraphEditor/GraphEditorView.cpp | 3 +- .../Rendering/Renderer/RenderingGraph.hpp | 2 +- .../Rendering/Renderer/RenderingGraph.inl | 5 +- 26 files changed, 395 insertions(+), 182 deletions(-) create mode 100644 src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp create mode 100644 src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp create mode 100644 src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp create mode 100644 src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/examples/DataflowExamples/GraphEditor/MainWindow.cpp index fada6544658..c05b4196c21 100644 --- a/examples/DataflowExamples/GraphEditor/MainWindow.cpp +++ b/examples/DataflowExamples/GraphEditor/MainWindow.cpp @@ -36,9 +36,9 @@ void MainWindow::newFile() { delete graph; setCurrentFile( "" ); - graph = new DataflowGraph( "untitled.flow" ); - auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); - graph->addFactory( defaultFactory->getName(), defaultFactory ); + graph = new DataflowGraph( "untitled.flow" ); + // auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); + // graph->addFactory( defaultFactory->getName(), defaultFactory ); graphEdit->editGraph( graph ); } } @@ -201,8 +201,8 @@ void MainWindow::loadFile( const QString& fileName ) graph->loadFromJson( fileName.toStdString() ); // Todo, always embed default factory in the graph - auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); - graph->addFactory( defaultFactory->getName(), defaultFactory ); + // auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); + // graph->addFactory( defaultFactory->getName(), defaultFactory ); graphEdit->editGraph( graph ); diff --git a/examples/DataflowExamples/GraphEditor/main.cpp b/examples/DataflowExamples/GraphEditor/main.cpp index c2acc43626c..48db8690166 100644 --- a/examples/DataflowExamples/GraphEditor/main.cpp +++ b/examples/DataflowExamples/GraphEditor/main.cpp @@ -24,20 +24,26 @@ int main( int argc, char* argv[] ) { parser.addPositionalArgument( "file", "The file to open." ); parser.process( app ); + // [Creating the factory for the custom nodes and add it to the nodes system] + using VectorType = std::vector; + // using VectorType = Ra::Core::VectorArray; + // custom node type are either specialization of templated nodes or user-define nodes class + // create the custom node factory NodeFactorySet::mapped_type customFactory { new NodeFactorySet::mapped_type::element_type( "ExampleCustomFactory" ) }; // add node creators to the factory - customFactory->registerNodeCreator>>( - Sources::SingleDataSourceNode>::getTypename() + "_", "Source" ); - customFactory->registerNodeCreator>( - Filters::FilterNode::getTypename() + "_", "Filters" ); - customFactory->registerNodeCreator>>( - Sinks::SinkNode>::getTypename() + "_", "Sink" ); + customFactory->registerNodeCreator>( + Sources::SingleDataSourceNode::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Filters::FilterNode::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Sinks::SinkNode::getTypename() + "_", "Custom" ); // register the factory into the system to enable loading any graph that use these nodes NodeFactoriesManager::registerFactory( customFactory ); + // [Creating the factory for the custom node types and add it to the node system] MainWindow mainWin; if ( !parser.positionalArguments().isEmpty() ) diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp index 58e0904959e..4bd99fd1dfb 100644 --- a/examples/DataflowExamples/GraphSerialization/main.cpp +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -3,6 +3,8 @@ #include #include +#include + using namespace Ra::Dataflow::Core; /* ----------------------------------------------------------------------------------- */ @@ -12,6 +14,8 @@ using namespace Ra::Dataflow::Core; int main( int argc, char* argv[] ) { //! [Creating the factory for the custom nodes and add it to the nodes system] + using VectorType = std::vector; + // using VectorType = Ra::Core::VectorArray; // custom node type are either specialization of templated nodes or user-define nodes class // create the custom node factory @@ -19,12 +23,12 @@ int main( int argc, char* argv[] ) { new NodeFactorySet::mapped_type::element_type( "ExampleCustomFactory" ) }; // add node creators to the factory - customFactory->registerNodeCreator>>( - Sources::SingleDataSourceNode>::getTypename() + "_", "Source" ); - customFactory->registerNodeCreator>( - Filters::FilterNode::getTypename() + "_", "Filters" ); - customFactory->registerNodeCreator>>( - Sinks::SinkNode>::getTypename() + "_", "Sink" ); + customFactory->registerNodeCreator>( + Sources::SingleDataSourceNode::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Filters::FilterNode::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Sinks::SinkNode::getTypename() + "_", "Custom" ); // register the factory into the system to enable loading any graph that use these nodes NodeFactoriesManager::registerFactory( customFactory ); @@ -39,11 +43,11 @@ int main( int argc, char* argv[] ) { //! [Creating an empty graph using the custom nodes factory] //! [Creating Nodes] - auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); + auto sourceNode = new Sources::SingleDataSourceNode( "Source" ); // non serializable node using a custom filter - auto filterNode = - new Filters::FilterNode( "Filter", []( Scalar x ) { return x > 0.5_ra; } ); - auto sinkNode = new Sinks::SinkNode>( "Sink" ); + auto filterNode = new Filters::FilterNode( + "Filter", []( const Scalar& x ) { return x > 0.5_ra; } ); + auto sinkNode = new Sinks::SinkNode( "Sink" ); //! [Creating Nodes] //! [Adding Nodes to the graph] @@ -112,7 +116,7 @@ int main( int argc, char* argv[] ) { //! [Verifing the graph can be compiled] //! [Creating input variable to test the graph] - std::vector test; + VectorType test; test.reserve( 10 ); std::mt19937 gen( 0 ); std::uniform_real_distribution<> dis( 0.0, 1.0 ); @@ -140,7 +144,7 @@ int main( int argc, char* argv[] ) { //! [Print the output result] auto output = g1.getDataGetter( "Sink_from" ); - std::vector result; + VectorType result; output->getData( result ); std::cout << "Output values : \n\t"; for ( auto ord : result ) { @@ -150,12 +154,12 @@ int main( int argc, char* argv[] ) { //! [Print the output result] //! [Set the correct filter on the filter node] - auto filter = dynamic_cast*>( g1.getNode( "Filter" ) ); + auto filter = dynamic_cast*>( g1.getNode( "Filter" ) ); if ( !filter ) { std::cerr << "Unable to cast the filter to the right type\n"; return 3; } - filter->setFilterFunction( []( Scalar f ) { return f > 0.5_ra; } ); + filter->setFilterFunction( []( const Scalar& f ) { return f > 0.5_ra; } ); //! [Set the correct filter on the filter node] //! [Execute the graph] diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index f65a7a5fc1d..799866926dd 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -14,10 +14,8 @@ DataflowGraph::DataflowGraph( const std::string& instanceName, const std::string Node( instanceName, typeName ) { // This will alllow to edit subgraph in an independant editor addEditableParameter( new EditableParameter( instanceName, *this ) ); - // A graph always use the builtin nodes factory - auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); - addFactory( defaultFactory->getName(), defaultFactory ); + addFactory( NodeFactoriesManager::getDataFlowBuiltInsFactory() ); } void DataflowGraph::init() { @@ -149,6 +147,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { m_recompile = true; // load the graph m_factories.reset( new NodeFactorySet ); + addFactory( NodeFactoriesManager::getDataFlowBuiltInsFactory() ); if ( data["graph"].contains( "factories" ) ) { auto factories = data["graph"]["factories"]; for ( const auto& factoryName : factories ) { @@ -158,7 +157,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { continue; } auto factory = NodeFactoriesManager::getFactory( factoryName ); - if ( factory ) { m_factories->addFactory( factoryName, factory ); } + if ( factory ) { addFactory( factory ); } else { std::cerr << "DataflowGraph::loadFromJson : Unable to find a factory with name " << factoryName << std::endl; diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index cee3d907cea..6b0b9bf0c36 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -43,6 +43,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Creates the factoryset if it does not exists void addFactory( const std::string& name, std::shared_ptr f ); + /// Add a factory to the factoryset of the graph. + /// Creates the factoryset if it does not exists + void addFactory( std::shared_ptr f ); + /// Loads nodes and links from a JSON file. /// @param jsonFilePath The path to the JSON file. bool loadFromJson( const std::string& jsonFilePath ); @@ -173,7 +177,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node int findNode( const std::string& name ); public: - static const std::string getTypename(); + static const std::string& getTypename(); }; } // namespace Core diff --git a/src/Dataflow/Core/DataflowGraph.inl b/src/Dataflow/Core/DataflowGraph.inl index b46ef2e6d54..1c4d0716305 100644 --- a/src/Dataflow/Core/DataflowGraph.inl +++ b/src/Dataflow/Core/DataflowGraph.inl @@ -17,6 +17,11 @@ inline void DataflowGraph::addFactory( const std::string& name, std::shared_ptr< m_factories->addFactory( name, f ); } +inline void DataflowGraph::addFactory( std::shared_ptr f ) { + if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } + m_factories->addFactory( f->getName(), f ); +} + inline const std::vector>* DataflowGraph::getNodes() const { return &m_nodes; } @@ -26,8 +31,9 @@ inline const std::vector>* DataflowGraph::getNodesByLevel() c inline size_t DataflowGraph::getNodesCount() { return m_nodes.size(); } -inline const std::string DataflowGraph::getTypename() { - return "DataFlow Graph"; +inline const std::string& DataflowGraph::getTypename() { + static std::string demangledTypeName { "Core DataflowGraph" }; + return demangledTypeName; } } // namespace Core diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 273b7b74967..1dd0a357c0d 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -201,9 +201,13 @@ class RA_DATAFLOW_API Node /// \brief Returns the demangled type name of the node or any human readable representation of /// the type name. /// This is a public static member each node must define to be serializable - static const std::string getTypename(); + static const std::string& getTypename(); }; +/// Return the human readable version of the type name T with simplified radium types +template +const char* simplifiedDemangledType() noexcept; + } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index 6d78a72f89b..3eed3490bdf 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -1,46 +1,22 @@ #pragma once #include +#include + namespace Ra { namespace Dataflow { namespace Core { -#if 0 -template -constexpr std::string_view type_name() { -# ifdef __clang__ - std::string_view p = __PRETTY_FUNCTION__; - return std::string_view( p.data() + 34, p.size() - 34 - 1 ); -# elif defined( __GNUC__ ) - std::string_view p = __PRETTY_FUNCTION__; -# if __cplusplus < 201402 - return std::string_view( p.data() + 36, p.size() - 36 - 1 ); -# else - return std::string_view( p.data() + 49, p.find( ';', 49 ) - 49 ); -# endif -# elif defined( _MSC_VER ) - std::string_view p = __FUNCSIG__; - return std::string_view( p.data() + 84, p.size() - 84 - 7 ); -# else - return type_id( T ).type_name(); -# endif -} - -inline std::size_t -replace_all_in_string( std::string& inout, std::string_view what, std::string_view with ) { - std::size_t count {}; - for ( std::string::size_type pos {}; - inout.npos != ( pos = inout.find( what.data(), pos, what.length() ) ); - pos += with.length(), ++count ) { - inout.replace( pos, what.length(), with.data(), with.length() ); - } - return count; -} - -inline std::size_t remove_all_in_string( std::string& inout, std::string_view what ) { - return replace_all_in_string( inout, what, "" ); +template +const char* simplifiedDemangledType() noexcept { + static std::string demangledType = Ra::Core::Utils::demangleType(); + Ra::Core::Utils::replaceAllInString( demangledType, "Ra::Core::VectorArray", "RaVector" ); + Ra::Core::Utils::replaceAllInString( + demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); + Ra::Core::Utils::replaceAllInString( + demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); + return demangledType.c_str(); } -#endif inline void Node::init() { #ifdef GRAPH_CALL_TRACE @@ -170,8 +146,9 @@ bool Node::removeEditableParameter( const std::string& name ) { return found; } -inline const std::string Node::getTypename() { - return "Node"; +inline const std::string& Node::getTypename() { + static std::string demangledTypeName { "Abstract Node" }; + return demangledTypeName; } inline bool Node::compile() { diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index af3e9e3f772..42916b3b3a3 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -78,6 +78,14 @@ Node* NodeFactorySet::createNode( std::string& nodeType, namespace NodeFactoriesManager { +/* + * TODO, replace static data by this when implementing an autoregistration mecanism +// to prevent static intialization order +static NodeFactorySet& s_factoryManager() { + static NodeFactorySet real_manager {}; + return real_manager; +}; +*/ static NodeFactorySet s_factoryManager {}; NodeFactorySet getFactoryManager() { @@ -107,21 +115,11 @@ NodeFactorySet::mapped_type getDataFlowBuiltInsFactory() { auto i = s_factoryManager.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); if ( i != s_factoryManager.end() ) { return i->second; } - // Create the DataFlowBuiltIns and add it to the manager - NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( - NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; - - // TODO Call an external function to populate the default factory - coreFactory->registerNodeCreator( - Sources::BooleanValueSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::ScalarValueSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::ColorSourceNode::getTypename() + "_", "Source" ); - - s_factoryManager.addFactory( NodeFactoriesManager::dataFlowBuiltInsFactoryName, coreFactory ); - return coreFactory; + // TODO, replace this by an autoregistration mecanism + registerStandardFactories(); + i = s_factoryManager.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); + return i->second; } } // namespace NodeFactoriesManager diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp new file mode 100644 index 00000000000..49c048762a7 --- /dev/null +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -0,0 +1,80 @@ +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +namespace NodeFactoriesManager { + +void registerStandardFactories() { + NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( + NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; + + /* --- Sources --- */ + coreFactory->registerNodeCreator( + Sources::BooleanValueSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::IntValueSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::UIntValueSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::ScalarValueSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::ColorSourceNode::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::FloatArrayDataSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::DoubleArrayDataSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::IntArrayDataSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::UIntArrayDataSource::getTypename() + "_", "Source" ); + coreFactory->registerNodeCreator( + Sources::ColorArrayDataSource::getTypename() + "_", "Source" ); + + /* --- Sinks --- */ + coreFactory->registerNodeCreator( Sinks::BooleanSink::getTypename() + "_", + "Sink" ); + coreFactory->registerNodeCreator( Sinks::IntSink::getTypename() + "_", "Sink" ); + coreFactory->registerNodeCreator( Sinks::UIntSink::getTypename() + "_", + "Sink" ); + coreFactory->registerNodeCreator( Sinks::ScalarSink::getTypename() + "_", + "Sink" ); + coreFactory->registerNodeCreator( Sinks::ColorSink::getTypename() + "_", + "Sink" ); + coreFactory->registerNodeCreator( + Sinks::FloatArraySink::getTypename() + "_", "Sink" ); + coreFactory->registerNodeCreator( + Sinks::DoubleArraySink::getTypename() + "_", "Sink" ); + coreFactory->registerNodeCreator( Sinks::IntArraySink::getTypename() + "_", + "Sink" ); + coreFactory->registerNodeCreator( + Sinks::UIntArraySink::getTypename() + "_", "Sink" ); + coreFactory->registerNodeCreator( + Sinks::ColorArraySink::getTypename() + "_", "Sink" ); + + /* --- Filters --- */ + coreFactory->registerNodeCreator( + Filters::FloatArrayFilter::getTypename() + "_", "Filter" ); + coreFactory->registerNodeCreator( + Filters::DoubleArrayFilter::getTypename() + "_", "Filter" ); + coreFactory->registerNodeCreator( + Filters::IntArrayFilter::getTypename() + "_", "Filter" ); + coreFactory->registerNodeCreator( + Filters::UIntArrayFilter::getTypename() + "_", "Filter" ); + coreFactory->registerNodeCreator( + Filters::ColorArrayFilter::getTypename() + "_", "Filter" ); + + /* --- Graphs --- */ + coreFactory->registerNodeCreator( DataflowGraph::getTypename() + "_", "Graph" ); + + registerFactory( coreFactory ); +} +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp index c7cad01fbaf..df16ce452ed 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -1,7 +1,19 @@ #pragma once +#include +#include #include +namespace Ra { +namespace Dataflow { +namespace Core { +namespace NodeFactoriesManager { +void registerStandardFactories(); +} +} // namespace Core +} // namespace Dataflow +} // namespace Ra + // These nodes will not be added in the BuiltIns factory as they are templated. // Only fully specialized template can be added to the factory. // #include diff --git a/src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp b/src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp new file mode 100644 index 00000000000..5a90c7c9e51 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp @@ -0,0 +1,21 @@ +#pragma once +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Filters { + +using FloatArrayFilter = FilterNode>; +using DoubleArrayFilter = FilterNode>; +using IntArrayFilter = FilterNode>; +using UIntArrayFilter = FilterNode>; +using ColorArrayFilter = FilterNode>; + +} // namespace Filters +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp b/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp index b8775d07c90..7ea3f7eb8a4 100644 --- a/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include + #include namespace Ra { @@ -8,20 +10,19 @@ namespace Dataflow { namespace Core { namespace Filters { -/** - * Filter a collection according to a unary predicate - * Copy each element which satisfies the filter unary predicate of the input std::vector - * to the output std::vector. - * @tparam T The value type of the collection +/** \brief Filter on iterable collection + * \tparam coll_t the collection to filter. Must respect the SequenceContainer requirements + * \tparam v_t (optional, type of the element in the collection) + * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer */ -template +template class FilterNode : public Node { public: /** * unaryPredicate Type */ - using UnaryPredicate = std::function; + using UnaryPredicate = std::function; /** * \brief Construct a filter accepting all its input ( true() lambda ) @@ -45,17 +46,17 @@ class FilterNode : public Node protected: FilterNode( const std::string& instanceName, const std::string& typeName, - std::function filterFunction ); + std::function filterFunction ); void toJsonInternal( nlohmann::json& data ) const override; void fromJsonInternal( const nlohmann::json& data ) override; private: - std::function m_filterFunction; - std::vector m_elements; + std::function m_filterFunction; + coll_t m_elements; public: - static const std::string getTypename(); + static const std::string& getTypename(); }; } // namespace Filters diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl b/src/Dataflow/Core/Nodes/Filters/FilterNode.inl index 74b916b0fa0..f298a48da0f 100644 --- a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Filters/FilterNode.inl @@ -1,38 +1,41 @@ #pragma once #include +#include + namespace Ra { namespace Dataflow { namespace Core { namespace Filters { -template -FilterNode::FilterNode( const std::string& instanceName ) : - FilterNode( instanceName, getTypename(), []( T ) { return true; } ) {} +template +FilterNode::FilterNode( const std::string& instanceName ) : + FilterNode( instanceName, getTypename(), []( v_t ) { return true; } ) {} -template -FilterNode::FilterNode( const std::string& instanceName, - FilterNode::UnaryPredicate filterFunction ) : +template +FilterNode::FilterNode( const std::string& instanceName, + UnaryPredicate filterFunction ) : FilterNode( instanceName, getTypename(), filterFunction ) {} -template -void FilterNode::setFilterFunction( UnaryPredicate filterFunction ) { +template +void FilterNode::setFilterFunction( UnaryPredicate filterFunction ) { m_filterFunction = filterFunction; } -template -void FilterNode::init() { +template +void FilterNode::init() { Node::init(); m_elements.clear(); } -template -void FilterNode::execute() { - auto input = dynamic_cast>*>( m_inputs[0].get() ); +template +void FilterNode::execute() { + auto input = dynamic_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { const auto& inData = input->getData(); m_elements.clear(); - m_elements.reserve( inData.size() ); + // m_elements.reserve( inData.size() ); // --> this is not a requirement of + // SequenceContainer std::copy_if( inData.begin(), inData.end(), std::back_inserter( m_elements ), m_filterFunction ); #ifdef GRAPH_CALL_TRACE @@ -43,33 +46,35 @@ void FilterNode::execute() { } } -template -const std::string FilterNode::getTypename() { - return std::string { "Filter::" } + Ra::Core::Utils::demangleType(); +template +const std::string& FilterNode::getTypename() { + static std::string demangledName = + std::string { "Filter<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; } -template -FilterNode::FilterNode( const std::string& instanceName, - const std::string& typeName, - std::function filterFunction ) : +template +FilterNode::FilterNode( const std::string& instanceName, + const std::string& typeName, + std::function filterFunction ) : Node( instanceName, typeName ), m_filterFunction( filterFunction ) { - auto portIn = new PortIn>( "in", this ); + auto portIn = new PortIn( "in", this ); addInput( portIn ); portIn->mustBeLinked(); - auto portOut = new PortOut>( "out", this ); + auto portOut = new PortOut( "out", this ); addOutput( portOut, &m_elements ); } -template -void FilterNode::toJsonInternal( nlohmann::json& data ) const { +template +void FilterNode::toJsonInternal( nlohmann::json& data ) const { data["comment"] = std::string { "Filtering function could not be serialized for " } + getTypeName(); LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to save data when serializing a " << getTypeName() << "."; } -template -void FilterNode::fromJsonInternal( const nlohmann::json& ) { +template +void FilterNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to read data when un-serializing a " << getTypeName() << "."; } diff --git a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp new file mode 100644 index 00000000000..b78770c9074 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp @@ -0,0 +1,29 @@ +#pragma once +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sinks { + +using BooleanSink = SinkNode; +using IntSink = SinkNode; +using UIntSink = SinkNode; +using ScalarSink = SinkNode; +using ColorSink = SinkNode; + +using FloatArraySink = SinkNode>; +using DoubleArraySink = SinkNode>; +using IntArraySink = SinkNode>; +using UIntArraySink = SinkNode>; +using ColorArraySink = SinkNode>; + +} // namespace Sinks +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl new file mode 100644 index 00000000000..45c67380a9d --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl @@ -0,0 +1,10 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sinks {} // namespace Sinks +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index 91cf7663a87..46a687ca621 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -9,8 +9,11 @@ namespace Sinks { template class SinkNode : public Node { + protected: + SinkNode( const std::string& instanceName, const std::string& typeName ); + public: - explicit SinkNode( const std::string& name ); + explicit SinkNode( const std::string& name ) : SinkNode( name, SinkNode::getTypename() ) {} void execute() override; /** @@ -32,7 +35,7 @@ class SinkNode : public Node T m_data; public: - static const std::string getTypename(); + static const std::string& getTypename(); }; } // namespace Sinks diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index 857e28762ea..7a478fb3b13 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -9,7 +9,8 @@ namespace Core { namespace Sinks { template -SinkNode::SinkNode( const std::string& name ) : Node( name, getTypename() ) { +SinkNode::SinkNode( const std::string& instanceName, const std::string& typeName ) : + Node( instanceName, typeName ) { auto portIn = new PortIn( "from", this ); portIn->mustBeLinked(); addInput( portIn ); @@ -43,15 +44,17 @@ const T& SinkNode::getDataByRef() const { } template -const std::string SinkNode::getTypename() { - return std::string { "Sink::" } + Ra::Core::Utils::demangleType(); +const std::string& SinkNode::getTypename() { + static std::string demangledName = + std::string { "Sink<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; } template -void SinkNode::toJsonInternal( nlohmann::json& data ) const {} +void SinkNode::toJsonInternal( nlohmann::json& /*data*/ ) const {} template -void SinkNode::fromJsonInternal( const nlohmann::json& data ) {} +void SinkNode::fromJsonInternal( const nlohmann::json& /*data*/ ) {} } // namespace Sinks } // namespace Core diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index c1e3f88522e..0ba8929c577 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -24,61 +24,79 @@ class RA_DATAFLOW_API BooleanValueSource : public SingleDataSourceNode void fromJsonInternal( const nlohmann::json& data ) override; public: - static const std::string getTypename(); + static const std::string& getTypename(); }; /** - * Specialization of SingleDataSourceNode for scalar value + * Specialization of SingleDataSourceNode for int value */ -class RA_DATAFLOW_API ScalarValueSource : public SingleDataSourceNode +class RA_DATAFLOW_API IntValueSource : public SingleDataSourceNode { public: - explicit ScalarValueSource( const std::string& name ); + explicit IntValueSource( const std::string& name ); protected: void toJsonInternal( nlohmann::json& data ) const override; void fromJsonInternal( const nlohmann::json& data ) override; public: - static const std::string getTypename(); + static const std::string& getTypename(); }; /** - * Specialization of SingleDataSourceNode for Core::Utils::Color value + * Specialization of SingleDataSourceNode for unsigned int value */ -class RA_DATAFLOW_API ColorSourceNode : public SingleDataSourceNode +class RA_DATAFLOW_API UIntValueSource : public SingleDataSourceNode { public: - explicit ColorSourceNode( const std::string& name ); + explicit UIntValueSource( const std::string& name ); protected: void toJsonInternal( nlohmann::json& data ) const override; void fromJsonInternal( const nlohmann::json& data ) override; public: - static const std::string getTypename(); + static const std::string& getTypename(); }; -/** \brief Partial specialization of SingleDataSourceNode for for Core::Containers::VectorArray. +/** + * Specialization of SingleDataSourceNode for scalar value */ -template -class VectorArrayDataSource : public SingleDataSourceNode> +class RA_DATAFLOW_API ScalarValueSource : public SingleDataSourceNode { + public: + explicit ScalarValueSource( const std::string& name ); + protected: - VectorArrayDataSource( const std::string& instanceName, const std::string& typeName ); + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + public: + static const std::string& getTypename(); +}; +/** + * Specialization of SingleDataSourceNode for Core::Utils::Color value + */ +class RA_DATAFLOW_API ColorSourceNode : public SingleDataSourceNode +{ public: - explicit VectorArrayDataSource( const std::string& name ) : - VectorArrayDataSource( name, VectorArrayDataSource::getTypename() ) {} + explicit ColorSourceNode( const std::string& name ); protected: void toJsonInternal( nlohmann::json& data ) const override; void fromJsonInternal( const nlohmann::json& data ) override; - private: - static const std::string getTypename(); + public: + static const std::string& getTypename(); }; +using FloatArrayDataSource = SingleDataSourceNode>; +using DoubleArrayDataSource = SingleDataSourceNode>; +using IntArrayDataSource = SingleDataSourceNode>; +using UIntArrayDataSource = SingleDataSourceNode>; +using ColorArrayDataSource = SingleDataSourceNode>; + } // namespace Sources } // namespace Core } // namespace Dataflow diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl index 16505466498..77dae2109fa 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl @@ -25,14 +25,59 @@ inline void BooleanValueSource::fromJsonInternal( const nlohmann::json& data ) { } } -inline const std::string BooleanValueSource::getTypename() { - return "Source::bool"; +inline const std::string& BooleanValueSource::getTypename() { + static std::string demangledTypeName { "Source" }; + return demangledTypeName; } -/* Source Scalar */ +/* Source int */ +inline IntValueSource::IntValueSource( const std::string& name ) : + SingleDataSourceNode( name, IntValueSource::getTypename() ) { + setEditable( "value" ); +} + +inline void IntValueSource::toJsonInternal( nlohmann::json& data ) const { + data["value"] = *getData(); +} + +inline void IntValueSource::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "value" ) ) { + int v = data["value"]; + setData( v ); + } +} -inline const std::string ScalarValueSource::getTypename() { - return "Source::Scalar"; +inline const std::string& IntValueSource::getTypename() { + static std::string demangledTypeName { "Source" }; + return demangledTypeName; +} + +/* Source uint */ +inline UIntValueSource::UIntValueSource( const std::string& name ) : + SingleDataSourceNode( name, UIntValueSource::getTypename() ) { + setEditable( "value" ); +} + +inline void UIntValueSource::toJsonInternal( nlohmann::json& data ) const { + data["value"] = *getData(); +} + +inline void UIntValueSource::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "value" ) ) { + unsigned int v = data["value"]; + setData( v ); + } +} + +inline const std::string& UIntValueSource::getTypename() { + static std::string demangledTypeName { "Source" }; + return demangledTypeName; +} + +/* Source Scalar */ +inline const std::string& ScalarValueSource::getTypename() { + static std::string demangledTypeName { "Source" }; + return demangledTypeName; } inline ScalarValueSource::ScalarValueSource( const std::string& name ) : @@ -52,9 +97,11 @@ inline void ScalarValueSource::fromJsonInternal( const nlohmann::json& data ) { } /* Source Color */ -inline const std::string ColorSourceNode::getTypename() { - return "Source::Color"; +inline const std::string& ColorSourceNode::getTypename() { + static std::string demangledTypeName { "Source" }; + return demangledTypeName; } + inline ColorSourceNode::ColorSourceNode( const std::string& name ) : SingleDataSourceNode( name, ColorSourceNode::getTypename() ) { setEditable( "color" ); @@ -75,24 +122,6 @@ inline void ColorSourceNode::fromJsonInternal( const nlohmann::json& data ) { } } -template -const std::string VectorArrayDataSource::getTypename() { - return std::string { "Source:VectorArray<" } + Ra::Core::Utils::demangleType() + ">"; -} - -template -void VectorArrayDataSource::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = std::string { "Serialization of data from a " } + - VectorArrayDataSource::getTypename() + " is not supported."; -} - -template -void VectorArrayDataSource::fromJsonInternal( const nlohmann::json& data ) { - if ( data.contains( "data" ) ) { - std::cerr << "Warning : deserialization of Ra::Core::VectorArray not yet implemented\n"; - } -} - } // namespace Sources } // namespace Core } // namespace Dataflow diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index de6df3589bf..ec0a8e02bb3 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -74,7 +74,7 @@ class SingleDataSourceNode : public Node void setData( T& data ); public: - static const std::string getTypename(); + static const std::string& getTypename(); }; } // namespace Sources diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index c74ac48188b..516ca42afa6 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -69,16 +69,17 @@ void SingleDataSourceNode::removeEditable( const std::string& name ) { } template -const std::string SingleDataSourceNode::getTypename() { - std::string templatedTypeName = Ra::Core::Utils::demangleType(); - return "Source::" + templatedTypeName; +const std::string& SingleDataSourceNode::getTypename() { + static std::string demangledTypeName = + std::string { "Source<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledTypeName; } template void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { data["comment"] = std::string( "Unable to save data when serializing a SingleDataSourceNode<" ) + - Ra::Core::Utils::demangleType() + ">."; + Ra::Dataflow::Core::simplifiedDemangledType() + ">."; LOG( Ra::Core::Utils::logDEBUG ) << "Unable to save data when serializing a " << getTypeName() << "."; } diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index da255b1b8ae..d5af6ccfd5a 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -4,7 +4,7 @@ # from ./scripts directory # ---------------------------------------------------- -set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp) +set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp Nodes/CoreBuiltInsNodes.cpp) set(dataflow_core_headers DataflowGraph.hpp @@ -13,7 +13,9 @@ set(dataflow_core_headers Node.hpp NodeFactory.hpp Nodes/CoreBuiltInsNodes.hpp + Nodes/Filters/CoreDataFilters.hpp Nodes/Filters/FilterNode.hpp + Nodes/Sinks/CoreDataSinks.hpp Nodes/Sinks/SinkNode.hpp Nodes/Sources/CoreDataSources.hpp Nodes/Sources/SingleDataSourceNode.hpp @@ -27,6 +29,7 @@ set(dataflow_core_inlines Node.inl NodeFactory.inl Nodes/Filters/FilterNode.inl + Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl Nodes/Sources/CoreDataSources.inl Nodes/Sources/SingleDataSourceNode.inl diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 7d1d567a944..f76e4e6de04 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -108,8 +108,7 @@ void GraphEditorView::editGraph( DataflowGraph* g ) { // Setup the graph to edit m_dataflowGraph = g; if ( m_dataflowGraph ) { - buildAdapterRegistry( *( m_dataflowGraph->getNodeFactories() ) ); - + buildAdapterRegistry( NodeFactoriesManager::getFactoryManager() ); const auto& nodes = *( m_dataflowGraph->getNodes() ); // inserting nodes for ( const auto& n : nodes ) { diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index b87862d1298..6fbdb743d41 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -47,7 +47,7 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph /// Set render techniques needed by the rendering nodes void buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const; /// Return the typename of the Graph - static const std::string getTypename(); + static const std::string& getTypename(); protected: bool postCompilationOperation() override; diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl b/src/Dataflow/Rendering/Renderer/RenderingGraph.inl index cbccca6184d..d9229568b38 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.inl @@ -10,8 +10,9 @@ namespace Renderer { inline RenderingGraph::RenderingGraph( const std::string& name ) : DataflowGraph( name, getTypename() ) {} -inline const std::string RenderingGraph::getTypename() { - return "RenderingGraph"; +inline const std::string& RenderingGraph::getTypename() { + static std::string demangledTypeName { "Rendering Graph" }; + return demangledTypeName; } inline void RenderingGraph::resize( uint32_t width, uint32_t height ) { From 794440aa9beb5359bf8dab817f502b720f1ea10a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 2 Oct 2022 19:01:43 +0200 Subject: [PATCH 017/239] [dataflow-core] improve type demangling for dataflow graph --- src/Dataflow/Core/Node.hpp | 4 ---- src/Dataflow/Core/Node.inl | 13 +------------ src/Dataflow/Core/Port.inl | 8 ++++++-- src/Dataflow/Core/TypeDemangler.hpp | 15 +++++++++++++++ src/Dataflow/Core/TypeDemangler.inl | 23 +++++++++++++++++++++++ src/Dataflow/Core/filelist.cmake | 2 ++ 6 files changed, 47 insertions(+), 18 deletions(-) create mode 100644 src/Dataflow/Core/TypeDemangler.hpp create mode 100644 src/Dataflow/Core/TypeDemangler.inl diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 1dd0a357c0d..ad35d115881 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -204,10 +204,6 @@ class RA_DATAFLOW_API Node static const std::string& getTypename(); }; -/// Return the human readable version of the type name T with simplified radium types -template -const char* simplifiedDemangledType() noexcept; - } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index 3eed3490bdf..7acf57a6f0b 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -1,23 +1,12 @@ #pragma once #include -#include +#include namespace Ra { namespace Dataflow { namespace Core { -template -const char* simplifiedDemangledType() noexcept { - static std::string demangledType = Ra::Core::Utils::demangleType(); - Ra::Core::Utils::replaceAllInString( demangledType, "Ra::Core::VectorArray", "RaVector" ); - Ra::Core::Utils::replaceAllInString( - demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); - Ra::Core::Utils::replaceAllInString( - demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); - return demangledType.c_str(); -} - inline void Node::init() { #ifdef GRAPH_CALL_TRACE std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl index ad88976b001..161f98b82c8 100644 --- a/src/Dataflow/Core/Port.inl +++ b/src/Dataflow/Core/Port.inl @@ -2,6 +2,8 @@ #include #include +#include + namespace Ra { namespace Dataflow { namespace Core { @@ -78,12 +80,14 @@ bool PortOut::connect( PortBase* o ) { template std::string PortOut::getTypeName() { - return Ra::Core::Utils::demangleType(); + // return Ra::Core::Utils::demangleType(); + return simplifiedDemangledType(); } template std::string PortIn::getTypeName() { - return Ra::Core::Utils::demangleType(); + // return Ra::Core::Utils::demangleType(); + return simplifiedDemangledType(); } template diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp new file mode 100644 index 00000000000..01a05c34069 --- /dev/null +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace Ra { +namespace Dataflow { +namespace Core { + +/// Return the human readable version of the type name T with simplified radium types +template +const char* simplifiedDemangledType() noexcept; + +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/TypeDemangler.inl b/src/Dataflow/Core/TypeDemangler.inl new file mode 100644 index 00000000000..650e7feb61b --- /dev/null +++ b/src/Dataflow/Core/TypeDemangler.inl @@ -0,0 +1,23 @@ +#pragma once +#include + +#include +namespace Ra { +namespace Dataflow { +namespace Core { + +/// Return the human readable version of the type name T with simplified radium types +template +const char* simplifiedDemangledType() noexcept { + static std::string demangledType = Ra::Core::Utils::demangleType(); + Ra::Core::Utils::replaceAllInString( demangledType, "Ra::Core::VectorArray", "RaVector" ); + Ra::Core::Utils::replaceAllInString( + demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); + Ra::Core::Utils::replaceAllInString( + demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); + return demangledType.c_str(); +} + +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index d5af6ccfd5a..db9fb38e72c 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -20,6 +20,7 @@ set(dataflow_core_headers Nodes/Sources/CoreDataSources.hpp Nodes/Sources/SingleDataSourceNode.hpp Port.hpp + TypeDemangler.hpp ) set(dataflow_core_inlines @@ -34,4 +35,5 @@ set(dataflow_core_inlines Nodes/Sources/CoreDataSources.inl Nodes/Sources/SingleDataSourceNode.inl Port.inl + TypeDemangler.inl ) From 120f7455f32de558c724afcdbec6573105e70d16 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 3 Oct 2022 15:57:36 +0200 Subject: [PATCH 018/239] [examples][dataflow] fix compilation of HelloGraph --- examples/DataflowExamples/HelloGraph/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index c5ac1c0bdc8..90dff419af5 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -17,8 +17,8 @@ int main( int argc, char* argv[] ) { //! [Creating Nodes] auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); // non serializable node using a custom filter - auto filterNode = - new Filters::FilterNode( "Filter", []( Scalar x ) { return x > 0.5_ra; } ); + auto filterNode = new Filters::FilterNode>( + "Filter", []( Scalar x ) { return x > 0.5_ra; } ); auto sinkNode = new Sinks::SinkNode>( "Sink" ); //! [Creating Nodes] From e0876e392e3e8dcc3636c5d3fb7f6d19c0135ade Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 3 Oct 2022 18:49:48 +0200 Subject: [PATCH 019/239] [dataflow] introduced function communication between nodes --- examples/DataflowExamples/HelloGraph/main.cpp | 23 +++--- src/Dataflow/Core/DataflowGraph.hpp | 8 ++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 7 ++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 1 + .../Core/Nodes/Filters/FilterNode.inl | 10 ++- .../Core/Nodes/Sources/FunctionSource.hpp | 61 ++++++++++++++++ .../Core/Nodes/Sources/FunctionSource.inl | 73 +++++++++++++++++++ .../Nodes/Sources/SingleDataSourceNode.inl | 5 +- src/Dataflow/Core/filelist.cmake | 2 + 9 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp create mode 100644 src/Dataflow/Core/Nodes/Sources/FunctionSource.inl diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index 90dff419af5..cf8ff826fd1 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include using namespace Ra::Dataflow::Core; @@ -15,21 +16,22 @@ int main( int argc, char* argv[] ) { //! [Creating an empty graph] //! [Creating Nodes] - auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); - // non serializable node using a custom filter - auto filterNode = new Filters::FilterNode>( - "Filter", []( Scalar x ) { return x > 0.5_ra; } ); - auto sinkNode = new Sinks::SinkNode>( "Sink" ); + auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); + auto predicateNode = new Sources::ScalarUnaryPredicate( "Selector" ); + auto filterNode = new Filters::FilterNode>( "Filter" ); + auto sinkNode = new Sinks::SinkNode>( "Sink" ); //! [Creating Nodes] //! [Adding Nodes to the graph] g.addNode( sourceNode ); + g.addNode( predicateNode ); g.addNode( filterNode ); g.addNode( sinkNode ); //! [Adding Nodes to the graph] //! [Creating links between Nodes] g.addLink( sourceNode, "to", filterNode, "in" ); + g.addLink( predicateNode, "f", filterNode, "f" ); g.addLink( filterNode, "out", sinkNode, "from" ); //! [Creating links between Nodes] @@ -57,6 +59,11 @@ int main( int argc, char* argv[] ) { auto input = g.getDataSetter( "Source_to" ); std::vector test; input->setData( &test ); + + auto selector = g.getDataSetter( "Selector_f" ); + Sources::ScalarUnaryPredicate::function_type pred = []( Scalar x ) { return x < 0.5; }; + selector->setData( &pred ); + auto output = g.getDataGetter( "Sink_from" ); // The reference to the result will not be available before the first run // auto& result = output->getData>(); @@ -93,10 +100,8 @@ int main( int argc, char* argv[] ) { //! [Print the output result] //! [Modify input and rerun the graph] - // here, we of re-use the same interface objects (pointer for input, reference for output) - for ( int n = 0; n < 10; ++n ) { - test.push_back( dis( gen ) ); - } + Sources::ScalarUnaryPredicate::function_type predbig = []( Scalar x ) { return x > 0.5; }; + selector->setData( &predbig ); g.execute(); std::cout << "Output values after second execution: \n\t"; for ( auto ord : result ) { diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 6b0b9bf0c36..f2290802059 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -9,6 +9,11 @@ namespace Ra { namespace Dataflow { namespace Core { +/** + * \todo : clarify what is a source and how to use it + * if sources must be used through interface port only, delete the set_data on all sources + * \todo Are node aliases a should-have (to make editing more user friendly)??? + */ /** * \brief Represent a set of connected nodes that definea computational graph * \todo make a "graph embedding node" that allow to seemlesly integrate a graph as a node in @@ -114,6 +119,8 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// pointer. \note If called multiple times for the same port, only the last returned result is /// usable. /// @params portName The name of the input port of the graph + /// TODO : Thereis a bug ???? When listing the data setters, they are connected ... + /// TODO : setters (and getters) Should be created once and activated/deactivated ??? std::shared_ptr getDataSetter( std::string portName ); /// Returns an alias to the named output port of the graph. @@ -129,6 +136,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// A tuple is composed of an output port connected to an input port of the graph, its name its /// type. \note If called multiple times for the same port, only the last returned result is /// usable. + /// TODO : Thereis a bug ???? When listing the data setters, they are connected ... std::vector getAllDataSetters(); /// Creates a vector that stores all the DataGetters (\see getDataGetter) of the graph. diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 49c048762a7..5042c7781cc 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -14,6 +14,7 @@ void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; + /** TODO : replace this by factory autoregistration at compile time */ /* --- Sources --- */ coreFactory->registerNodeCreator( Sources::BooleanValueSource::getTypename() + "_", "Source" ); @@ -69,6 +70,12 @@ void registerStandardFactories() { coreFactory->registerNodeCreator( Filters::ColorArrayFilter::getTypename() + "_", "Filter" ); + /* --- Functions --- */ + coreFactory->registerNodeCreator( + Sources::ScalarBinaryPredicate::getTypename() + "_", "Functions" ); + coreFactory->registerNodeCreator( + Sources::ScalarUnaryPredicate::getTypename() + "_", "Functions" ); + /* --- Graphs --- */ coreFactory->registerNodeCreator( DataflowGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp index df16ce452ed..9cdc868629d 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace Ra { namespace Dataflow { diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl b/src/Dataflow/Core/Nodes/Filters/FilterNode.inl index f298a48da0f..2f77544e778 100644 --- a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Filters/FilterNode.inl @@ -30,14 +30,16 @@ void FilterNode::init() { template void FilterNode::execute() { + auto predPort = dynamic_cast>*>( m_inputs[1].get() ); + auto f = m_filterFunction; + if ( predPort->isLinked() ) { f = predPort->getData(); } auto input = dynamic_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { const auto& inData = input->getData(); m_elements.clear(); // m_elements.reserve( inData.size() ); // --> this is not a requirement of // SequenceContainer - std::copy_if( - inData.begin(), inData.end(), std::back_inserter( m_elements ), m_filterFunction ); + std::copy_if( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); #ifdef GRAPH_CALL_TRACE std::cout << "\e[36m\e[1mFilterNode \e[0m \"" << m_instanceName << "\": execute, from " << input->getData().size() << " to " << m_elements.size() << " " @@ -61,6 +63,10 @@ FilterNode::FilterNode( const std::string& instanceName, auto portIn = new PortIn( "in", this ); addInput( portIn ); portIn->mustBeLinked(); + + auto predicate = new PortIn>( "f", this ); + addInput( predicate ); + auto portOut = new PortOut( "out", this ); addOutput( portOut, &m_elements ); } diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp new file mode 100644 index 00000000000..0db74cb5dbd --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -0,0 +1,61 @@ +#pragma once +#pragma once +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sources { + +template +class FunctionSourceNode : public Node +{ + + public: + using function_type = std::function; + + explicit FunctionSourceNode( const std::string& name ) : + FunctionSourceNode( name, FunctionSourceNode::getTypename() ) {} + + void execute() override; + + /** \brief Set the function to be delivered by the node. + * @param data + */ + void setData( function_type* data ); + + /** + * \brief Get the delivered data + * @return The non owning pointer (alias) to the delivered data. + */ + function_type* getData() const; + + protected: + FunctionSourceNode( const std::string& instanceName, const std::string& typeName ); + + void fromJsonInternal( const nlohmann::json& ) override; + void toJsonInternal( nlohmann::json& ) const override; + + /// The data provided by the node + function_type* m_data { nullptr }; + + function_type m_localData; + + /// Alias to the output port + PortOut* m_portOut { nullptr }; + + public: + static const std::string& getTypename(); +}; + +using ScalarBinaryPredicate = FunctionSourceNode; +using ScalarUnaryPredicate = FunctionSourceNode; +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl new file mode 100644 index 00000000000..5dd46cf832e --- /dev/null +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl @@ -0,0 +1,73 @@ +#pragma once +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Sources { + +template +FunctionSourceNode::FunctionSourceNode( const std::string& instanceName, + const std::string& typeName ) : + Node( instanceName, typeName ) { + m_localData = []( Args... ) { return R {}; }; + m_data = &m_localData; + m_portOut = new PortOut( "f", this ); + addOutput( m_portOut, m_data ); +} + +template +void FunctionSourceNode::execute() { + auto interface = static_cast*>( m_interface[0] ); + if ( interface->isLinked() ) { m_data = &( interface->getData() ); } + else { + m_data = &m_localData; + } + m_portOut->setData( m_data ); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[34m\e[1mFunctionSourceNode\e[0m \"" << m_instanceName << "\": execute." + << std::endl; +#endif +} + +template +void FunctionSourceNode::setData( function_type* data ) { + m_localData = *data; + m_data = &m_localData; + + m_portOut->setData( m_data ); +} + +template +typename FunctionSourceNode::function_type* +FunctionSourceNode::getData() const { + return m_data; +} + +template +const std::string& FunctionSourceNode::getTypename() { + static std::string demangledTypeName = + std::string { "Source<" } + Ra::Dataflow::Core::simplifiedDemangledType() + + ">"; + return demangledTypeName; +} + +template +void FunctionSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = std::string( "Unable to save data when serializing a FunctionSourceNode<" ) + + Ra::Dataflow::Core::simplifiedDemangledType() + ">."; + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +void FunctionSourceNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to read data when un-serializing a " << getTypeName() << "."; +} + +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 516ca42afa6..305061f4abb 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -19,10 +19,7 @@ SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, template void SingleDataSourceNode::execute() { auto interface = static_cast*>( m_interface[0] ); - if ( interface->isLinked() ) { - // copy the data to be delivered into the node (COPY_DATA) - m_data = &( interface->getData() ); - } + if ( interface->isLinked() ) { m_data = &( interface->getData() ); } else { m_data = &m_localData; } diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index db9fb38e72c..dd94e206e1d 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -18,6 +18,7 @@ set(dataflow_core_headers Nodes/Sinks/CoreDataSinks.hpp Nodes/Sinks/SinkNode.hpp Nodes/Sources/CoreDataSources.hpp + Nodes/Sources/FunctionSource.hpp Nodes/Sources/SingleDataSourceNode.hpp Port.hpp TypeDemangler.hpp @@ -33,6 +34,7 @@ set(dataflow_core_inlines Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl Nodes/Sources/CoreDataSources.inl + Nodes/Sources/FunctionSource.inl Nodes/Sources/SingleDataSourceNode.inl Port.inl TypeDemangler.inl From 7f3db0d3f48ba7d6808c7ac52dbe4ca653af8730 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Oct 2022 14:34:32 +0200 Subject: [PATCH 020/239] [dataflow] better management of dataSetters for interface port --- examples/DataflowExamples/HelloGraph/main.cpp | 29 ++++++++- src/Dataflow/Core/DataflowGraph.cpp | 61 ++++++++++++++----- src/Dataflow/Core/DataflowGraph.hpp | 14 +++++ .../Nodes/Sources/SingleDataSourceNode.inl | 3 +- 4 files changed, 88 insertions(+), 19 deletions(-) diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index cf8ff826fd1..0f9e8fc46ec 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -92,7 +92,7 @@ int main( int argc, char* argv[] ) { //! [Execute the graph] //! [Print the output result] - std::cout << "Output values (reference): \n\t"; + std::cout << "Output values (reference): " << result.size() << "\n\t"; for ( auto ord : result ) { std::cout << ord << ' '; } @@ -103,12 +103,37 @@ int main( int argc, char* argv[] ) { Sources::ScalarUnaryPredicate::function_type predbig = []( Scalar x ) { return x > 0.5; }; selector->setData( &predbig ); g.execute(); - std::cout << "Output values after second execution: \n\t"; + std::cout << "Output values after second execution: " << result.size() << "\n\t"; for ( auto ord : result ) { std::cout << ord << ' '; } std::cout << '\n'; //! [Modify input and rerun the graph] + //! [Disconnect data setter and rerun the graph - result is now empty] + g.releaseDataSetter( "Source_to" ); + g.execute(); + std::cout << "Output values after third execution: " << result.size() << "\n"; + //! [Disconnect data setter and rerun the graph] + + //! [As interface is disconnected, we can set data direclty on the source node] + sourceNode->setData( &test ); + g.execute(); + std::cout << "Output values after fourth execution: " << result.size() << "\n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Disconnect data setter and rerun the graph] + + //! [Reconnect data setter and rerun the graph - result is the same than second execution] + g.activateDataSetter( "Source_to" ); + g.execute(); + std::cout << "Output values after last execution: " << result.size() << "\n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Disconnect data setter and rerun the graph] return 0; } diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 799866926dd..58d61276051 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -270,7 +270,7 @@ bool DataflowGraph::addNode( Node* newNode ) { p->reflect( this, newNode->getInstanceName() + '_' + p->getName() ); portInterface->mustBeLinked(); newNode->addInterface( portInterface ); - addInput( portInterface ); + addSetter( portInterface ); } } if ( newNode->getOutputs().size() == 0 ) { // Check if it is a sink node @@ -656,14 +656,49 @@ int DataflowGraph::goThroughGraph( return maxLevel; } +bool DataflowGraph::addSetter( PortBase* in ) { + addInput( in ); + if ( m_dataSetters.find( in->getName() ) == m_dataSetters.end() ) { + auto portOut = std::shared_ptr( in->reflect( this, in->getName() ) ); + m_dataSetters.emplace( std::make_pair( + in->getName(), + DataSetter { DataSetterDesc { portOut, portOut->getName(), portOut->getTypeName() }, + in } ) ); + return true; + } + return false; +} + +bool DataflowGraph::releaseDataSetter( std::string portName ) { + auto setter = m_dataSetters.find( portName ); + if ( setter != m_dataSetters.end() ) { + auto [desc, in] = setter->second; + in->disconnect(); + return true; + } + return false; +} + +bool DataflowGraph::activateDataSetter( std::string portName ) { + auto setter = m_dataSetters.find( portName ); + if ( setter != m_dataSetters.end() ) { + auto [desc, in] = setter->second; + auto p = std::get<0>( desc ); + in->disconnect(); + p->connect( in ); + return true; + } + return false; +} + std::shared_ptr DataflowGraph::getDataSetter( std::string portName ) { - for ( auto& portIn : m_inputs ) { - if ( portIn->getName() == portName ) { - portIn->disconnect(); - auto portOut = portIn->reflect( this, portName ); - portOut->connect( portIn.get() ); - return std::shared_ptr( portOut ); - } + auto setter = m_dataSetters.find( portName ); + if ( setter != m_dataSetters.end() ) { + auto [desc, in] = setter->second; + auto p = std::get<0>( desc ); + in->disconnect(); + p->connect( in ); + return p; } #ifdef GRAPH_CALL_TRACE std::cout << "\e[36m\e[1mDataflowGraph::graphGetInput \e[0m \"" @@ -675,13 +710,9 @@ std::shared_ptr DataflowGraph::getDataSetter( std::string portName ) { std::vector DataflowGraph::getAllDataSetters() { std::vector r; - r.reserve( m_inputs.size() ); - for ( auto& portIn : m_inputs ) { - portIn->disconnect(); - auto portOut = portIn->reflect( this, portIn->getName() ); - portOut->connect( portIn.get() ); - r.emplace_back( - std::shared_ptr( portOut ), portOut->getName(), portOut->getTypeName() ); + r.reserve( m_dataSetters.size() ); + for ( auto& s : m_dataSetters ) { + r.push_back( s.second.first ); } return r; } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index f2290802059..3f0920b3456 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -123,6 +123,9 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// TODO : setters (and getters) Should be created once and activated/deactivated ??? std::shared_ptr getDataSetter( std::string portName ); + bool releaseDataSetter( std::string portName ); + bool activateDataSetter( std::string portName ); + /// Returns an alias to the named output port of the graph. /// Allows to get the data stored at this port after the execution of the graph. /// \note ownership is left to the graph. @@ -186,6 +189,17 @@ class RA_DATAFLOW_API DataflowGraph : public Node public: static const std::string& getTypename(); + + private: + /// Data setters management : used to pass parameter to the graph when the graph is not embeded + /// into another graph (inputs are here for this case). + + using DataSetter = std::pair; + + std::map m_dataSetters; + std::map m_dataGetters; + + bool addSetter( PortBase* in ); }; } // namespace Core diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 305061f4abb..5da2c80003b 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -33,8 +33,7 @@ void SingleDataSourceNode::execute() { template void SingleDataSourceNode::setData( T* data ) { if ( m_data == &m_localData ) { - // TODO : do we need to copy the data ? - std::cerr << "SingleDataSourceNode::setData : overriding the local data !!!\n"; + // copy data into local storage m_localData = *data; } else { From 9bad6c0ba3e5733f65983f07ab3933ca669ce22a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Oct 2022 19:14:23 +0200 Subject: [PATCH 021/239] [dataflow] Simplify code structure and node instanciation --- .../DataflowExamples/GraphEditor/main.cpp | 6 +- .../GraphSerialization/main.cpp | 10 +-- examples/DataflowExamples/HelloGraph/main.cpp | 9 +- src/Dataflow/Core/DataflowGraph.cpp | 14 ++- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 33 ++++--- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 2 +- .../Core/Nodes/Filters/CoreDataFilters.hpp | 21 ----- .../Nodes/Functionals/CoreDataFunctionals.hpp | 45 ++++++++++ .../{Filters => Functionals}/FilterNode.hpp | 16 ++-- .../{Filters => Functionals}/FilterNode.inl | 25 +++--- .../Core/Nodes/Functionals/MapNode.hpp | 67 ++++++++++++++ .../Core/Nodes/Functionals/MapNode.inl | 89 +++++++++++++++++++ .../Core/Nodes/Sources/FunctionSource.hpp | 4 +- src/Dataflow/Core/filelist.cmake | 8 +- 14 files changed, 276 insertions(+), 73 deletions(-) delete mode 100644 src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp create mode 100644 src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp rename src/Dataflow/Core/Nodes/{Filters => Functionals}/FilterNode.hpp (82%) rename src/Dataflow/Core/Nodes/{Filters => Functionals}/FilterNode.inl (83%) create mode 100644 src/Dataflow/Core/Nodes/Functionals/MapNode.hpp create mode 100644 src/Dataflow/Core/Nodes/Functionals/MapNode.inl diff --git a/examples/DataflowExamples/GraphEditor/main.cpp b/examples/DataflowExamples/GraphEditor/main.cpp index 48db8690166..f7d46ae583d 100644 --- a/examples/DataflowExamples/GraphEditor/main.cpp +++ b/examples/DataflowExamples/GraphEditor/main.cpp @@ -4,7 +4,7 @@ #include "MainWindow.hpp" -#include +#include #include #include @@ -36,8 +36,8 @@ int main( int argc, char* argv[] ) { // add node creators to the factory customFactory->registerNodeCreator>( Sources::SingleDataSourceNode::getTypename() + "_", "Custom" ); - customFactory->registerNodeCreator>( - Filters::FilterNode::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Functionals::FilterNode::getTypename() + "_", "Custom" ); customFactory->registerNodeCreator>( Sinks::SinkNode::getTypename() + "_", "Custom" ); diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp index 4bd99fd1dfb..950cf251f85 100644 --- a/examples/DataflowExamples/GraphSerialization/main.cpp +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -25,8 +25,8 @@ int main( int argc, char* argv[] ) { // add node creators to the factory customFactory->registerNodeCreator>( Sources::SingleDataSourceNode::getTypename() + "_", "Custom" ); - customFactory->registerNodeCreator>( - Filters::FilterNode::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Functionals::FilterNode::getTypename() + "_", "Custom" ); customFactory->registerNodeCreator>( Sinks::SinkNode::getTypename() + "_", "Custom" ); @@ -45,7 +45,7 @@ int main( int argc, char* argv[] ) { //! [Creating Nodes] auto sourceNode = new Sources::SingleDataSourceNode( "Source" ); // non serializable node using a custom filter - auto filterNode = new Filters::FilterNode( + auto filterNode = new Functionals::FilterNode( "Filter", []( const Scalar& x ) { return x > 0.5_ra; } ); auto sinkNode = new Sinks::SinkNode( "Sink" ); //! [Creating Nodes] @@ -154,7 +154,7 @@ int main( int argc, char* argv[] ) { //! [Print the output result] //! [Set the correct filter on the filter node] - auto filter = dynamic_cast*>( g1.getNode( "Filter" ) ); + auto filter = dynamic_cast*>( g1.getNode( "Filter" ) ); if ( !filter ) { std::cerr << "Unable to cast the filter to the right type\n"; return 3; diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index 0f9e8fc46ec..329fe7ebb3e 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -18,7 +18,7 @@ int main( int argc, char* argv[] ) { //! [Creating Nodes] auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); auto predicateNode = new Sources::ScalarUnaryPredicate( "Selector" ); - auto filterNode = new Filters::FilterNode>( "Filter" ); + auto filterNode = new Functionals::FilterNode>( "Filter" ); auto sinkNode = new Sinks::SinkNode>( "Sink" ); //! [Creating Nodes] @@ -31,7 +31,10 @@ int main( int argc, char* argv[] ) { //! [Creating links between Nodes] g.addLink( sourceNode, "to", filterNode, "in" ); - g.addLink( predicateNode, "f", filterNode, "f" ); + if ( !g.addLink( predicateNode, "f", filterNode, "f" ) ) { + std::cerr << "Error, can't link functional ports !\n"; + std::abort(); + } g.addLink( filterNode, "out", sinkNode, "from" ); //! [Creating links between Nodes] diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 58d61276051..58358984a3c 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -268,7 +268,9 @@ bool DataflowGraph::addNode( Node* newNode ) { #endif auto portInterface = p->reflect( this, newNode->getInstanceName() + '_' + p->getName() ); - portInterface->mustBeLinked(); + // an interface port may not be linked. + // TODO allow nodes to override addInterface so that they can add this requirements + // portInterface->mustBeLinked(); newNode->addInterface( portInterface ); addSetter( portInterface ); } @@ -418,12 +420,18 @@ bool DataflowGraph::addLink( Node* nodeFrom, } // Compare types + // TODO fix the variable naming ... if ( nodeTo->getInputs()[foundTo]->getType() != nodeFrom->getOutputs()[foundFrom]->getType() ) { #ifdef GRAPH_CALL_TRACE std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + - "\" for node \"" + nodeFrom->getInstanceName() + "\" : type mismatch." - << std::endl; + "\" for node \"" + nodeFrom->getInstanceName() + + "\" : type mismatch : \n\t" + "Type to : " + << nodeTo->getInputs()[foundTo]->getTypeName() + << "\n\t" + "Type from : " + << nodeFrom->getOutputs()[foundFrom]->getTypeName() << "\n\t" << std::endl; #endif return false; } diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 5042c7781cc..7c814c05713 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -9,6 +9,11 @@ namespace Dataflow { namespace Core { namespace NodeFactoriesManager { +#define ADD_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayMapper##SUFFIX::getTypename() + "_", #NAMESPACE ) void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( @@ -58,17 +63,23 @@ void registerStandardFactories() { coreFactory->registerNodeCreator( Sinks::ColorArraySink::getTypename() + "_", "Sink" ); - /* --- Filters --- */ - coreFactory->registerNodeCreator( - Filters::FloatArrayFilter::getTypename() + "_", "Filter" ); - coreFactory->registerNodeCreator( - Filters::DoubleArrayFilter::getTypename() + "_", "Filter" ); - coreFactory->registerNodeCreator( - Filters::IntArrayFilter::getTypename() + "_", "Filter" ); - coreFactory->registerNodeCreator( - Filters::UIntArrayFilter::getTypename() + "_", "Filter" ); - coreFactory->registerNodeCreator( - Filters::ColorArrayFilter::getTypename() + "_", "Filter" ); + /* --- Functionals */ + ADD_TO_FACTORY( coreFactory, Functionals, Float ); + ADD_TO_FACTORY( coreFactory, Functionals, Int ); + ADD_TO_FACTORY( coreFactory, Functionals, UInt ); + ADD_TO_FACTORY( coreFactory, Functionals, Color ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector2f ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector2d ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector3f ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector3d ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector4f ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector4d ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector2i ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector2ui ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector3i ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector3ui ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector4i ); + ADD_TO_FACTORY( coreFactory, Functionals, Vector4ui ); /* --- Functions --- */ coreFactory->registerNodeCreator( diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp index 9cdc868629d..d652532b949 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include #include diff --git a/src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp b/src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp deleted file mode 100644 index 5a90c7c9e51..00000000000 --- a/src/Dataflow/Core/Nodes/Filters/CoreDataFilters.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include - -#include -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Filters { - -using FloatArrayFilter = FilterNode>; -using DoubleArrayFilter = FilterNode>; -using IntArrayFilter = FilterNode>; -using UIntArrayFilter = FilterNode>; -using ColorArrayFilter = FilterNode>; - -} // namespace Filters -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp new file mode 100644 index 00000000000..48049844c7d --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -0,0 +1,45 @@ +#pragma once +#include +#include + +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +using namespace Ra::Core; + +// This macro does not end with semicolon. To be added when callin it +#define DECLARE_FUNCTIONALS( SUFFIX, TYPE ) \ + using ArrayFilter##SUFFIX = FilterNode>; \ + using ArrayMapper##SUFFIX = MapNode> + +DECLARE_FUNCTIONALS( Float, float ); +DECLARE_FUNCTIONALS( Double, double ); +DECLARE_FUNCTIONALS( Int, int ); +DECLARE_FUNCTIONALS( UInt, unsigned int ); +DECLARE_FUNCTIONALS( Color, Utils::Color ); +DECLARE_FUNCTIONALS( Vector2f, Vector2f ); +DECLARE_FUNCTIONALS( Vector2d, Vector2d ); +DECLARE_FUNCTIONALS( Vector3f, Vector3f ); +DECLARE_FUNCTIONALS( Vector3d, Vector3d ); +DECLARE_FUNCTIONALS( Vector4f, Vector4f ); +DECLARE_FUNCTIONALS( Vector4d, Vector4d ); +DECLARE_FUNCTIONALS( Vector2i, Vector2i ); +DECLARE_FUNCTIONALS( Vector3i, Vector3i ); +DECLARE_FUNCTIONALS( Vector4i, Vector4i ); +DECLARE_FUNCTIONALS( Vector2ui, Vector2ui ); +DECLARE_FUNCTIONALS( Vector3ui, Vector3ui ); +DECLARE_FUNCTIONALS( Vector4ui, Vector4ui ); + +// TODO, instanciate nodes for otherypes ? + +#undef DECLARE_FUNCTIONALS +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp similarity index 82% rename from src/Dataflow/Core/Nodes/Filters/FilterNode.hpp rename to src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index 7ea3f7eb8a4..ff6b7926b30 100644 --- a/src/Dataflow/Core/Nodes/Filters/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -8,7 +8,7 @@ namespace Ra { namespace Dataflow { namespace Core { -namespace Filters { +namespace Functionals { /** \brief Filter on iterable collection * \tparam coll_t the collection to filter. Must respect the SequenceContainer requirements @@ -33,35 +33,35 @@ class FilterNode : public Node /** * \brief Construct a filter with the given predicate * \param instanceName - * \param filterFunction + * \param predicate */ - FilterNode( const std::string& instanceName, UnaryPredicate filterFunction ); + FilterNode( const std::string& instanceName, UnaryPredicate predicate ); void init() override; void execute() override; /// Sets the filtering predicate on the node - void setFilterFunction( UnaryPredicate filterFunction ); + void setFilterFunction( UnaryPredicate predicate ); protected: FilterNode( const std::string& instanceName, const std::string& typeName, - std::function filterFunction ); + UnaryPredicate predicate ); void toJsonInternal( nlohmann::json& data ) const override; void fromJsonInternal( const nlohmann::json& data ) override; private: - std::function m_filterFunction; + UnaryPredicate m_predicate; coll_t m_elements; public: static const std::string& getTypename(); }; -} // namespace Filters +} // namespace Functionals } // namespace Core } // namespace Dataflow } // namespace Ra -#include +#include diff --git a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl similarity index 83% rename from src/Dataflow/Core/Nodes/Filters/FilterNode.inl rename to src/Dataflow/Core/Nodes/Functionals/FilterNode.inl index 2f77544e778..cb8c3aeeb42 100644 --- a/src/Dataflow/Core/Nodes/Filters/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl @@ -1,25 +1,24 @@ #pragma once -#include +#include #include namespace Ra { namespace Dataflow { namespace Core { -namespace Filters { +namespace Functionals { template FilterNode::FilterNode( const std::string& instanceName ) : FilterNode( instanceName, getTypename(), []( v_t ) { return true; } ) {} template -FilterNode::FilterNode( const std::string& instanceName, - UnaryPredicate filterFunction ) : - FilterNode( instanceName, getTypename(), filterFunction ) {} +FilterNode::FilterNode( const std::string& instanceName, UnaryPredicate predicate ) : + FilterNode( instanceName, getTypename(), predicate ) {} template -void FilterNode::setFilterFunction( UnaryPredicate filterFunction ) { - m_filterFunction = filterFunction; +void FilterNode::setFilterFunction( UnaryPredicate predicate ) { + m_predicate = predicate; } template @@ -30,8 +29,8 @@ void FilterNode::init() { template void FilterNode::execute() { - auto predPort = dynamic_cast>*>( m_inputs[1].get() ); - auto f = m_filterFunction; + auto predPort = dynamic_cast*>( m_inputs[1].get() ); + auto f = m_predicate; if ( predPort->isLinked() ) { f = predPort->getData(); } auto input = dynamic_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { @@ -58,13 +57,13 @@ const std::string& FilterNode::getTypename() { template FilterNode::FilterNode( const std::string& instanceName, const std::string& typeName, - std::function filterFunction ) : - Node( instanceName, typeName ), m_filterFunction( filterFunction ) { + UnaryPredicate filterFunction ) : + Node( instanceName, typeName ), m_predicate( filterFunction ) { auto portIn = new PortIn( "in", this ); addInput( portIn ); portIn->mustBeLinked(); - auto predicate = new PortIn>( "f", this ); + auto predicate = new PortIn( "f", this ); addInput( predicate ); auto portOut = new PortOut( "out", this ); @@ -85,7 +84,7 @@ void FilterNode::fromJsonInternal( const nlohmann::json& ) { << "Unable to read data when un-serializing a " << getTypeName() << "."; } -} // namespace Filters +} // namespace Functionals } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Functionals/MapNode.hpp b/src/Dataflow/Core/Nodes/Functionals/MapNode.hpp new file mode 100644 index 00000000000..26b13252440 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/MapNode.hpp @@ -0,0 +1,67 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +/** \brief Filter on iterable collection + * \tparam coll_t the collection to filter. Must respect the SequenceContainer requirements + * \tparam v_t (optional, type of the element in the collection) + * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer + */ +template +class MapNode : public Node +{ + public: + /** + * unaryPredicate Type + */ + using MapOperator = std::function; + + /** + * \brief Construct a filter accepting all its input ( true() lambda ) + * \param instanceName + */ + explicit MapNode( const std::string& instanceName ); + + /** + * \brief Construct a filter with the given predicate + * \param instanceName + * \param filterFunction + */ + MapNode( const std::string& instanceName, MapOperator mapOperator ); + + void init() override; + void execute() override; + + /// Sets the filtering predicate on the node + void setFilterFunction( MapOperator mapOperator ); + + protected: + MapNode( const std::string& instanceName, + const std::string& typeName, + MapOperator mapOperator ); + + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + private: + MapOperator m_mapOperator; + coll_t m_elements; + + public: + static const std::string& getTypename(); +}; + +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Functionals/MapNode.inl b/src/Dataflow/Core/Nodes/Functionals/MapNode.inl new file mode 100644 index 00000000000..626678d83b0 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/MapNode.inl @@ -0,0 +1,89 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +template +MapNode::MapNode( const std::string& instanceName ) : + MapNode( instanceName, getTypename(), []( v_t ) { return v_t {}; } ) {} + +template +MapNode::MapNode( const std::string& instanceName, MapOperator mapOperator ) : + MapNode( instanceName, getTypename(), mapOperator ) {} + +template +void MapNode::setFilterFunction( MapOperator mapOperator ) { + m_mapOperator = mapOperator; +} + +template +void MapNode::init() { + Node::init(); + m_elements.clear(); +} + +template +void MapNode::execute() { + auto predPort = dynamic_cast*>( m_inputs[1].get() ); + auto f = m_mapOperator; + if ( predPort->isLinked() ) { f = predPort->getData(); } + auto input = dynamic_cast*>( m_inputs[0].get() ); + if ( input->isLinked() ) { + const auto& inData = input->getData(); + m_elements.clear(); + // m_elements.reserve( inData.size() ); // --> this is not a requirement of + // SequenceContainer + std::transform( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[36m\e[1mMapNode \e[0m \"" << m_instanceName << "\": execute, from " + << input->getData().size() << " to " << m_elements.size() << " " + << typeid( T ).name() << "." << std::endl; +#endif + } +} + +template +const std::string& MapNode::getTypename() { + static std::string demangledName = + std::string { "Map<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; +} + +template +MapNode::MapNode( const std::string& instanceName, + const std::string& typeName, + MapOperator mapOperator ) : + Node( instanceName, typeName ), m_mapOperator( mapOperator ) { + auto portIn = new PortIn( "in", this ); + addInput( portIn ); + portIn->mustBeLinked(); + + auto op = new PortIn( "f", this ); + addInput( op ); + + auto portOut = new PortOut( "out", this ); + addOutput( portOut, &m_elements ); +} + +template +void MapNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = std::string { "Map operator could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +void MapNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; +} + +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index 0db74cb5dbd..5cde9652bcc 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -51,8 +51,8 @@ class FunctionSourceNode : public Node static const std::string& getTypename(); }; -using ScalarBinaryPredicate = FunctionSourceNode; -using ScalarUnaryPredicate = FunctionSourceNode; +using ScalarBinaryPredicate = FunctionSourceNode; +using ScalarUnaryPredicate = FunctionSourceNode; } // namespace Sources } // namespace Core } // namespace Dataflow diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index dd94e206e1d..0c3b60ff7df 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -13,8 +13,9 @@ set(dataflow_core_headers Node.hpp NodeFactory.hpp Nodes/CoreBuiltInsNodes.hpp - Nodes/Filters/CoreDataFilters.hpp - Nodes/Filters/FilterNode.hpp + Nodes/Functionals/CoreDataFunctionals.hpp + Nodes/Functionals/FilterNode.hpp + Nodes/Functionals/MapNode.hpp Nodes/Sinks/CoreDataSinks.hpp Nodes/Sinks/SinkNode.hpp Nodes/Sources/CoreDataSources.hpp @@ -30,7 +31,8 @@ set(dataflow_core_inlines Enumerator.inl Node.inl NodeFactory.inl - Nodes/Filters/FilterNode.inl + Nodes/Functionals/FilterNode.inl + Nodes/Functionals/MapNode.inl Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl Nodes/Sources/CoreDataSources.inl From b86c85dc96f92914cd771a567659bdbd577a715a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Oct 2022 18:36:47 +0200 Subject: [PATCH 022/239] [dataflow] TODO on builtinsNodes --- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 7c814c05713..0d2785127a3 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -9,6 +9,7 @@ namespace Dataflow { namespace Core { namespace NodeFactoriesManager { +/** TODO : replace this by factory autoregistration at compile time */ #define ADD_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ FACTORY->registerNodeCreator( \ NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ @@ -19,7 +20,6 @@ void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; - /** TODO : replace this by factory autoregistration at compile time */ /* --- Sources --- */ coreFactory->registerNodeCreator( Sources::BooleanValueSource::getTypename() + "_", "Source" ); From 7489803f6d32405baa0840d5a0d7853cfc9c7104 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Oct 2022 19:01:54 +0200 Subject: [PATCH 023/239] [dataflow] make QtNodeEditor an external under then name RadiumNodeEditor --- doc/basics/dependencies.md | 5 +- external/Dataflow/CMakeLists.txt | 17 + external/Dataflow/package.cmake | 5 + src/Dataflow/QtGui/CMakeLists.txt | 19 +- src/Dataflow/QtGui/Config.cmake.in | 8 +- .../QtGui/QtNodeEditor/CMakeLists.txt | 150 ----- src/Dataflow/QtGui/QtNodeEditor/LICENSE | 28 - src/Dataflow/QtGui/QtNodeEditor/README.md | 41 -- .../cmake/NodeEditorConfig.cmake.in | 11 - .../QtNodeEditor/include/nodes/Connection | 1 - .../include/nodes/ConnectionStyle | 1 - .../include/nodes/DataModelRegistry | 1 - .../QtNodeEditor/include/nodes/FlowScene | 1 - .../QtGui/QtNodeEditor/include/nodes/FlowView | 1 - .../QtNodeEditor/include/nodes/FlowViewStyle | 1 - .../QtGui/QtNodeEditor/include/nodes/Node | 1 - .../QtGui/QtNodeEditor/include/nodes/NodeData | 1 - .../QtNodeEditor/include/nodes/NodeDataModel | 1 - .../QtNodeEditor/include/nodes/NodeGeometry | 1 - .../include/nodes/NodePainterDelegate | 1 - .../QtNodeEditor/include/nodes/NodeState | 1 - .../QtNodeEditor/include/nodes/NodeStyle | 1 - .../include/nodes/StyleCollection | 1 - .../QtNodeEditor/include/nodes/TypeConverter | 1 - .../include/nodes/internal/Compiler.hpp | 40 -- .../include/nodes/internal/Connection.hpp | 126 ----- .../nodes/internal/ConnectionGeometry.hpp | 47 -- .../internal/ConnectionGraphicsObject.hpp | 66 --- .../nodes/internal/ConnectionState.hpp | 48 -- .../nodes/internal/ConnectionStyle.hpp | 54 -- .../nodes/internal/DataModelRegistry.hpp | 162 ------ .../include/nodes/internal/Export.hpp | 48 -- .../include/nodes/internal/FlowScene.hpp | 163 ------ .../include/nodes/internal/FlowView.hpp | 63 --- .../include/nodes/internal/FlowViewStyle.hpp | 32 -- .../include/nodes/internal/Node.hpp | 96 ---- .../include/nodes/internal/NodeData.hpp | 29 - .../include/nodes/internal/NodeDataModel.hpp | 119 ---- .../include/nodes/internal/NodeGeometry.hpp | 128 ----- .../nodes/internal/NodeGraphicsObject.hpp | 83 --- .../nodes/internal/NodePainterDelegate.hpp | 21 - .../include/nodes/internal/NodeState.hpp | 71 --- .../include/nodes/internal/NodeStyle.hpp | 51 -- .../nodes/internal/OperatingSystem.hpp | 49 -- .../include/nodes/internal/PortType.hpp | 48 -- .../include/nodes/internal/QStringStdHash.hpp | 21 - .../include/nodes/internal/QUuidStdHash.hpp | 13 - .../include/nodes/internal/Serializable.hpp | 16 - .../include/nodes/internal/Style.hpp | 20 - .../nodes/internal/StyleCollection.hpp | 42 -- .../include/nodes/internal/TypeConverter.hpp | 18 - .../include/nodes/internal/memory.hpp | 23 - .../QtNodeEditor/resources/DefaultStyle.json | 42 -- .../QtGui/QtNodeEditor/resources/convert.png | Bin 10203 -> 0 bytes .../QtNodeEditor/resources/resources.qrc | 6 - .../QtGui/QtNodeEditor/src/Connection.cpp | 316 ----------- .../QtNodeEditor/src/ConnectionBlurEffect.cpp | 21 - .../QtNodeEditor/src/ConnectionBlurEffect.hpp | 19 - .../QtNodeEditor/src/ConnectionGeometry.cpp | 103 ---- .../src/ConnectionGraphicsObject.cpp | 187 ------ .../QtNodeEditor/src/ConnectionPainter.cpp | 256 --------- .../QtNodeEditor/src/ConnectionPainter.hpp | 18 - .../QtNodeEditor/src/ConnectionState.cpp | 32 -- .../QtNodeEditor/src/ConnectionStyle.cpp | 173 ------ .../QtNodeEditor/src/DataModelRegistry.cpp | 42 -- .../QtGui/QtNodeEditor/src/FlowScene.cpp | 532 ------------------ .../QtGui/QtNodeEditor/src/FlowView.cpp | 316 ----------- .../QtGui/QtNodeEditor/src/FlowViewStyle.cpp | 94 ---- src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp | 179 ------ .../src/NodeConnectionInteraction.cpp | 180 ------ .../src/NodeConnectionInteraction.hpp | 59 -- .../QtGui/QtNodeEditor/src/NodeDataModel.cpp | 26 - .../QtGui/QtNodeEditor/src/NodeGeometry.cpp | 279 --------- .../QtNodeEditor/src/NodeGraphicsObject.cpp | 305 ---------- .../QtGui/QtNodeEditor/src/NodePainter.cpp | 341 ----------- .../QtGui/QtNodeEditor/src/NodePainter.hpp | 57 -- .../QtGui/QtNodeEditor/src/NodeState.cpp | 83 --- .../QtGui/QtNodeEditor/src/NodeStyle.cpp | 119 ---- .../QtGui/QtNodeEditor/src/Properties.cpp | 7 - .../QtGui/QtNodeEditor/src/Properties.hpp | 36 -- .../QtNodeEditor/src/StyleCollection.cpp | 36 -- 81 files changed, 38 insertions(+), 5822 deletions(-) delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/LICENSE delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/README.md delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/resources/convert.png delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp delete mode 100644 src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp diff --git a/doc/basics/dependencies.md b/doc/basics/dependencies.md index 73031d687cc..a34affe78eb 100644 --- a/doc/basics/dependencies.md +++ b/doc/basics/dependencies.md @@ -101,7 +101,7 @@ set(tinyEXR_DIR "/path/to/external/install/share/tinyEXR/cmake/" CACHE PATH "My set(assimp_DIR "/path/to/external/install/lib/cmake/assimp-5.0/" CACHE PATH "My assimp location") set(tinyply_DIR "/path/to/external/install/lib/cmake/tinyply/" CACHE PATH "My tinyply location") set(PowerSlider_DIR "/path/to/external/install/lib/cmake/PowerSlider/" CACHE PATH "My PowerSlider location") -set(stduuid_DIR "${RADIUM_DEP_PREFIX}/lib/cmake/stduuid/" CACHE PATH "My stduuid") +set(stduuid_DIR "/path/to/external/install/lib/cmake/stduuid/" CACHE PATH "My stduuid") set(RADIUM_IO_ASSIMP ON CACHE BOOL "Radium uses assimp io") set(RADIUM_IO_TINYPLY ON CACHE BOOL "Radium uses tinyply io") ~~~ @@ -125,6 +125,7 @@ Currently supported (note that these paths must refer to the installation direct * `stduuid_DIR` +* `RadiumNodeEditor_DIR` * `assimp_DIR` * `tinyply_DIR` * `PowerSlider_DIR` @@ -142,6 +143,8 @@ Radium is compiled and tested with specific version of dependencies, as given in * stduuid: https://github.com/mariusbancila/stduuid, [3afe7193facd5d674de709fccc44d5055e144d7a], * with options `-DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON` +* RadiumNodeEditor: https://github.com/MathiasPaulin/RadiumQtNodeEditor.git, [main], + * with options `None` * assimp: https://github.com/assimp/assimp.git, [tags/v5.0.1], * with options `-DASSIMP_BUILD_ASSIMP_TOOLS=False -DASSIMP_BUILD_SAMPLES=False -DASSIMP_BUILD_TESTS=False -DIGNORE_GIT_HASH=True -DASSIMP_NO_EXPORT=True` * tinyply: https://github.com/ddiakopoulos/tinyply.git, [tags/2.3.2], diff --git a/external/Dataflow/CMakeLists.txt b/external/Dataflow/CMakeLists.txt index 3820aff385c..b1f07f8970f 100644 --- a/external/Dataflow/CMakeLists.txt +++ b/external/Dataflow/CMakeLists.txt @@ -34,3 +34,20 @@ if(NOT DEFINED stduuid_DIR) else() status_message("" "stduuid" ${stduuid_DIR}) endif() + +if(NOT DEFINED RadiumNodeEditor_DIR) + check_externals_prerequisite() + status_message("[DataflowExternal]" "RadiumNodeEditor" "remote git") + ExternalProject_Add( + RadiumNodeEditor + GIT_REPOSITORY https://github.com/MathiasPaulin/RadiumQtNodeEditor.git + GIT_TAG main + GIT_SHALLOW TRUE + GIT_PROGRESS TRUE + INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" + CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" + ) + add_dependencies(DataflowExternals RadiumNodeEditor) +else() + status_message("" "RadiumNodeEditor" ${RadiumNodeEditor_DIR}) +endif() diff --git a/external/Dataflow/package.cmake b/external/Dataflow/package.cmake index 61807b50091..1b47a899a02 100644 --- a/external/Dataflow/package.cmake +++ b/external/Dataflow/package.cmake @@ -2,3 +2,8 @@ if(NOT DEFINED stduuid_DIR) set(stduuid_sub_DIR lib/cmake/stduuid CACHE INTERNAL "") set(stduuid_DIR ${CMAKE_INSTALL_PREFIX}/${stduuid_sub_DIR}) endif() + +if(NOT DEFINED RadiumNodeEditor_DIR) + set(RadiumNodeEditor_sub_DIR lib/cmake/RadiumNodeEditor CACHE INTERNAL "") + set(RadiumNodeEditor_DIR ${CMAKE_INSTALL_PREFIX}/${RadiumNodeEditor_sub_DIR}) +endif() diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt index 189ba53d445..044737d9216 100644 --- a/src/Dataflow/QtGui/CMakeLists.txt +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -3,7 +3,7 @@ list(APPEND CMAKE_MESSAGE_INDENT "[${ra_dataflowqtgui_target}] ") project(${ra_dataflowqtgui_target} LANGUAGES CXX VERSION ${Radium_VERSION}) -add_subdirectory(QtNodeEditor) +find_package(RadiumNodeEditor REQUIRED) include(filelist.cmake) @@ -33,20 +33,13 @@ target_compile_definitions(${ra_dataflowqtgui_target} PRIVATE RA_DATAFLOW_EXPORT target_compile_options(${ra_dataflowqtgui_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) -target_include_directories( - ${ra_dataflowqtgui_target} - PUBLIC $ - $ -) -target_include_directories( - ${ra_dataflowqtgui_target} PUBLIC $ - $ - PRIVATE ${CMAKE_CURRENT_BINARY_DIR} -) +target_include_directories(${ra_dataflowqtgui_target} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + add_dependencies(${ra_dataflowqtgui_target} DataflowCore Gui) + target_link_libraries( - ${ra_dataflowqtgui_target} PUBLIC ${Qt_LIBRARIES} DataflowCore Gui PUBLIC NodeEditor::NodeEditor - PRIVATE PowerSlider::PowerSlider + ${ra_dataflowqtgui_target} PUBLIC ${Qt_LIBRARIES} DataflowCore Gui + PUBLIC RadiumNodeEditor::RadiumNodeEditor PRIVATE PowerSlider::PowerSlider ) message(STATUS "Configuring library ${ra_dataflowqtgui_target} with standard settings") diff --git a/src/Dataflow/QtGui/Config.cmake.in b/src/Dataflow/QtGui/Config.cmake.in index 8729b0003d2..b630c4a6c28 100644 --- a/src/Dataflow/QtGui/Config.cmake.in +++ b/src/Dataflow/QtGui/Config.cmake.in @@ -34,8 +34,12 @@ if (DataflowQtGui_FOUND AND NOT TARGET DataflowQtGui) endif() if(Configure_DataflowQtGui) - if(NOT TARGET NodeEditor) - find_dependency(NodeEditor REQUIRED PATHS ${CMAKE_CURRENT_LIST_DIR}/NodeEditor) + if("@RadiumNodeEditor_DIR@" STREQUAL "") + set(RadiumNodeEditor_DIR "${_BaseExternalDir_}/@RadiumNodeEditor_sub_DIR@") + else() + set(RadiumNodeEditor_DIR "@RadiumNodeEditor_DIR@") endif() + find_dependency(glm REQUIRED NO_DEFAULT_PATH) + find_dependency(RadiumNodeEditor REQUIRED) include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/QtGui/DataflowQtGuiTargets.cmake") endif() diff --git a/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt b/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt deleted file mode 100644 index 843e3b89027..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/CMakeLists.txt +++ /dev/null @@ -1,150 +0,0 @@ -cmake_minimum_required(VERSION 3.2) -# version 3.4 is required as other do not work with C++14 and clang - -project(NodeEditor CXX) - -set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) -set(CMAKE_DISABLE_SOURCE_CHANGES ON) - -get_directory_property(_has_parent PARENT_DIRECTORY) -if(_has_parent) - set(is_root_project OFF) -else() - set(is_root_project ON) -endif() - -set(NE_DEVELOPER_DEFAULTS "${is_root_project}" - CACHE BOOL "Turns on default settings for development of NodeEditor" -) - -option(BUILD_SHARED_LIBS "Build as shared library" ON) -option(BUILD_DEBUG_POSTFIX_D "Append d suffix to debug libraries" OFF) - -enable_testing() - -if(NE_DEVELOPER_DEFAULTS) - set(CMAKE_CXX_STANDARD 14) - set(CMAKE_CXX_EXTENSIONS OFF) - - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") -endif() - -if(BUILD_DEBUG_POSTFIX_D) - set(CMAKE_DEBUG_POSTFIX d) -endif() - -include(QtFunctions) - -# Find the QtWidgets library -find_qt_package(COMPONENTS Core Widgets Gui OpenGL REQUIRED) - -qt_add_resources(RESOURCES ./resources/resources.qrc) - -# Unfortunately, as we have a split include/src, AUTOMOC doesn't work. We'll have to manually -# specify some files -set(CMAKE_AUTOMOC ON) - -set(CPP_SOURCE_FILES - src/Connection.cpp - src/ConnectionBlurEffect.cpp - src/ConnectionGeometry.cpp - src/ConnectionGraphicsObject.cpp - src/ConnectionPainter.cpp - src/ConnectionState.cpp - src/ConnectionStyle.cpp - src/DataModelRegistry.cpp - src/FlowScene.cpp - src/FlowView.cpp - src/FlowViewStyle.cpp - src/Node.cpp - src/NodeConnectionInteraction.cpp - src/NodeDataModel.cpp - src/NodeGeometry.cpp - src/NodeGraphicsObject.cpp - src/NodePainter.cpp - src/NodeState.cpp - src/NodeStyle.cpp - src/Properties.cpp - src/StyleCollection.cpp -) - -# If we want to give the option to build a static library, set BUILD_SHARED_LIBS option to OFF -add_library(NodeEditor ${CPP_SOURCE_FILES} ${RESOURCES}) -add_library(NodeEditor::NodeEditor ALIAS NodeEditor) - -target_include_directories( - NodeEditor - PUBLIC $ $ - PRIVATE $ - $ -) - -target_link_libraries(NodeEditor PUBLIC Qt::Core Qt::Widgets Qt::Gui Qt::OpenGL) - -target_compile_definitions( - NodeEditor - PUBLIC ${QtWidgets_DEFINITIONS} NODE_EDITOR_SHARED - PRIVATE NODE_EDITOR_EXPORTS - # NODE_DEBUG_DRAWING - QT_NO_KEYWORDS -) - -target_compile_options( - NodeEditor PRIVATE $<$:/W4 /wd4127 /EHsc> $<$:-Wall - -Wextra> $<$:-Wall -Wextra> -) - -target_compile_features(NodeEditor PUBLIC cxx_generic_lambdas # Require C++14 -) - -set_target_properties( - NodeEditor - PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib LIBRARY_OUTPUT_DIRECTORY - ${CMAKE_BINARY_DIR}/lib - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin -) - -# -# Moc -# - -file(GLOB_RECURSE HEADERS_TO_MOC include/nodes/internal/*.hpp) - -qt_wrap_cpp( - nodes_moc ${HEADERS_TO_MOC} TARGET NodeEditor OPTIONS --no-notes # Don't display a note for the - # headers which don't produce a - # moc_*.cpp -) - -target_sources(NodeEditor PRIVATE ${nodes_moc}) - -# ################################################################################################## -# Installation -# - -include(GNUInstallDirs) - -set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/Radium/DataflowQtGui/NodeEditor) - -install(TARGETS NodeEditor EXPORT NodeEditorTargets LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) - -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) - -install(EXPORT NodeEditorTargets FILE NodeEditorTargets.cmake NAMESPACE NodeEditor:: - DESTINATION ${INSTALL_CONFIGDIR} -) - -export(TARGETS NodeEditor NAMESPACE NodeEditor:: FILE NodeEditorTargets.cmake) - -include(CMakePackageConfigHelpers) - -configure_package_config_file( - ${CMAKE_CURRENT_LIST_DIR}/cmake/NodeEditorConfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake INSTALL_DESTINATION ${INSTALL_CONFIGDIR} -) - -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/NodeEditorConfig.cmake DESTINATION ${INSTALL_CONFIGDIR}) diff --git a/src/Dataflow/QtGui/QtNodeEditor/LICENSE b/src/Dataflow/QtGui/QtNodeEditor/LICENSE deleted file mode 100644 index bc2bbeaf955..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2017, Dmitry Pinaev -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of copyright holder, nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/Dataflow/QtGui/QtNodeEditor/README.md b/src/Dataflow/QtGui/QtNodeEditor/README.md deleted file mode 100644 index 49d407f429c..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/README.md +++ /dev/null @@ -1,41 +0,0 @@ -### Radium adaptation - -Code source taken from https://github.com/paceholder/nodeeditor -Some adaptations were made to allow seamless integration with Radium dataflow graph. - -> only used for graph editing (no evaluation with data propagation) - -> externalization of load/save and NodeDataModel management operation. - -> modified CMakeLists.txt and directory structure to keep only what is used in Radium - -### Purpose - -**NodeEditor** is conceived as a general-purpose Qt-based library aimed at -graph-controlled data processing. Nodes represent algorithms with certain inputs -and outputs. Connections transfer data from the output (source) of the first node -to the input (sink) of the second one. - -**NodeEditor** framework is a Visual [Dataflow -Programming](https://en.wikipedia.org/wiki/Dataflow_programming) tool. A library -client defines models and registers them in the data model registry. Further -work is driven by events taking place in DataModels and Nodes. The model -computing is triggered upon arriving of any new input data. The computed result -is propagated to the output connections. Each new connection fetches available -data and propagates is further. - -Each change in the source node is immediately propagated through all the -connections updating the whole graph. - -### Citing - - Dmitry Pinaev et al, Qt5 Node Editor, (2017), GitHub repository, https://github.com/paceholder/nodeeditor - -BibTeX - - @misc{Pinaev2017, - author = {Dmitry Pinaev et al}, - title = {Qt5 Node Editor}, - year = {2017}, - publisher = {GitHub}, - journal = {GitHub repository}, - howpublished = {\url{https://github.com/paceholder/nodeeditor}}, - commit = {1d1757d09b03cea0e4921bc19659465fe6e65b9b} - } diff --git a/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in b/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in deleted file mode 100644 index 15670832a28..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/cmake/NodeEditorConfig.cmake.in +++ /dev/null @@ -1,11 +0,0 @@ -get_filename_component(NodeEditor_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) - -if(NOT TARGET NodeEditor::NodeEditor) - include(${CMAKE_CURRENT_LIST_DIR}/../../QtFunctions.cmake) - check_and_set_qt_version("@QT_DEFAULT_MAJOR_VERSION@") - find_qt_dependency(COMPONENTS Core Widgets OpenGL Gui REQUIRED) - - include("${NodeEditor_CMAKE_DIR}/NodeEditorTargets.cmake") -endif() - -set(NodeEditor_LIBRARIES NodeEditor::NodeEditor) diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection deleted file mode 100644 index 9b6d297dd38..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Connection +++ /dev/null @@ -1 +0,0 @@ -#include "internal/Connection.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle deleted file mode 100644 index b771bf33f7e..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/ConnectionStyle +++ /dev/null @@ -1 +0,0 @@ -#include "internal/ConnectionStyle.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry deleted file mode 100644 index 2bb975c00d9..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/DataModelRegistry +++ /dev/null @@ -1 +0,0 @@ -#include "internal/DataModelRegistry.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene deleted file mode 100644 index b4e0745b64e..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowScene +++ /dev/null @@ -1 +0,0 @@ -#include "internal/FlowScene.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView deleted file mode 100644 index ca35f57633e..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowView +++ /dev/null @@ -1 +0,0 @@ -#include "internal/FlowView.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle deleted file mode 100644 index e66055b1b5c..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/FlowViewStyle +++ /dev/null @@ -1 +0,0 @@ -#include "internal/FlowViewStyle.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node deleted file mode 100644 index 9270781ad50..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/Node +++ /dev/null @@ -1 +0,0 @@ -#include "internal/Node.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData deleted file mode 100644 index 5fcbd7b3adb..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeData +++ /dev/null @@ -1 +0,0 @@ -#include "internal/NodeData.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel deleted file mode 100644 index 5346e982816..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeDataModel +++ /dev/null @@ -1 +0,0 @@ -#include "internal/NodeDataModel.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry deleted file mode 100644 index 5e1b523ad9f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeGeometry +++ /dev/null @@ -1 +0,0 @@ -#include "internal/NodeGeometry.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate deleted file mode 100644 index 3824071debd..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodePainterDelegate +++ /dev/null @@ -1 +0,0 @@ -#include "internal/NodePainterDelegate.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState deleted file mode 100644 index 5b531fd0e8b..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeState +++ /dev/null @@ -1 +0,0 @@ -#include "internal/NodeState.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle deleted file mode 100644 index d96092dc265..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/NodeStyle +++ /dev/null @@ -1 +0,0 @@ -#include "internal/NodeStyle.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection deleted file mode 100644 index e1f93ecac9f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/StyleCollection +++ /dev/null @@ -1 +0,0 @@ -#include "internal/StyleCollection.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter deleted file mode 100644 index 9bcdbbd23c2..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/TypeConverter +++ /dev/null @@ -1 +0,0 @@ -#include "internal/TypeConverter.hpp" diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp deleted file mode 100644 index 0e0eb0eee1b..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Compiler.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#if defined( __MINGW32__ ) || defined( __MINGW64__ ) -# define NODE_EDITOR_COMPILER "MinGW" -# define NODE_EDITOR_COMPILER_MINGW -#elif defined( __GNUC__ ) -# define NODE_EDITOR_COMPILER "GNU" -# define NODE_EDITOR_COMPILER_GNU -# define NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR __GNUC__ -# define NODE_EDITOR_COMPILER_GNU_VERSION_MINOR __GNUC_MINOR__ -# define NODE_EDITOR_COMPILER_GNU_VERSION_PATCH __GNUC_PATCHLEVEL__ -#elif defined( __clang__ ) -# define NODE_EDITOR_COMPILER "Clang" -# define NODE_EDITOR_COMPILER_CLANG -#elif defined( _MSC_VER ) -# define NODE_EDITOR_COMPILER "Microsoft Visual C++" -# define NODE_EDITOR_COMPILER_MICROSOFT -#elif defined( __BORLANDC__ ) -# define NODE_EDITOR_COMPILER "Borland C++ Builder" -# define NODE_EDITOR_COMPILER_BORLAND -#elif defined( __CODEGEARC__ ) -# define NODE_EDITOR_COMPILER "CodeGear C++ Builder" -# define NODE_EDITOR_COMPILER_CODEGEAR -#elif defined( __INTEL_COMPILER ) || defined( __ICL ) -# define NODE_EDITOR_COMPILER "Intel C++" -# define NODE_EDITOR_COMPILER_INTEL -#elif defined( __xlC__ ) || defined( __IBMCPP__ ) -# define NODE_EDITOR_COMPILER "IBM XL C++" -# define NODE_EDITOR_COMPILER_IBM -#elif defined( __HP_aCC ) -# define NODE_EDITOR_COMPILER "HP aC++" -# define NODE_EDITOR_COMPILER_HP -#elif defined( __WATCOMC__ ) -# define NODE_EDITOR_COMPILER "Watcom C++" -# define NODE_EDITOR_COMPILER_WATCOM -#endif - -#ifndef NODE_EDITOR_COMPILER -# error "Current compiler is not supported." -#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp deleted file mode 100644 index 24fbe5245fd..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Connection.hpp +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "NodeData.hpp" -#include "PortType.hpp" - -#include "ConnectionGeometry.hpp" -#include "ConnectionState.hpp" -#include "Export.hpp" -#include "QUuidStdHash.hpp" -#include "Serializable.hpp" -#include "TypeConverter.hpp" -#include "memory.hpp" - -class QPointF; - -namespace QtNodes { - -class Node; -class NodeData; -class ConnectionGraphicsObject; - -/// -class NODE_EDITOR_PUBLIC Connection : public QObject, public Serializable -{ - - Q_OBJECT - - public: - /// New Connection is attached to the port of the given Node. - /// The port has parameters (portType, portIndex). - /// The opposite connection end will require anothre port. - Connection( PortType portType, Node& node, PortIndex portIndex ); - - Connection( Node& nodeIn, - PortIndex portIndexIn, - Node& nodeOut, - PortIndex portIndexOut, - TypeConverter converter = TypeConverter {} ); - - Connection( const Connection& ) = delete; - Connection operator=( const Connection& ) = delete; - - ~Connection(); - - public: - QJsonObject save() const override; - - public: - QUuid id() const; - - /// Remembers the end being dragged. - /// Invalidates Node address. - /// Grabs mouse. - void setRequiredPort( PortType portType ); - PortType requiredPort() const; - - void setGraphicsObject( std::unique_ptr&& graphics ); - - /// Assigns a node to the required port. - /// It is assumed that there is a required port, no extra checks - void setNodeToPort( Node& node, PortType portType, PortIndex portIndex ); - - void removeFromNodes() const; - - public: - ConnectionGraphicsObject& getConnectionGraphicsObject() const; - - ConnectionState const& connectionState() const; - ConnectionState& connectionState(); - - ConnectionGeometry& connectionGeometry(); - - ConnectionGeometry const& connectionGeometry() const; - - Node* getNode( PortType portType ) const; - - Node*& getNode( PortType portType ); - - PortIndex getPortIndex( PortType portType ) const; - - void clearNode( PortType portType ); - - NodeDataType dataType( PortType portType ) const; - - void setTypeConverter( TypeConverter converter ); - - bool complete() const; - - public: // data propagation - void propagateData( std::shared_ptr nodeData ) const; - - void propagateEmptyData() const; - - Q_SIGNALS: - - void connectionCompleted( Connection const& ) const; - - void connectionMadeIncomplete( Connection const& ) const; - - private: - QUuid _uid; - - private: - Node* _outNode = nullptr; - Node* _inNode = nullptr; - - PortIndex _outPortIndex; - PortIndex _inPortIndex; - - private: - ConnectionState _connectionState; - ConnectionGeometry _connectionGeometry; - - std::unique_ptr _connectionGraphicsObject; - - TypeConverter _converter; - - Q_SIGNALS: - - void updated( Connection& conn ) const; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp deleted file mode 100644 index 7ca5b965b7d..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGeometry.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "PortType.hpp" - -#include -#include - -#include - -namespace QtNodes { - -class ConnectionGeometry -{ - public: - ConnectionGeometry(); - - public: - QPointF const& getEndPoint( PortType portType ) const; - - void setEndPoint( PortType portType, QPointF const& point ); - - void moveEndPoint( PortType portType, QPointF const& offset ); - - QRectF boundingRect() const; - - std::pair pointsC1C2() const; - - QPointF source() const { return _out; } - QPointF sink() const { return _in; } - - double lineWidth() const { return _lineWidth; } - - bool hovered() const { return _hovered; } - void setHovered( bool hovered ) { _hovered = hovered; } - - private: - // local object coordinates - QPointF _in; - QPointF _out; - - // int _animationPhase; - - double _lineWidth; - - bool _hovered; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp deleted file mode 100644 index b0286da0e9f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionGraphicsObject.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include - -#include - -class QGraphicsSceneMouseEvent; - -namespace QtNodes { - -class FlowScene; -class Connection; -class ConnectionGeometry; -class Node; - -/// Graphic Object for connection. Adds itself to scene -class ConnectionGraphicsObject : public QGraphicsObject -{ - Q_OBJECT - - public: - ConnectionGraphicsObject( FlowScene& scene, Connection& connection ); - - virtual ~ConnectionGraphicsObject(); - - enum { Type = UserType + 2 }; - int type() const override { return Type; } - - public: - Connection& connection(); - - QRectF boundingRect() const override; - - QPainterPath shape() const override; - - void setGeometryChanged(); - - /// Updates the position of both ends - void move(); - - void lock( bool locked ); - - protected: - void paint( QPainter* painter, - QStyleOptionGraphicsItem const* option, - QWidget* widget = 0 ) override; - - void mousePressEvent( QGraphicsSceneMouseEvent* event ) override; - - void mouseMoveEvent( QGraphicsSceneMouseEvent* event ) override; - - void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) override; - - void hoverEnterEvent( QGraphicsSceneHoverEvent* event ) override; - - void hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) override; - - private: - void addGraphicsEffect(); - - private: - FlowScene& _scene; - - Connection& _connection; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp deleted file mode 100644 index 1e9d9588e10..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionState.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include - -#include "PortType.hpp" - -class QPointF; - -namespace QtNodes { - -class Node; - -/// Stores currently draggind end. -/// Remembers last hovered Node. -class ConnectionState -{ - public: - ConnectionState( PortType port = PortType::None ) : _requiredPort( port ) {} - - ConnectionState( const ConnectionState& ) = delete; - ConnectionState operator=( const ConnectionState& ) = delete; - - ~ConnectionState(); - - public: - void setRequiredPort( PortType end ) { _requiredPort = end; } - - PortType requiredPort() const { return _requiredPort; } - - bool requiresPort() const { return _requiredPort != PortType::None; } - - void setNoRequiredPort() { _requiredPort = PortType::None; } - - public: - void interactWithNode( Node* node ); - - void setLastHoveredNode( Node* node ); - - Node* lastHoveredNode() const { return _lastHoveredNode; } - - void resetLastHoveredNode(); - - private: - PortType _requiredPort; - - Node* _lastHoveredNode { nullptr }; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp deleted file mode 100644 index df4883402f1..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/ConnectionStyle.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" -#include "Style.hpp" - -namespace QtNodes { - -class NODE_EDITOR_PUBLIC ConnectionStyle : public Style -{ - public: - ConnectionStyle(); - - ConnectionStyle( QString jsonText ); - - public: - static void setConnectionStyle( QString jsonText ); - - private: - void loadJsonText( QString jsonText ) override; - - void loadJsonFile( QString fileName ) override; - - void loadJsonFromByteArray( QByteArray const& byteArray ) override; - - public: - QColor constructionColor() const; - QColor normalColor() const; - QColor normalColor( QString typeId ) const; - QColor selectedColor() const; - QColor selectedHaloColor() const; - QColor hoveredColor() const; - - float lineWidth() const; - float constructionLineWidth() const; - float pointDiameter() const; - - bool useDataDefinedColors() const; - - private: - QColor ConstructionColor; - QColor NormalColor; - QColor SelectedColor; - QColor SelectedHaloColor; - QColor HoveredColor; - - float LineWidth; - float ConstructionLineWidth; - float PointDiameter; - - bool UseDataDefinedColors; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp deleted file mode 100644 index 3003b489bd8..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/DataModelRegistry.hpp +++ /dev/null @@ -1,162 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "Export.hpp" -#include "NodeDataModel.hpp" -#include "QStringStdHash.hpp" -#include "TypeConverter.hpp" -#include "memory.hpp" - -namespace QtNodes { - -inline bool operator<( QtNodes::NodeDataType const& d1, QtNodes::NodeDataType const& d2 ) { - return d1.id < d2.id; -} - -/// Class uses map for storing models (name, model) -class NODE_EDITOR_PUBLIC DataModelRegistry -{ - - public: - using RegistryItemPtr = std::unique_ptr; - using RegistryItemCreator = std::function; - using RegisteredModelCreatorsMap = std::unordered_map; - using RegisteredModelsCategoryMap = std::unordered_map; - using CategoriesSet = std::set; - - using RegisteredTypeConvertersMap = std::map; - - DataModelRegistry() = default; - ~DataModelRegistry() = default; - - DataModelRegistry( DataModelRegistry const& ) = delete; - DataModelRegistry( DataModelRegistry&& ) = default; - - DataModelRegistry& operator=( DataModelRegistry const& ) = delete; - DataModelRegistry& operator=( DataModelRegistry&& ) = default; - - public: - // --- - /* Added for Radium */ - void - registerModel( QString const& typeName, RegistryItemCreator creator, QString const& category ) { - if ( !_registeredItemCreators.count( typeName ) ) { - _registeredItemCreators[typeName] = std::move( creator ); - _categories.insert( category ); - _registeredModelsCategory[typeName] = category; - } - } - // --- - template - void registerModel( RegistryItemCreator creator, QString const& category = "Nodes" ) { - const QString name = computeName( HasStaticMethodName {}, creator ); - if ( !_registeredItemCreators.count( name ) ) { - _registeredItemCreators[name] = std::move( creator ); - _categories.insert( category ); - _registeredModelsCategory[name] = category; - } - } - - template - void registerModel( QString const& category = "Nodes" ) { - RegistryItemCreator creator = []() { return std::make_unique(); }; - registerModel( std::move( creator ), category ); - } - - template - void registerModel( QString const& category, RegistryItemCreator creator ) { - registerModel( std::move( creator ), category ); - } - - template - void registerModel( ModelCreator&& creator, QString const& category = "Nodes" ) { - using ModelType = compute_model_type_t; - registerModel( std::forward( creator ), category ); - } - - template - void registerModel( QString const& category, ModelCreator&& creator ) { - registerModel( std::forward( creator ), category ); - } - - void registerTypeConverter( TypeConverterId const& id, TypeConverter typeConverter ) { - _registeredTypeConverters[id] = std::move( typeConverter ); - } - - std::unique_ptr create( QString const& modelName ); - - RegisteredModelCreatorsMap const& registeredModelCreators() const; - - RegisteredModelsCategoryMap const& registeredModelsCategoryAssociation() const; - - CategoriesSet const& categories() const; - - TypeConverter getTypeConverter( NodeDataType const& d1, NodeDataType const& d2 ) const; - - private: - RegisteredModelsCategoryMap _registeredModelsCategory; - - CategoriesSet _categories; - - RegisteredModelCreatorsMap _registeredItemCreators; - - RegisteredTypeConvertersMap _registeredTypeConverters; - - private: - // If the registered ModelType class has the static member method - // - // static Qstring Name(); - // - // use it. Otherwise use the non-static method: - // - // virtual QString name() const; - - template - struct HasStaticMethodName : std::false_type {}; - - template - struct HasStaticMethodName< - T, - typename std::enable_if::value>::type> - : std::true_type {}; - - template - static QString computeName( std::true_type, RegistryItemCreator const& ) { - return ModelType::Name(); - } - - template - static QString computeName( std::false_type, RegistryItemCreator const& creator ) { - return creator()->name(); - } - - template - struct UnwrapUniquePtr { - // Assert always fires, but the compiler doesn't know this: - static_assert( !std::is_same::value, - "The ModelCreator must return a std::unique_ptr, where T " - "inherits from NodeDataModel" ); - }; - - template - struct UnwrapUniquePtr> { - static_assert( std::is_base_of::value, - "The ModelCreator must return a std::unique_ptr, where T " - "inherits from NodeDataModel" ); - using type = T; - }; - - template - using compute_model_type_t = typename UnwrapUniquePtr::type; -}; - -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp deleted file mode 100644 index 15d4cea17f2..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Export.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include "Compiler.hpp" -#include "OperatingSystem.hpp" - -#ifdef NODE_EDITOR_PLATFORM_WINDOWS -# define NODE_EDITOR_EXPORT __declspec( dllexport ) -# define NODE_EDITOR_IMPORT __declspec( dllimport ) -# define NODE_EDITOR_LOCAL -#elif NODE_EDITOR_COMPILER_GNU_VERSION_MAJOR >= 4 || defined( NODE_EDITOR_COMPILER_CLANG ) -# define NODE_EDITOR_EXPORT __attribute__( ( visibility( "default" ) ) ) -# define NODE_EDITOR_IMPORT __attribute__( ( visibility( "default" ) ) ) -# define NODE_EDITOR_LOCAL __attribute__( ( visibility( "hidden" ) ) ) -#else -# define NODE_EDITOR_EXPORT -# define NODE_EDITOR_IMPORT -# define NODE_EDITOR_LOCAL -#endif - -#ifdef __cplusplus -# define NODE_EDITOR_DEMANGLED extern "C" -#else -# define NODE_EDITOR_DEMANGLED -#endif - -#if defined( NODE_EDITOR_SHARED ) && !defined( NODE_EDITOR_STATIC ) -# ifdef NODE_EDITOR_EXPORTS -# define NODE_EDITOR_PUBLIC NODE_EDITOR_EXPORT -# else -# define NODE_EDITOR_PUBLIC NODE_EDITOR_IMPORT -# endif -# define NODE_EDITOR_PRIVATE NODE_EDITOR_LOCAL -#elif !defined( NODE_EDITOR_SHARED ) && defined( NODE_EDITOR_STATIC ) -# define NODE_EDITOR_PUBLIC -# define NODE_EDITOR_PRIVATE -#elif defined( NODE_EDITOR_SHARED ) && defined( NODE_EDITOR_STATIC ) -# ifdef NODE_EDITOR_EXPORTS -# error "Cannot build as shared and static simultaneously." -# else -# error "Cannot link against shared and static simultaneously." -# endif -#else -# ifdef NODE_EDITOR_EXPORTS -# error "Choose whether to build as shared or static." -# else -# error "Choose whether to link against shared or static." -# endif -#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp deleted file mode 100644 index 69915c230af..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowScene.hpp +++ /dev/null @@ -1,163 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include - -#include "DataModelRegistry.hpp" -#include "Export.hpp" -#include "QUuidStdHash.hpp" -#include "TypeConverter.hpp" -#include "memory.hpp" - -namespace QtNodes { - -class NodeDataModel; -class FlowItemInterface; -class Node; -class NodeGraphicsObject; -class Connection; -class ConnectionGraphicsObject; -class NodeStyle; - -/// Scene holds connections and nodes. -class NODE_EDITOR_PUBLIC FlowScene : public QGraphicsScene -{ - Q_OBJECT - public: - FlowScene( std::shared_ptr registry, QObject* parent = Q_NULLPTR ); - - FlowScene( QObject* parent = Q_NULLPTR ); - - ~FlowScene(); - - public: - std::shared_ptr - createConnection( PortType connectedPort, Node& node, PortIndex portIndex ); - - std::shared_ptr - createConnection( Node& nodeIn, - PortIndex portIndexIn, - Node& nodeOut, - PortIndex portIndexOut, - TypeConverter const& converter = TypeConverter {} ); - - std::shared_ptr restoreConnection( QJsonObject const& connectionJson ); - - void importConnection( const QString& fromNodeId, - int fromNodePort, - const QString& toNodeId, - int toNodePort ); - - void deleteConnection( Connection& connection ); - - Node& createNode( std::unique_ptr&& dataModel ); - - Node& importNode( std::unique_ptr&& dataModel ); - - Node& restoreNode( QJsonObject const& nodeJson ); - - void removeNode( Node& node ); - - DataModelRegistry& registry() const; - - void setRegistry( std::shared_ptr registry ); - - void iterateOverNodes( std::function const& visitor ); - - void iterateOverNodeData( std::function const& visitor ); - - void iterateOverNodeDataDependentOrder( std::function const& visitor ); - - QPointF getNodePosition( Node const& node ) const; - - void setNodePosition( Node& node, QPointF const& pos ) const; - - QSizeF getNodeSize( Node const& node ) const; - - public: - std::unordered_map> const& nodes() const; - - std::unordered_map> const& connections() const; - - std::vector allNodes() const; - - std::vector selectedNodes() const; - - public: - void clearScene(); - - void save() const; - - void load(); - - QByteArray saveToMemory() const; - - void loadFromMemory( const QByteArray& data ); - - void setSceneName( const QString& newname ) { _scenename = newname; } - - QString getSceneName() const { return _scenename; } - - Q_SIGNALS: - - /** - * @brief Node has been created but not on the scene yet. - * @see nodePlaced() - */ - void nodeCreated( Node& n ); - - /** - * @brief Node has been added to the scene. - * @details Connect to this signal if need a correct position of node. - * @see nodeCreated() - */ - void nodePlaced( Node& n ); - - void nodeDeleted( Node& n ); - - void connectionCreated( Connection const& c ); - void connectionDeleted( Connection const& c ); - - void nodeMoved( Node& n, const QPointF& newLocation ); - - void nodeDoubleClicked( Node& n ); - - void connectionHovered( Connection& c, QPoint screenPos ); - - void nodeHovered( Node& n, QPoint screenPos ); - - void connectionHoverLeft( Connection& c ); - - void nodeHoverLeft( Node& n ); - - void nodeContextMenu( Node& n, const QPointF& pos ); - - private: - using SharedConnection = std::shared_ptr; - using UniqueNode = std::unique_ptr; - - // DO NOT reorder this member to go after the others. - // This should outlive all the connections and nodes of - // the graph, so that nodes can potentially have pointers into it, - // which is why it comes first in the class. - std::shared_ptr _registry; - - std::unordered_map _connections; - std::unordered_map _nodes; - - // name of the scene - QString _scenename { "" }; - private Q_SLOTS: - - void setupConnectionSignals( Connection const& c ); - - void sendConnectionCreatedToNodes( Connection const& c ); - void sendConnectionDeletedToNodes( Connection const& c ); -}; - -Node* locateNodeAt( QPointF scenePoint, FlowScene& scene, QTransform const& viewTransform ); -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp deleted file mode 100644 index 79a23b35a30..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowView.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" - -namespace QtNodes { - -class FlowScene; - -class NODE_EDITOR_PUBLIC FlowView : public QGraphicsView -{ - Q_OBJECT - public: - FlowView( QWidget* parent = Q_NULLPTR ); - FlowView( FlowScene* scene, QWidget* parent = Q_NULLPTR ); - - FlowView( const FlowView& ) = delete; - FlowView operator=( const FlowView& ) = delete; - - QAction* clearSelectionAction() const; - - QAction* deleteSelectionAction() const; - - void setScene( FlowScene* scene ); - - public Q_SLOTS: - - void scaleUp(); - - void scaleDown(); - - void deleteSelectedNodes(); - - protected: - void contextMenuEvent( QContextMenuEvent* event ) override; - - void wheelEvent( QWheelEvent* event ) override; - - void keyPressEvent( QKeyEvent* event ) override; - - void keyReleaseEvent( QKeyEvent* event ) override; - - void mousePressEvent( QMouseEvent* event ) override; - - void mouseMoveEvent( QMouseEvent* event ) override; - - void drawBackground( QPainter* painter, const QRectF& r ) override; - - void showEvent( QShowEvent* event ) override; - - protected: - FlowScene* scene(); - - private: - QAction* _clearSelectionAction; - QAction* _deleteSelectionAction; - - QPointF _clickPos; - - FlowScene* _scene; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp deleted file mode 100644 index e98dbfda7ce..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/FlowViewStyle.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" -#include "Style.hpp" - -namespace QtNodes { - -class NODE_EDITOR_PUBLIC FlowViewStyle : public Style -{ - public: - FlowViewStyle(); - - FlowViewStyle( QString jsonText ); - - public: - static void setStyle( QString jsonText ); - - private: - void loadJsonText( QString jsonText ) override; - - void loadJsonFile( QString fileName ) override; - - void loadJsonFromByteArray( QByteArray const& byteArray ) override; - - public: - QColor BackgroundColor; - QColor FineGridColor; - QColor CoarseGridColor; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp deleted file mode 100644 index f766be6b78f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Node.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include "PortType.hpp" - -#include "ConnectionGraphicsObject.hpp" -#include "Export.hpp" -#include "NodeData.hpp" -#include "NodeGeometry.hpp" -#include "NodeGraphicsObject.hpp" -#include "NodeState.hpp" -#include "Serializable.hpp" -#include "memory.hpp" - -namespace QtNodes { - -class Connection; -class ConnectionState; -class NodeGraphicsObject; -class NodeDataModel; - -class NODE_EDITOR_PUBLIC Node : public QObject, public Serializable -{ - Q_OBJECT - - public: - /// NodeDataModel should be an rvalue and is moved into the Node - Node( std::unique_ptr&& dataModel ); - - virtual ~Node(); - - public: - QJsonObject save() const override; - - void restore( QJsonObject const& json ) override; - - void import(); - - public: - QUuid id() const; - - void reactToPossibleConnection( PortType, NodeDataType const&, QPointF const& scenePoint ); - - void resetReactionToConnection(); - - public: - NodeGraphicsObject const& nodeGraphicsObject() const; - - NodeGraphicsObject& nodeGraphicsObject(); - - void setGraphicsObject( std::unique_ptr&& graphics ); - - NodeGeometry& nodeGeometry(); - - NodeGeometry const& nodeGeometry() const; - - NodeState const& nodeState() const; - - NodeState& nodeState(); - - NodeDataModel* nodeDataModel() const; - - public Q_SLOTS: // data propagation - - /// Propagates incoming data to the underlying model. - void propagateData( std::shared_ptr nodeData, PortIndex inPortIndex ) const; - - /// Fetches data from model's OUT #index port - /// and propagates it to the connection - void onDataUpdated( PortIndex index ); - - /// update the graphic part if the size of the embeddedwidget changes - void onNodeSizeUpdated(); - - private: - // addressing - friend class FlowScene; - QUuid _uid; - - // data - - std::unique_ptr _nodeDataModel; - - NodeState _nodeState; - - // painting - - NodeGeometry _nodeGeometry; - - std::unique_ptr _nodeGraphicsObject; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp deleted file mode 100644 index b1176f9fed2..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeData.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" - -namespace QtNodes { - -struct NodeDataType { - QString id; - QString name; -}; - -/// Class represents data transferred between nodes. -/// @param type is used for comparing the types -/// The actual data is stored in subtypes -class NODE_EDITOR_PUBLIC NodeData -{ - public: - virtual ~NodeData() = default; - - virtual bool sameType( NodeData const& nodeData ) const { - return ( this->type().id == nodeData.type().id ); - } - - /// Type for inner use - virtual NodeDataType type() const = 0; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp deleted file mode 100644 index 4d8d021df1a..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeDataModel.hpp +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" -#include "NodeData.hpp" -#include "NodeGeometry.hpp" -#include "NodePainterDelegate.hpp" -#include "NodeStyle.hpp" -#include "PortType.hpp" -#include "Serializable.hpp" -#include "memory.hpp" - -namespace QtNodes { - -enum class NodeValidationState { Valid, Warning, Error }; - -class Connection; - -class StyleCollection; - -class NODE_EDITOR_PUBLIC NodeDataModel : public QObject, public Serializable -{ - Q_OBJECT - - public: - NodeDataModel(); - - virtual ~NodeDataModel() = default; - - /// Caption is used in GUI - virtual QString caption() const = 0; - - /// It is possible to hide caption in GUI - virtual bool captionVisible() const { return true; } - - /// Port caption is used in GUI to label individual ports - virtual QString portCaption( PortType, PortIndex ) const { return QString(); } - - /// It is possible to hide port caption in GUI - virtual bool portCaptionVisible( PortType, PortIndex ) const { return false; } - - /// Name makes this model unique - virtual QString name() const = 0; - - /// Added to allow node ownership management from outside the editor - /// The uuid of the node - virtual QString uuid() const = 0; //{return "";} - - virtual bool isDeletable() { return true; } - - virtual void updateState() {} - - virtual void addMetaData( QJsonObject& ) {} - - public: - QJsonObject save() const override; - - public: - virtual unsigned int nPorts( PortType portType ) const = 0; - - virtual NodeDataType dataType( PortType portType, PortIndex portIndex ) const = 0; - - public: - enum class ConnectionPolicy { - One, - Many, - }; - - virtual ConnectionPolicy portOutConnectionPolicy( PortIndex ) const { - return ConnectionPolicy::Many; - } - - NodeStyle const& nodeStyle() const; - - void setNodeStyle( NodeStyle const& style ); - - public: - /// Triggers the algorithm - virtual void setInData( std::shared_ptr nodeData, PortIndex port ) = 0; - - virtual std::shared_ptr outData( PortIndex port ) = 0; - - virtual QWidget* embeddedWidget() = 0; - - virtual bool resizable() const { return false; } - - virtual NodeValidationState validationState() const { return NodeValidationState::Valid; } - - virtual QString validationMessage() const { return QString( "" ); } - - virtual NodePainterDelegate* painterDelegate() const { return nullptr; } - - public Q_SLOTS: - - virtual void inputConnectionCreated( Connection const& ) {} - - virtual void inputConnectionDeleted( Connection const& ) {} - - virtual void outputConnectionCreated( Connection const& ) {} - - virtual void outputConnectionDeleted( Connection const& ) {} - - Q_SIGNALS: - - void dataUpdated( PortIndex index ); - - void dataInvalidated( PortIndex index ); - - void computingStarted(); - - void computingFinished(); - - void embeddedWidgetSizeUpdated(); - - private: - NodeStyle _nodeStyle; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp deleted file mode 100644 index 7c8800b5053..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGeometry.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "Export.hpp" -#include "PortType.hpp" -#include "memory.hpp" - -namespace QtNodes { - -class NodeState; -class NodeDataModel; -class Node; - -class NODE_EDITOR_PUBLIC NodeGeometry -{ - public: - NodeGeometry( std::unique_ptr const& dataModel ); - - public: - unsigned int height() const { return _height; } - - void setHeight( unsigned int h ) { _height = h; } - - unsigned int width() const { return _width; } - - void setWidth( unsigned int w ) { _width = w; } - - unsigned int entryHeight() const { return _entryHeight; } - void setEntryHeight( unsigned int h ) { _entryHeight = h; } - - unsigned int entryWidth() const { return _entryWidth; } - - void setEntryWidth( unsigned int w ) { _entryWidth = w; } - - unsigned int spacing() const { return _spacing; } - - void setSpacing( unsigned int s ) { _spacing = s; } - - bool hovered() const { return _hovered; } - - void setHovered( unsigned int h ) { _hovered = h; } - - unsigned int nSources() const; - - unsigned int nSinks() const; - - QPointF const& draggingPos() const { return _draggingPos; } - - void setDraggingPosition( QPointF const& pos ) { _draggingPos = pos; } - - public: - QRectF entryBoundingRect() const; - - QRectF boundingRect() const; - - /// Updates size unconditionally - void recalculateSize() const; - - /// Updates size if the QFontMetrics is changed - void recalculateSize( QFont const& font ) const; - - // TODO removed default QTransform() - QPointF portScenePosition( PortIndex index, - PortType portType, - QTransform const& t = QTransform() ) const; - - PortIndex checkHitScenePoint( PortType portType, - QPointF point, - QTransform const& t = QTransform() ) const; - - QRect resizeRect() const; - - /// Returns the position of a widget on the Node surface - QPointF widgetPosition() const; - - /// Returns the maximum height a widget can be without causing the node to grow. - int equivalentWidgetHeight() const; - - unsigned int validationHeight() const; - - unsigned int validationWidth() const; - - static QPointF calculateNodePositionBetweenNodePorts( PortIndex targetPortIndex, - PortType targetPort, - Node* targetNode, - PortIndex sourcePortIndex, - PortType sourcePort, - Node* sourceNode, - Node& newNode ); - - private: - unsigned int captionHeight() const; - - unsigned int captionWidth() const; - - unsigned int portWidth( PortType portType ) const; - - private: - // some variables are mutable because - // we need to change drawing metrics - // corresponding to fontMetrics - // but this doesn't change constness of Node - - mutable unsigned int _width; - mutable unsigned int _height; - unsigned int _entryWidth; - mutable unsigned int _inputPortWidth; - mutable unsigned int _outputPortWidth; - mutable unsigned int _entryHeight; - unsigned int _spacing; - - bool _hovered; - - unsigned int _nSources; - unsigned int _nSinks; - - QPointF _draggingPos; - - std::unique_ptr const& _dataModel; - - mutable QFontMetrics _fontMetrics; - mutable QFontMetrics _boldFontMetrics; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp deleted file mode 100644 index 0c25331890b..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeGraphicsObject.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -#include -#include - -#include "Connection.hpp" - -#include "NodeGeometry.hpp" -#include "NodeState.hpp" - -class QGraphicsProxyWidget; - -namespace QtNodes { - -class FlowScene; -class FlowItemEntry; - -/// Class reacts on GUI events, mouse clicks and -/// forwards painting operation. -class NodeGraphicsObject : public QGraphicsObject -{ - Q_OBJECT - - public: - NodeGraphicsObject( FlowScene& scene, Node& node ); - - virtual ~NodeGraphicsObject(); - - Node& node(); - - Node const& node() const; - - QRectF boundingRect() const override; - - void setGeometryChanged(); - - /// Visits all attached connections and corrects - /// their corresponding end points. - void moveConnections() const; - - enum { Type = UserType + 1 }; - - int type() const override { return Type; } - - void lock( bool locked ); - - protected: - void paint( QPainter* painter, - QStyleOptionGraphicsItem const* option, - QWidget* widget = 0 ) override; - - QVariant itemChange( GraphicsItemChange change, const QVariant& value ) override; - - void mousePressEvent( QGraphicsSceneMouseEvent* event ) override; - - void mouseMoveEvent( QGraphicsSceneMouseEvent* event ) override; - - void mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) override; - - void hoverEnterEvent( QGraphicsSceneHoverEvent* event ) override; - - void hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) override; - - void hoverMoveEvent( QGraphicsSceneHoverEvent* ) override; - - void mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) override; - - void contextMenuEvent( QGraphicsSceneContextMenuEvent* event ) override; - - private: - void embedQWidget(); - - private: - FlowScene& _scene; - - Node& _node; - - bool _locked; - - // either nullptr or owned by parent QGraphicsItem - QGraphicsProxyWidget* _proxyWidget; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp deleted file mode 100644 index 09e7513d96f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodePainterDelegate.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" -#include "NodeDataModel.hpp" -#include "NodeGeometry.hpp" - -namespace QtNodes { - -/// Class to allow for custom painting -class NODE_EDITOR_PUBLIC NodePainterDelegate -{ - - public: - virtual ~NodePainterDelegate() = default; - - virtual void - paint( QPainter* painter, NodeGeometry const& geom, NodeDataModel const* model ) = 0; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp deleted file mode 100644 index e9e635c9b2f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeState.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include "Export.hpp" - -#include "NodeData.hpp" -#include "PortType.hpp" -#include "memory.hpp" - -namespace QtNodes { - -class Connection; -class NodeDataModel; - -/// Contains vectors of connected input and output connections. -/// Stores bool for reacting on hovering connections -class NODE_EDITOR_PUBLIC NodeState -{ - public: - enum ReactToConnectionState { REACTING, NOT_REACTING }; - - public: - NodeState( std::unique_ptr const& model ); - - public: - using ConnectionPtrSet = std::unordered_map; - - /// Returns vector of connections ID. - /// Some of them can be empty (null) - std::vector const& getEntries( PortType ) const; - - std::vector& getEntries( PortType ); - - ConnectionPtrSet connections( PortType portType, PortIndex portIndex ) const; - - void setConnection( PortType portType, PortIndex portIndex, Connection& connection ); - - void eraseConnection( PortType portType, PortIndex portIndex, QUuid id ); - - ReactToConnectionState reaction() const; - - PortType reactingPortType() const; - - NodeDataType reactingDataType() const; - - void setReaction( ReactToConnectionState reaction, - PortType reactingPortType = PortType::None, - - NodeDataType reactingDataType = NodeDataType() ); - - bool isReacting() const; - - void setResizing( bool resizing ); - - bool resizing() const; - - private: - std::vector _inConnections; - std::vector _outConnections; - - ReactToConnectionState _reaction; - PortType _reactingPortType; - NodeDataType _reactingDataType; - - bool _resizing; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp deleted file mode 100644 index 43c3866163f..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/NodeStyle.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include - -#include "Export.hpp" -#include "Style.hpp" - -namespace QtNodes { - -class NODE_EDITOR_PUBLIC NodeStyle : public Style -{ - public: - NodeStyle(); - - NodeStyle( QString jsonText ); - - public: - static void setNodeStyle( QString jsonText ); - - private: - void loadJsonText( QString jsonText ) override; - - void loadJsonFile( QString fileName ) override; - - void loadJsonFromByteArray( QByteArray const& byteArray ) override; - - public: - QColor NormalBoundaryColor; - QColor SelectedBoundaryColor; - QColor GradientColor0; - QColor GradientColor1; - QColor GradientColor2; - QColor GradientColor3; - QColor ShadowColor; - QColor FontColor; - QColor FontColorFaded; - - QColor ConnectionPointColor; - QColor FilledConnectionPointColor; - - QColor WarningColor; - QColor ErrorColor; - - float PenWidth; - float HoveredPenWidth; - - float ConnectionPointDiameter; - - float Opacity; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp deleted file mode 100644 index 423894a1f4a..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/OperatingSystem.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#if defined( __CYGWIN__ ) || defined( __CYGWIN32__ ) -# define NODE_EDITOR_PLATFORM "Cygwin" -# define NODE_EDITOR_PLATFORM_CYGWIN -# define NODE_EDITOR_PLATFORM_UNIX -# define NODE_EDITOR_PLATFORM_WINDOWS -#elif defined( _WIN16 ) || defined( _WIN32 ) || defined( _WIN64 ) || defined( __WIN32__ ) || \ - defined( __TOS_WIN__ ) || defined( __WINDOWS__ ) -# define NODE_EDITOR_PLATFORM "Windows" -# define NODE_EDITOR_PLATFORM_WINDOWS -#elif defined( macintosh ) || defined( Macintosh ) || defined( __TOS_MACOS__ ) || \ - ( defined( __APPLE__ ) && defined( __MACH__ ) ) -# define NODE_EDITOR_PLATFORM "Mac" -# define NODE_EDITOR_PLATFORM_MAC -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( linux ) || defined( __linux ) || defined( __linux__ ) || defined( __TOS_LINUX__ ) -# define NODE_EDITOR_PLATFORM "Linux" -# define NODE_EDITOR_PLATFORM_LINUX -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( __FreeBSD__ ) || defined( __OpenBSD__ ) || defined( __NetBSD__ ) || \ - defined( __bsdi__ ) || defined( __DragonFly__ ) -# define NODE_EDITOR_PLATFORM "BSD" -# define NODE_EDITOR_PLATFORM_BSD -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( sun ) || defined( __sun ) -# define NODE_EDITOR_PLATFORM "Solaris" -# define NODE_EDITOR_PLATFORM_SOLARIS -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( _AIX ) || defined( __TOS_AIX__ ) -# define NODE_EDITOR_PLATFORM "AIX" -# define NODE_EDITOR_PLATFORM_AIX -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( hpux ) || defined( _hpux ) || defined( __hpux ) -# define NODE_EDITOR_PLATFORM "HPUX" -# define NODE_EDITOR_PLATFORM_HPUX -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( __QNX__ ) -# define NODE_EDITOR_PLATFORM "QNX" -# define NODE_EDITOR_PLATFORM_QNX -# define NODE_EDITOR_PLATFORM_UNIX -#elif defined( unix ) || defined( __unix ) || defined( __unix__ ) -# define NODE_EDITOR_PLATFORM "Unix" -# define NODE_EDITOR_PLATFORM_UNIX -#endif - -#ifndef NODE_EDITOR_PLATFORM -# error "Current platform is not supported." -#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp deleted file mode 100644 index a39bcfddcfb..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/PortType.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include - -namespace QtNodes { - -enum class PortType { None, In, Out }; - -static const int INVALID = -1; - -using PortIndex = int; - -struct Port { - PortType type; - - PortIndex index; - - Port() : type( PortType::None ), index( INVALID ) {} - - Port( PortType t, PortIndex i ) : type( t ), index( i ) {} - - bool indexIsValid() { return index != INVALID; } - - bool portTypeIsValid() { return type != PortType::None; } -}; - -// using PortAddress = std::pair; - -inline PortType oppositePort( PortType port ) { - PortType result = PortType::None; - - switch ( port ) { - case PortType::In: - result = PortType::Out; - break; - - case PortType::Out: - result = PortType::In; - break; - - default: - break; - } - - return result; -} -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp deleted file mode 100644 index 191e882b3d3..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QStringStdHash.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include - -#if ( QT_VERSION < QT_VERSION_CHECK( 5, 14, 0 ) ) - -// As of 5.14 there is a specialization std::hash - -# include - -# include -# include - -namespace std { -template <> -struct hash { - inline std::size_t operator()( QString const& s ) const { return qHash( s ); } -}; -} // namespace std - -#endif diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp deleted file mode 100644 index ee33f3ff6a3..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/QUuidStdHash.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace std { -template <> -struct hash { - inline std::size_t operator()( QUuid const& uid ) const { return qHash( uid ); } -}; -} // namespace std diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp deleted file mode 100644 index 1058b5d6ac7..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Serializable.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include - -namespace QtNodes { - -class Serializable -{ - public: - virtual ~Serializable() = default; - - virtual QJsonObject save() const = 0; - - virtual void restore( QJsonObject const& /*p*/ ) {} -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp deleted file mode 100644 index 6979b1e759b..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/Style.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -namespace QtNodes { - -class Style -{ - public: - virtual ~Style() = default; - - private: - virtual void loadJsonText( QString jsonText ) = 0; - - virtual void loadJsonFile( QString fileName ) = 0; - - virtual void loadJsonFromByteArray( QByteArray const& byteArray ) = 0; -}; - -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp deleted file mode 100644 index b56f159bce7..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/StyleCollection.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include "ConnectionStyle.hpp" -#include "Export.hpp" -#include "FlowViewStyle.hpp" -#include "NodeStyle.hpp" - -namespace QtNodes { - -class NODE_EDITOR_PUBLIC StyleCollection -{ - public: - static NodeStyle const& nodeStyle(); - - static ConnectionStyle const& connectionStyle(); - - static FlowViewStyle const& flowViewStyle(); - - public: - static void setNodeStyle( NodeStyle ); - - static void setConnectionStyle( ConnectionStyle ); - - static void setFlowViewStyle( FlowViewStyle ); - - private: - StyleCollection() = default; - - StyleCollection( StyleCollection const& ) = delete; - - StyleCollection& operator=( StyleCollection const& ) = delete; - - static StyleCollection& instance(); - - private: - NodeStyle _nodeStyle; - - ConnectionStyle _connectionStyle; - - FlowViewStyle _flowViewStyle; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp deleted file mode 100644 index 740d178bd11..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/TypeConverter.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "NodeData.hpp" -#include "memory.hpp" - -#include - -namespace QtNodes { - -using SharedNodeData = std::shared_ptr; - -// a function taking in NodeData and returning NodeData -using TypeConverter = std::function; - -// data-type-in, data-type-out -using TypeConverterId = std::pair; - -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp b/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp deleted file mode 100644 index 7e0d5bf28b2..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/include/nodes/internal/memory.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace QtNodes { -namespace detail { -#if ( !defined( _MSC_VER ) && ( __cplusplus < 201300 ) ) || \ - ( defined( _MSC_VER ) && ( _MSC_VER < 1800 ) ) -//_MSC_VER == 1800 is Visual Studio 2013, which is already somewhat C++14 compilant, -// and it has make_unique in it's standard library implementation -template -std::unique_ptr make_unique( Args&&... args ) { - return std::unique_ptr( new T( std::forward( args )... ) ); -} -#else -template -std::unique_ptr make_unique( Args&&... args ) { - return std::make_unique( std::forward( args )... ); -} -#endif -} // namespace detail -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json b/src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json deleted file mode 100644 index 8375b4a15d4..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/resources/DefaultStyle.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "FlowViewStyle": { - "BackgroundColor": [53, 53, 53], - "FineGridColor": [60, 60, 60], - "CoarseGridColor": [25, 25, 25] - }, - "NodeStyle": { - "NormalBoundaryColor": [255, 255, 255], - "SelectedBoundaryColor": [255, 165, 0], - "GradientColor0": "gray", - "GradientColor1": [80, 80, 80], - "GradientColor2": [64, 64, 64], - "GradientColor3": [58, 58, 58], - "ShadowColor": [20, 20, 20], - "FontColor" : "white", - "FontColorFaded" : "gray", - "ConnectionPointColor": [169, 169, 169], - "FilledConnectionPointColor": "cyan", - "ErrorColor": "red", - "WarningColor": [128, 128, 0], - - "PenWidth": 1.0, - "HoveredPenWidth": 1.5, - - "ConnectionPointDiameter": 8.0, - - "Opacity": 0.8 - }, - "ConnectionStyle": { - "ConstructionColor": "gray", - "NormalColor": "darkcyan", - "SelectedColor": [100, 100, 100], - "SelectedHaloColor": "orange", - "HoveredColor": "lightcyan", - - "LineWidth": 3.0, - "ConstructionLineWidth": 2.0, - "PointDiameter": 10.0, - - "UseDataDefinedColors": false - } -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/resources/convert.png b/src/Dataflow/QtGui/QtNodeEditor/resources/convert.png deleted file mode 100644 index f3797885497d0810382068634de30476db82f535..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10203 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 BsEPmp diff --git a/src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc b/src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc deleted file mode 100644 index 14ef4318183..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/resources/resources.qrc +++ /dev/null @@ -1,6 +0,0 @@ - - - DefaultStyle.json - convert.png - - diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp deleted file mode 100644 index 3425777527e..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/Connection.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#include "Connection.hpp" - -#include -#include - -#include -#include - -#include "FlowScene.hpp" -#include "FlowView.hpp" -#include "Node.hpp" - -#include "NodeDataModel.hpp" -#include "NodeGeometry.hpp" -#include "NodeGraphicsObject.hpp" - -#include "ConnectionGeometry.hpp" -#include "ConnectionGraphicsObject.hpp" -#include "ConnectionState.hpp" - -using QtNodes::Connection; -using QtNodes::ConnectionGeometry; -using QtNodes::ConnectionGraphicsObject; -using QtNodes::ConnectionState; -using QtNodes::Node; -using QtNodes::NodeData; -using QtNodes::NodeDataType; -using QtNodes::PortIndex; -using QtNodes::PortType; -using QtNodes::TypeConverter; - -Connection::Connection( PortType portType, Node& node, PortIndex portIndex ) : - _uid( QUuid::createUuid() ), - _outPortIndex( INVALID ), - _inPortIndex( INVALID ), - _connectionState() { - setNodeToPort( node, portType, portIndex ); - - setRequiredPort( oppositePort( portType ) ); -} - -Connection::Connection( Node& nodeIn, - PortIndex portIndexIn, - Node& nodeOut, - PortIndex portIndexOut, - TypeConverter typeConverter ) : - _uid( QUuid::createUuid() ), - _outNode( &nodeOut ), - _inNode( &nodeIn ), - _outPortIndex( portIndexOut ), - _inPortIndex( portIndexIn ), - _connectionState(), - _converter( std::move( typeConverter ) ) { - setNodeToPort( nodeIn, PortType::In, portIndexIn ); - setNodeToPort( nodeOut, PortType::Out, portIndexOut ); -} - -Connection::~Connection() { - if ( complete() ) { connectionMadeIncomplete( *this ); } - - propagateEmptyData(); - - if ( _inNode ) { _inNode->nodeGraphicsObject().update(); } - - if ( _outNode ) { _outNode->nodeGraphicsObject().update(); } -} - -QJsonObject Connection::save() const { - QJsonObject connectionJson; - - if ( _inNode && _outNode ) { - connectionJson["in_id"] = _inNode->id().toString(); - connectionJson["in_index"] = _inPortIndex; - - connectionJson["out_id"] = _outNode->id().toString(); - connectionJson["out_index"] = _outPortIndex; - - if ( _converter ) { - auto getTypeJson = [this]( PortType type ) { - QJsonObject typeJson; - NodeDataType nodeType = this->dataType( type ); - typeJson["id"] = nodeType.id; - typeJson["name"] = nodeType.name; - - return typeJson; - }; - - QJsonObject converterTypeJson; - - converterTypeJson["in"] = getTypeJson( PortType::In ); - converterTypeJson["out"] = getTypeJson( PortType::Out ); - - connectionJson["converter"] = converterTypeJson; - } - } - - return connectionJson; -} - -QUuid Connection::id() const { - return _uid; -} - -bool Connection::complete() const { - return _inNode != nullptr && _outNode != nullptr; -} - -void Connection::setRequiredPort( PortType dragging ) { - _connectionState.setRequiredPort( dragging ); - - switch ( dragging ) { - case PortType::Out: - _outNode = nullptr; - _outPortIndex = INVALID; - break; - - case PortType::In: - _inNode = nullptr; - _inPortIndex = INVALID; - break; - - default: - break; - } -} - -PortType Connection::requiredPort() const { - return _connectionState.requiredPort(); -} - -void Connection::setGraphicsObject( std::unique_ptr&& graphics ) { - _connectionGraphicsObject = std::move( graphics ); - - // This function is only called when the ConnectionGraphicsObject - // is newly created. At this moment both end coordinates are (0, 0) - // in Connection G.O. coordinates. The position of the whole - // Connection G. O. in scene coordinate system is also (0, 0). - // By moving the whole object to the Node Port position - // we position both connection ends correctly. - - if ( requiredPort() != PortType::None ) { - - PortType attachedPort = oppositePort( requiredPort() ); - - PortIndex attachedPortIndex = getPortIndex( attachedPort ); - - auto node = getNode( attachedPort ); - - QTransform nodeSceneTransform = node->nodeGraphicsObject().sceneTransform(); - - QPointF pos = node->nodeGeometry().portScenePosition( - attachedPortIndex, attachedPort, nodeSceneTransform ); - - _connectionGraphicsObject->setPos( pos ); - } - - _connectionGraphicsObject->move(); -} - -PortIndex Connection::getPortIndex( PortType portType ) const { - PortIndex result = INVALID; - - switch ( portType ) { - case PortType::In: - result = _inPortIndex; - break; - - case PortType::Out: - result = _outPortIndex; - - break; - - default: - break; - } - - return result; -} - -void Connection::setNodeToPort( Node& node, PortType portType, PortIndex portIndex ) { - bool wasIncomplete = !complete(); - - auto& nodeWeak = getNode( portType ); - - nodeWeak = &node; - - if ( portType == PortType::Out ) - _outPortIndex = portIndex; - else - _inPortIndex = portIndex; - - _connectionState.setNoRequiredPort(); - - updated( *this ); - if ( complete() && wasIncomplete ) { connectionCompleted( *this ); } -} - -void Connection::removeFromNodes() const { - if ( _inNode ) _inNode->nodeState().eraseConnection( PortType::In, _inPortIndex, id() ); - - if ( _outNode ) _outNode->nodeState().eraseConnection( PortType::Out, _outPortIndex, id() ); -} - -ConnectionGraphicsObject& Connection::getConnectionGraphicsObject() const { - return *_connectionGraphicsObject; -} - -ConnectionState& Connection::connectionState() { - return _connectionState; -} - -ConnectionState const& Connection::connectionState() const { - return _connectionState; -} - -ConnectionGeometry& Connection::connectionGeometry() { - return _connectionGeometry; -} - -ConnectionGeometry const& Connection::connectionGeometry() const { - return _connectionGeometry; -} - -Node* Connection::getNode( PortType portType ) const { - switch ( portType ) { - case PortType::In: - return _inNode; - break; - - case PortType::Out: - return _outNode; - break; - - default: - // not possible - break; - } - return nullptr; -} - -Node*& Connection::getNode( PortType portType ) { - switch ( portType ) { - case PortType::In: - return _inNode; - break; - - case PortType::Out: - return _outNode; - break; - - default: - // not possible - break; - } - Q_UNREACHABLE(); -} - -void Connection::clearNode( PortType portType ) { - if ( complete() ) { connectionMadeIncomplete( *this ); } - - getNode( portType ) = nullptr; - - if ( portType == PortType::In ) - _inPortIndex = INVALID; - else - _outPortIndex = INVALID; -} - -NodeDataType Connection::dataType( PortType portType ) const { - if ( _inNode && _outNode ) { - auto const& model = - ( portType == PortType::In ) ? _inNode->nodeDataModel() : _outNode->nodeDataModel(); - PortIndex index = ( portType == PortType::In ) ? _inPortIndex : _outPortIndex; - - return model->dataType( portType, index ); - } - else { - Node* validNode; - PortIndex index = INVALID; - - if ( ( validNode = _inNode ) ) { - index = _inPortIndex; - portType = PortType::In; - } - else if ( ( validNode = _outNode ) ) { - index = _outPortIndex; - portType = PortType::Out; - } - - if ( validNode ) { - auto const& model = validNode->nodeDataModel(); - - return model->dataType( portType, index ); - } - } - - Q_UNREACHABLE(); -} - -void Connection::setTypeConverter( TypeConverter converter ) { - _converter = std::move( converter ); -} - -void Connection::propagateData( std::shared_ptr nodeData ) const { - if ( _inNode ) { - if ( _converter ) { nodeData = _converter( nodeData ); } - - _inNode->propagateData( nodeData, _inPortIndex ); - } -} - -void Connection::propagateEmptyData() const { - std::shared_ptr emptyData; - - propagateData( emptyData ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp deleted file mode 100644 index 7d2922494a8..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "ConnectionBlurEffect.hpp" - -#include "ConnectionGraphicsObject.hpp" -#include "ConnectionPainter.hpp" - -using QtNodes::ConnectionBlurEffect; -using QtNodes::ConnectionGraphicsObject; - -ConnectionBlurEffect::ConnectionBlurEffect( ConnectionGraphicsObject* ) { - // -} - -void ConnectionBlurEffect::draw( QPainter* painter ) { - QGraphicsBlurEffect::draw( painter ); - - // ConnectionPainter::paint(painter, - //_object->connectionGeometry(), - //_object->connectionState()); - - //_item->paint(painter, nullptr, nullptr); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp deleted file mode 100644 index f730a2a50b4..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionBlurEffect.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include - -namespace QtNodes { - -class ConnectionGraphicsObject; - -class ConnectionBlurEffect : public QGraphicsBlurEffect -{ - - public: - ConnectionBlurEffect( ConnectionGraphicsObject* item ); - - void draw( QPainter* painter ) override; - - private: -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp deleted file mode 100644 index 2212abc988d..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGeometry.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include "ConnectionGeometry.hpp" - -#include - -#include "StyleCollection.hpp" - -using QtNodes::ConnectionGeometry; -using QtNodes::PortType; - -ConnectionGeometry::ConnectionGeometry() : - _in( 0, 0 ), - _out( 0, 0 ) - //, _animationPhase(0) - , - _lineWidth( 3.0 ), - _hovered( false ) {} - -QPointF const& ConnectionGeometry::getEndPoint( PortType portType ) const { - Q_ASSERT( portType != PortType::None ); - - return ( portType == PortType::Out ? _out : _in ); -} - -void ConnectionGeometry::setEndPoint( PortType portType, QPointF const& point ) { - switch ( portType ) { - case PortType::Out: - _out = point; - break; - - case PortType::In: - _in = point; - break; - - default: - break; - } -} - -void ConnectionGeometry::moveEndPoint( PortType portType, QPointF const& offset ) { - switch ( portType ) { - case PortType::Out: - _out += offset; - break; - - case PortType::In: - _in += offset; - break; - - default: - break; - } -} - -QRectF ConnectionGeometry::boundingRect() const { - auto points = pointsC1C2(); - - QRectF basicRect = QRectF( _out, _in ).normalized(); - - QRectF c1c2Rect = QRectF( points.first, points.second ).normalized(); - - auto const& connectionStyle = StyleCollection::connectionStyle(); - - float const diam = connectionStyle.pointDiameter(); - - QRectF commonRect = basicRect.united( c1c2Rect ); - - QPointF const cornerOffset( diam, diam ); - - commonRect.setTopLeft( commonRect.topLeft() - cornerOffset ); - commonRect.setBottomRight( commonRect.bottomRight() + 2 * cornerOffset ); - - return commonRect; -} - -std::pair ConnectionGeometry::pointsC1C2() const { - const double defaultOffset = 200; - - double xDistance = _in.x() - _out.x(); - - double horizontalOffset = qMin( defaultOffset, std::abs( xDistance ) ); - - double verticalOffset = 0; - - double ratioX = 0.5; - - if ( xDistance <= 0 ) { - double yDistance = _in.y() - _out.y() + 20; - - double vector = yDistance < 0 ? -1.0 : 1.0; - - verticalOffset = qMin( defaultOffset, std::abs( yDistance ) ) * vector; - - ratioX = 1.0; - } - - horizontalOffset *= ratioX; - - QPointF c1( _out.x() + horizontalOffset, _out.y() + verticalOffset ); - - QPointF c2( _in.x() - horizontalOffset, _in.y() - verticalOffset ); - - return std::make_pair( c1, c2 ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp deleted file mode 100644 index cbb0b71d3c6..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionGraphicsObject.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "ConnectionGraphicsObject.hpp" - -#include -#include -#include -#include -#include - -#include "FlowScene.hpp" - -#include "Connection.hpp" -#include "ConnectionBlurEffect.hpp" -#include "ConnectionGeometry.hpp" -#include "ConnectionPainter.hpp" -#include "ConnectionState.hpp" - -#include "NodeGraphicsObject.hpp" - -#include "NodeConnectionInteraction.hpp" - -#include "Node.hpp" - -using QtNodes::Connection; -using QtNodes::ConnectionGraphicsObject; -using QtNodes::FlowScene; - -ConnectionGraphicsObject::ConnectionGraphicsObject( FlowScene& scene, Connection& connection ) : - _scene( scene ), _connection( connection ) { - _scene.addItem( this ); - - setFlag( QGraphicsItem::ItemIsMovable, true ); - setFlag( QGraphicsItem::ItemIsFocusable, true ); - setFlag( QGraphicsItem::ItemIsSelectable, true ); - - setAcceptHoverEvents( true ); - - // addGraphicsEffect(); - - setZValue( -1.0 ); -} - -ConnectionGraphicsObject::~ConnectionGraphicsObject() { - _scene.removeItem( this ); -} - -QtNodes::Connection& ConnectionGraphicsObject::connection() { - return _connection; -} - -QRectF ConnectionGraphicsObject::boundingRect() const { - return _connection.connectionGeometry().boundingRect(); -} - -QPainterPath ConnectionGraphicsObject::shape() const { -#ifdef DEBUG_DRAWING - - // QPainterPath path; - - // path.addRect(boundingRect()); - // return path; - -#else - auto const& geom = _connection.connectionGeometry(); - - return ConnectionPainter::getPainterStroke( geom ); - -#endif -} - -void ConnectionGraphicsObject::setGeometryChanged() { - prepareGeometryChange(); -} - -void ConnectionGraphicsObject::move() { - for ( PortType portType : { PortType::In, PortType::Out } ) { - if ( auto node = _connection.getNode( portType ) ) { - auto const& nodeGraphics = node->nodeGraphicsObject(); - - auto const& nodeGeom = node->nodeGeometry(); - - QPointF scenePos = nodeGeom.portScenePosition( - _connection.getPortIndex( portType ), portType, nodeGraphics.sceneTransform() ); - - QTransform sceneTransform = this->sceneTransform(); - - QPointF connectionPos = sceneTransform.inverted().map( scenePos ); - - _connection.connectionGeometry().setEndPoint( portType, connectionPos ); - - _connection.getConnectionGraphicsObject().setGeometryChanged(); - _connection.getConnectionGraphicsObject().update(); - } - } -} - -void ConnectionGraphicsObject::lock( bool locked ) { - setFlag( QGraphicsItem::ItemIsMovable, !locked ); - setFlag( QGraphicsItem::ItemIsFocusable, !locked ); - setFlag( QGraphicsItem::ItemIsSelectable, !locked ); -} - -void ConnectionGraphicsObject::paint( QPainter* painter, - QStyleOptionGraphicsItem const* option, - QWidget* ) { - painter->setClipRect( option->exposedRect ); - - ConnectionPainter::paint( painter, _connection ); -} - -void ConnectionGraphicsObject::mousePressEvent( QGraphicsSceneMouseEvent* event ) { - QGraphicsItem::mousePressEvent( event ); - // event->ignore(); -} - -void ConnectionGraphicsObject::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) { - prepareGeometryChange(); - - auto view = static_cast( event->widget() ); - auto node = locateNodeAt( event->scenePos(), _scene, view->transform() ); - - auto& state = _connection.connectionState(); - - state.interactWithNode( node ); - if ( node ) { - node->reactToPossibleConnection( - state.requiredPort(), - _connection.dataType( oppositePort( state.requiredPort() ) ), - event->scenePos() ); - } - - //------------------- - - QPointF offset = event->pos() - event->lastPos(); - - auto requiredPort = _connection.requiredPort(); - - if ( requiredPort != PortType::None ) { - _connection.connectionGeometry().moveEndPoint( requiredPort, offset ); - } - - //------------------- - - update(); - - event->accept(); -} - -void ConnectionGraphicsObject::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) { - ungrabMouse(); - event->accept(); - auto node = locateNodeAt( event->scenePos(), _scene, _scene.views()[0]->transform() ); - - if ( node ) { - NodeConnectionInteraction interaction( *node, _connection, _scene ); - if ( interaction.tryConnect() ) { node->resetReactionToConnection(); } - } - - if ( _connection.connectionState().requiresPort() ) { _scene.deleteConnection( _connection ); } -} - -void ConnectionGraphicsObject::hoverEnterEvent( QGraphicsSceneHoverEvent* event ) { - _connection.connectionGeometry().setHovered( true ); - - update(); - _scene.connectionHovered( connection(), event->screenPos() ); - event->accept(); -} - -void ConnectionGraphicsObject::hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) { - _connection.connectionGeometry().setHovered( false ); - - update(); - _scene.connectionHoverLeft( connection() ); - event->accept(); -} - -void ConnectionGraphicsObject::addGraphicsEffect() { - auto effect = new QGraphicsBlurEffect; - - effect->setBlurRadius( 5 ); - setGraphicsEffect( effect ); - - // auto effect = new QGraphicsDropShadowEffect; - // auto effect = new ConnectionBlurEffect(this); - // effect->setOffset(4, 4); - // effect->setColor(QColor(Qt::gray).darker(800)); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp deleted file mode 100644 index 263c2e87e84..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include "ConnectionPainter.hpp" - -#include - -#include "Connection.hpp" -#include "ConnectionGeometry.hpp" -#include "ConnectionGraphicsObject.hpp" -#include "ConnectionState.hpp" - -#include "NodeData.hpp" - -#include "StyleCollection.hpp" - -using QtNodes::Connection; -using QtNodes::ConnectionGeometry; -using QtNodes::ConnectionPainter; - -static QPainterPath cubicPath( ConnectionGeometry const& geom ) { - QPointF const& source = geom.source(); - QPointF const& sink = geom.sink(); - - auto c1c2 = geom.pointsC1C2(); - - // cubic spline - QPainterPath cubic( source ); - - cubic.cubicTo( c1c2.first, c1c2.second, sink ); - - return cubic; -} - -QPainterPath ConnectionPainter::getPainterStroke( ConnectionGeometry const& geom ) { - auto cubic = cubicPath( geom ); - - QPointF const& source = geom.source(); - QPainterPath result( source ); - - unsigned segments = 20; - - for ( auto i = 0ul; i < segments; ++i ) { - double ratio = double( i + 1 ) / segments; - result.lineTo( cubic.pointAtPercent( ratio ) ); - } - - QPainterPathStroker stroker; - stroker.setWidth( 10.0 ); - - return stroker.createStroke( result ); -} - -#ifdef NODE_DEBUG_DRAWING -static void debugDrawing( QPainter* painter, Connection const& connection ) { - Q_UNUSED( painter ); - Q_UNUSED( connection ); - ConnectionGeometry const& geom = connection.connectionGeometry(); - - { - QPointF const& source = geom.source(); - QPointF const& sink = geom.sink(); - - auto points = geom.pointsC1C2(); - - painter->setPen( Qt::red ); - painter->setBrush( Qt::red ); - - painter->drawLine( QLineF( source, points.first ) ); - painter->drawLine( QLineF( points.first, points.second ) ); - painter->drawLine( QLineF( points.second, sink ) ); - painter->drawEllipse( points.first, 3, 3 ); - painter->drawEllipse( points.second, 3, 3 ); - - painter->setBrush( Qt::NoBrush ); - - painter->drawPath( cubicPath( geom ) ); - } - - { - painter->setPen( Qt::yellow ); - - painter->drawRect( geom.boundingRect() ); - } -} -#endif - -static void drawSketchLine( QPainter* painter, Connection const& connection ) { - using QtNodes::ConnectionState; - - ConnectionState const& state = connection.connectionState(); - - if ( state.requiresPort() ) { - auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); - - QPen p; - p.setWidth( connectionStyle.constructionLineWidth() ); - p.setColor( connectionStyle.constructionColor() ); - p.setStyle( Qt::DashLine ); - - painter->setPen( p ); - painter->setBrush( Qt::NoBrush ); - - using QtNodes::ConnectionGeometry; - ConnectionGeometry const& geom = connection.connectionGeometry(); - - auto cubic = cubicPath( geom ); - // cubic spline - painter->drawPath( cubic ); - } -} - -static void drawHoveredOrSelected( QPainter* painter, Connection const& connection ) { - using QtNodes::ConnectionGeometry; - - ConnectionGeometry const& geom = connection.connectionGeometry(); - bool const hovered = geom.hovered(); - - auto const& graphicsObject = connection.getConnectionGraphicsObject(); - - bool const selected = graphicsObject.isSelected(); - - // drawn as a fat background - if ( hovered || selected ) { - QPen p; - - auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); - double const lineWidth = connectionStyle.lineWidth(); - - p.setWidth( 2 * lineWidth ); - p.setColor( selected ? connectionStyle.selectedHaloColor() - : connectionStyle.hoveredColor() ); - - painter->setPen( p ); - painter->setBrush( Qt::NoBrush ); - - // cubic spline - auto cubic = cubicPath( geom ); - painter->drawPath( cubic ); - } -} - -static void drawNormalLine( QPainter* painter, Connection const& connection ) { - using QtNodes::ConnectionState; - - ConnectionState const& state = connection.connectionState(); - - if ( state.requiresPort() ) return; - - // colors - - auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); - - QColor normalColorOut = connectionStyle.normalColor(); - QColor normalColorIn = connectionStyle.normalColor(); - QColor selectedColor = connectionStyle.selectedColor(); - - bool gradientColor = false; - - if ( connectionStyle.useDataDefinedColors() ) { - using QtNodes::PortType; - - auto dataTypeOut = connection.dataType( PortType::Out ); - auto dataTypeIn = connection.dataType( PortType::In ); - - gradientColor = ( dataTypeOut.id != dataTypeIn.id ); - - normalColorOut = connectionStyle.normalColor( dataTypeOut.id ); - normalColorIn = connectionStyle.normalColor( dataTypeIn.id ); - selectedColor = normalColorOut.darker( 200 ); - } - - // geometry - - ConnectionGeometry const& geom = connection.connectionGeometry(); - - double const lineWidth = connectionStyle.lineWidth(); - - // draw normal line - QPen p; - - p.setWidth( lineWidth ); - - auto const& graphicsObject = connection.getConnectionGraphicsObject(); - bool const selected = graphicsObject.isSelected(); - - auto cubic = cubicPath( geom ); - if ( gradientColor ) { - painter->setBrush( Qt::NoBrush ); - - QColor c = normalColorOut; - if ( selected ) c = c.darker( 200 ); - p.setColor( c ); - painter->setPen( p ); - - unsigned int const segments = 60; - - for ( unsigned int i = 0ul; i < segments; ++i ) { - double ratioPrev = double( i ) / segments; - double ratio = double( i + 1 ) / segments; - - if ( i == segments / 2 ) { - QColor cn = normalColorIn; - if ( selected ) cn = cn.darker( 200 ); - - p.setColor( cn ); - painter->setPen( p ); - } - painter->drawLine( cubic.pointAtPercent( ratioPrev ), cubic.pointAtPercent( ratio ) ); - } - - { - QIcon icon( ":convert.png" ); - - QPixmap pixmap = icon.pixmap( QSize( 22, 22 ) ); - painter->drawPixmap( cubic.pointAtPercent( 0.50 ) - - QPoint( pixmap.width() / 2, pixmap.height() / 2 ), - pixmap ); - } - } - else { - p.setColor( normalColorOut ); - - if ( selected ) { p.setColor( selectedColor ); } - - painter->setPen( p ); - painter->setBrush( Qt::NoBrush ); - - painter->drawPath( cubic ); - } -} - -void ConnectionPainter::paint( QPainter* painter, Connection const& connection ) { - drawHoveredOrSelected( painter, connection ); - - drawSketchLine( painter, connection ); - - drawNormalLine( painter, connection ); - -#ifdef NODE_DEBUG_DRAWING - debugDrawing( painter, connection ); -#endif - - // draw end points - ConnectionGeometry const& geom = connection.connectionGeometry(); - - QPointF const& source = geom.source(); - QPointF const& sink = geom.sink(); - - auto const& connectionStyle = QtNodes::StyleCollection::connectionStyle(); - - double const pointDiameter = connectionStyle.pointDiameter(); - - painter->setPen( connectionStyle.constructionColor() ); - painter->setBrush( connectionStyle.constructionColor() ); - double const pointRadius = pointDiameter / 2.0; - painter->drawEllipse( source, pointRadius, pointRadius ); - painter->drawEllipse( sink, pointRadius, pointRadius ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp deleted file mode 100644 index 681391ae8f1..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionPainter.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -namespace QtNodes { - -class ConnectionGeometry; -class ConnectionState; -class Connection; - -class ConnectionPainter -{ - public: - static void paint( QPainter* painter, Connection const& connection ); - - static QPainterPath getPainterStroke( ConnectionGeometry const& geom ); -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp deleted file mode 100644 index f9f86f85378..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionState.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "ConnectionState.hpp" - -#include - -#include - -#include "FlowScene.hpp" -#include "Node.hpp" - -using QtNodes::ConnectionState; -using QtNodes::Node; - -ConnectionState::~ConnectionState() { - resetLastHoveredNode(); -} - -void ConnectionState::interactWithNode( Node* node ) { - if ( node ) { _lastHoveredNode = node; } - else { - resetLastHoveredNode(); - } -} - -void ConnectionState::setLastHoveredNode( Node* node ) { - _lastHoveredNode = node; -} - -void ConnectionState::resetLastHoveredNode() { - if ( _lastHoveredNode ) _lastHoveredNode->resetReactionToConnection(); - - _lastHoveredNode = nullptr; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp deleted file mode 100644 index de66e99e486..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/ConnectionStyle.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include "ConnectionStyle.hpp" - -#include - -#include -#include -#include -#include -#include - -#include - -#include "StyleCollection.hpp" - -using QtNodes::ConnectionStyle; - -inline void initResources() { - Q_INIT_RESOURCE( resources ); -} - -ConnectionStyle::ConnectionStyle() { - // Explicit resources inialization for preventing the static initialization - // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order - initResources(); - - // This configuration is stored inside the compiled unit and is loaded statically - loadJsonFile( ":DefaultStyle.json" ); -} - -ConnectionStyle::ConnectionStyle( QString jsonText ) { - loadJsonFile( ":DefaultStyle.json" ); - loadJsonText( jsonText ); -} - -void ConnectionStyle::setConnectionStyle( QString jsonText ) { - ConnectionStyle style( jsonText ); - - StyleCollection::setConnectionStyle( style ); -} - -#ifdef STYLE_DEBUG -# define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) \ - { \ - if ( v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null ) \ - qWarning() << "Undefined value for parameter:" << #variable; \ - } -#else -# define CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) -#endif - -#define CONNECTION_VALUE_EXISTS( v ) \ - ( v.type() != QJsonValue::Undefined && v.type() != QJsonValue::Null ) - -#define CONNECTION_STYLE_READ_COLOR( values, variable ) \ - { \ - auto valueRef = values[#variable]; \ - CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ - if ( CONNECTION_VALUE_EXISTS( valueRef ) ) { \ - if ( valueRef.isArray() ) { \ - auto colorArray = valueRef.toArray(); \ - std::vector rgb; \ - rgb.reserve( 3 ); \ - for ( auto it = colorArray.begin(); it != colorArray.end(); ++it ) { \ - rgb.push_back( ( *it ).toInt() ); \ - } \ - variable = QColor( rgb[0], rgb[1], rgb[2] ); \ - } \ - else { \ - variable = QColor( valueRef.toString() ); \ - } \ - } \ - } - -#define CONNECTION_STYLE_READ_FLOAT( values, variable ) \ - { \ - auto valueRef = values[#variable]; \ - CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ - if ( CONNECTION_VALUE_EXISTS( valueRef ) ) variable = valueRef.toDouble(); \ - } - -#define CONNECTION_STYLE_READ_BOOL( values, variable ) \ - { \ - auto valueRef = values[#variable]; \ - CONNECTION_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ - if ( CONNECTION_VALUE_EXISTS( valueRef ) ) variable = valueRef.toBool(); \ - } - -void ConnectionStyle::loadJsonFile( QString styleFile ) { - QFile file( styleFile ); - - if ( !file.open( QIODevice::ReadOnly ) ) { - qWarning() << "Couldn't open file " << styleFile; - - return; - } - - loadJsonFromByteArray( file.readAll() ); -} - -void ConnectionStyle::loadJsonText( QString jsonText ) { - loadJsonFromByteArray( jsonText.toUtf8() ); -} - -void ConnectionStyle::loadJsonFromByteArray( QByteArray const& byteArray ) { - QJsonDocument json( QJsonDocument::fromJson( byteArray ) ); - - QJsonObject topLevelObject = json.object(); - - QJsonValueRef nodeStyleValues = topLevelObject["ConnectionStyle"]; - - QJsonObject obj = nodeStyleValues.toObject(); - - CONNECTION_STYLE_READ_COLOR( obj, ConstructionColor ); - CONNECTION_STYLE_READ_COLOR( obj, NormalColor ); - CONNECTION_STYLE_READ_COLOR( obj, SelectedColor ); - CONNECTION_STYLE_READ_COLOR( obj, SelectedHaloColor ); - CONNECTION_STYLE_READ_COLOR( obj, HoveredColor ); - - CONNECTION_STYLE_READ_FLOAT( obj, LineWidth ); - CONNECTION_STYLE_READ_FLOAT( obj, ConstructionLineWidth ); - CONNECTION_STYLE_READ_FLOAT( obj, PointDiameter ); - - CONNECTION_STYLE_READ_BOOL( obj, UseDataDefinedColors ); -} - -QColor ConnectionStyle::constructionColor() const { - return ConstructionColor; -} - -QColor ConnectionStyle::normalColor() const { - return NormalColor; -} - -QColor ConnectionStyle::normalColor( QString typeId ) const { - std::size_t hash = qHash( typeId ); - - std::size_t const hue_range = 0xFF; - - std::srand( hash ); - std::size_t hue = std::rand() % hue_range; - - std::size_t sat = 120 + hash % 129; - - return QColor::fromHsl( hue, sat, 160 ); -} - -QColor ConnectionStyle::selectedColor() const { - return SelectedColor; -} - -QColor ConnectionStyle::selectedHaloColor() const { - return SelectedHaloColor; -} - -QColor ConnectionStyle::hoveredColor() const { - return HoveredColor; -} - -float ConnectionStyle::lineWidth() const { - return LineWidth; -} - -float ConnectionStyle::constructionLineWidth() const { - return ConstructionLineWidth; -} - -float ConnectionStyle::pointDiameter() const { - return PointDiameter; -} - -bool ConnectionStyle::useDataDefinedColors() const { - return UseDataDefinedColors; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp deleted file mode 100644 index 6427712d846..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/DataModelRegistry.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "DataModelRegistry.hpp" - -#include -#include - -using QtNodes::DataModelRegistry; -using QtNodes::NodeDataModel; -using QtNodes::NodeDataType; -using QtNodes::TypeConverter; - -std::unique_ptr DataModelRegistry::create( QString const& modelName ) { - auto it = _registeredItemCreators.find( modelName ); - - if ( it != _registeredItemCreators.end() ) { return it->second(); } - - return nullptr; -} - -DataModelRegistry::RegisteredModelCreatorsMap const& -DataModelRegistry::registeredModelCreators() const { - return _registeredItemCreators; -} - -DataModelRegistry::RegisteredModelsCategoryMap const& -DataModelRegistry::registeredModelsCategoryAssociation() const { - return _registeredModelsCategory; -} - -DataModelRegistry::CategoriesSet const& DataModelRegistry::categories() const { - return _categories; -} - -TypeConverter DataModelRegistry::getTypeConverter( NodeDataType const& d1, - NodeDataType const& d2 ) const { - TypeConverterId converterId = std::make_pair( d1, d2 ); - - auto it = _registeredTypeConverters.find( converterId ); - - if ( it != _registeredTypeConverters.end() ) { return it->second; } - - return TypeConverter {}; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp deleted file mode 100644 index 7440646d443..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/FlowScene.cpp +++ /dev/null @@ -1,532 +0,0 @@ -#include "FlowScene.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "Node.hpp" -#include "NodeGraphicsObject.hpp" - -#include "ConnectionGraphicsObject.hpp" -#include "NodeGraphicsObject.hpp" - -#include "Connection.hpp" - -#include "DataModelRegistry.hpp" -#include "FlowView.hpp" - -using QtNodes::Connection; -using QtNodes::DataModelRegistry; -using QtNodes::FlowScene; -using QtNodes::Node; -using QtNodes::NodeDataModel; -using QtNodes::NodeGraphicsObject; -using QtNodes::PortIndex; -using QtNodes::PortType; -using QtNodes::TypeConverter; - -FlowScene::FlowScene( std::shared_ptr registry, QObject* parent ) : - QGraphicsScene( parent ), _registry( std::move( registry ) ) { - setItemIndexMethod( QGraphicsScene::NoIndex ); - - // This connection should come first - connect( this, &FlowScene::connectionCreated, this, &FlowScene::setupConnectionSignals ); - connect( this, &FlowScene::connectionCreated, this, &FlowScene::sendConnectionCreatedToNodes ); - connect( this, &FlowScene::connectionDeleted, this, &FlowScene::sendConnectionDeletedToNodes ); -} - -FlowScene::FlowScene( QObject* parent ) : - FlowScene( std::make_shared(), parent ) {} - -FlowScene::~FlowScene() { - clearScene(); -} - -//------------------------------------------------------------------------------ - -std::shared_ptr -FlowScene::createConnection( PortType connectedPort, Node& node, PortIndex portIndex ) { - auto connection = std::make_shared( connectedPort, node, portIndex ); - - auto cgo = detail::make_unique( *this, *connection ); - - // after this function connection points are set to node port - connection->setGraphicsObject( std::move( cgo ) ); - - _connections[connection->id()] = connection; - - // Note: this connection isn't truly created yet. It's only partially created. - // Thus, don't send the connectionCreated(...) signal. - - connect( connection.get(), - &Connection::connectionCompleted, - this, - [this]( Connection const& c ) { connectionCreated( c ); } ); - - return connection; -} - -std::shared_ptr FlowScene::createConnection( Node& nodeIn, - PortIndex portIndexIn, - Node& nodeOut, - PortIndex portIndexOut, - TypeConverter const& converter ) { - auto connection = - std::make_shared( nodeIn, portIndexIn, nodeOut, portIndexOut, converter ); - - auto cgo = detail::make_unique( *this, *connection ); - - nodeIn.nodeState().setConnection( PortType::In, portIndexIn, *connection ); - nodeOut.nodeState().setConnection( PortType::Out, portIndexOut, *connection ); - - // after this function connection points are set to node port - connection->setGraphicsObject( std::move( cgo ) ); - - // trigger data propagation - nodeOut.onDataUpdated( portIndexOut ); - - _connections[connection->id()] = connection; - - connectionCreated( *connection ); - - return connection; -} - -std::shared_ptr FlowScene::restoreConnection( QJsonObject const& connectionJson ) { - QUuid nodeInId = QUuid( connectionJson["in_id"].toString() ); - QUuid nodeOutId = QUuid( connectionJson["out_id"].toString() ); - - PortIndex portIndexIn = connectionJson["in_index"].toInt(); - PortIndex portIndexOut = connectionJson["out_index"].toInt(); - - auto nodeIn = _nodes[nodeInId].get(); - auto nodeOut = _nodes[nodeOutId].get(); - - auto getConverter = [&]() { - QJsonValue converterVal = connectionJson["converter"]; - - if ( !converterVal.isUndefined() ) { - QJsonObject converterJson = converterVal.toObject(); - - NodeDataType inType { converterJson["in"].toObject()["id"].toString(), - converterJson["in"].toObject()["name"].toString() }; - - NodeDataType outType { converterJson["out"].toObject()["id"].toString(), - converterJson["out"].toObject()["name"].toString() }; - - auto converter = registry().getTypeConverter( outType, inType ); - - if ( converter ) return converter; - } - - return TypeConverter {}; - }; - - std::shared_ptr connection = - createConnection( *nodeIn, portIndexIn, *nodeOut, portIndexOut, getConverter() ); - - // Note: the connectionCreated(...) signal has already been sent - // by createConnection(...) - - return connection; -} - -void FlowScene::importConnection( const QString& fromNodeId, - int fromNodePort, - const QString& toNodeId, - int toNodePort ) { - QUuid nodeInId = QUuid( toNodeId ); - QUuid nodeOutId = QUuid( fromNodeId ); - auto nodeIn = _nodes[nodeInId].get(); - auto nodeOut = _nodes[nodeOutId].get(); - - auto connection = std::make_shared( - *nodeIn, toNodePort, *nodeOut, fromNodePort, TypeConverter {} ); - - auto cgo = detail::make_unique( *this, *connection ); - - nodeIn->nodeState().setConnection( PortType::In, toNodePort, *connection ); - nodeOut->nodeState().setConnection( PortType::Out, fromNodePort, *connection ); - - nodeIn->nodeDataModel()->updateState(); - - // after this function connection points are set to node port - connection->setGraphicsObject( std::move( cgo ) ); - - _connections[connection->id()] = connection; -} - -void FlowScene::deleteConnection( Connection& connection ) { - auto it = _connections.find( connection.id() ); - if ( it != _connections.end() ) { - connection.removeFromNodes(); - _connections.erase( it ); - } -} - -Node& FlowScene::createNode( std::unique_ptr&& dataModel ) { - auto node = detail::make_unique( std::move( dataModel ) ); - auto ngo = detail::make_unique( *this, *node ); - - node->setGraphicsObject( std::move( ngo ) ); - - // generates the uuid of the node (delegated to nodeDataModel implementation) - node->_uid = QUuid( node->nodeDataModel()->uuid() ); - - auto nodePtr = node.get(); - _nodes[node->id()] = std::move( node ); - - nodeCreated( *nodePtr ); - return *nodePtr; -} - -Node& FlowScene::importNode( std::unique_ptr&& dataModel ) { - auto node = detail::make_unique( std::move( dataModel ) ); - auto ngo = detail::make_unique( *this, *node ); - node->setGraphicsObject( std::move( ngo ) ); - node->import(); - - auto nodePtr = node.get(); - _nodes[node->id()] = std::move( node ); - - nodePlaced( *nodePtr ); - nodeCreated( *nodePtr ); - return *nodePtr; -} - -Node& FlowScene::restoreNode( QJsonObject const& nodeJson ) { - QString modelName = nodeJson["model"].toObject()["name"].toString(); - - auto dataModel = registry().create( modelName ); - - if ( !dataModel ) - throw std::logic_error( std::string( "No registered model with name " ) + - modelName.toLocal8Bit().data() ); - - auto node = detail::make_unique( std::move( dataModel ) ); - auto ngo = detail::make_unique( *this, *node ); - node->setGraphicsObject( std::move( ngo ) ); - - node->restore( nodeJson ); - - auto nodePtr = node.get(); - _nodes[node->id()] = std::move( node ); - - nodePlaced( *nodePtr ); - nodeCreated( *nodePtr ); - return *nodePtr; -} - -void FlowScene::removeNode( Node& node ) { - // call signal - nodeDeleted( node ); - - for ( auto portType : { PortType::In, PortType::Out } ) { - auto nodeState = node.nodeState(); - auto const& nodeEntries = nodeState.getEntries( portType ); - - for ( auto& connections : nodeEntries ) { - for ( auto const& pair : connections ) - deleteConnection( *pair.second ); - } - } - - _nodes.erase( node.id() ); -} - -DataModelRegistry& FlowScene::registry() const { - return *_registry; -} - -void FlowScene::setRegistry( std::shared_ptr registry ) { - _registry = std::move( registry ); -} - -void FlowScene::iterateOverNodes( std::function const& visitor ) { - for ( const auto& _node : _nodes ) { - visitor( _node.second.get() ); - } -} - -void FlowScene::iterateOverNodeData( std::function const& visitor ) { - for ( const auto& _node : _nodes ) { - visitor( _node.second->nodeDataModel() ); - } -} - -void FlowScene::iterateOverNodeDataDependentOrder( - std::function const& visitor ) { - std::set visitedNodesSet; - - // A leaf node is a node with no input ports, or all possible input ports empty - auto isNodeLeaf = []( Node const& node, NodeDataModel const& model ) { - for ( unsigned int i = 0; i < model.nPorts( PortType::In ); ++i ) { - auto connections = node.nodeState().connections( PortType::In, i ); - if ( !connections.empty() ) { return false; } - } - - return true; - }; - - // Iterate over "leaf" nodes - for ( auto const& _node : _nodes ) { - auto const& node = _node.second; - auto model = node->nodeDataModel(); - - if ( isNodeLeaf( *node, *model ) ) { - visitor( model ); - visitedNodesSet.insert( node->id() ); - } - } - - auto areNodeInputsVisitedBefore = [&]( Node const& node, NodeDataModel const& model ) { - for ( size_t i = 0; i < model.nPorts( PortType::In ); ++i ) { - auto connections = node.nodeState().connections( PortType::In, i ); - - for ( auto& conn : connections ) { - if ( visitedNodesSet.find( conn.second->getNode( PortType::Out )->id() ) == - visitedNodesSet.end() ) { - return false; - } - } - } - - return true; - }; - - // Iterate over dependent nodes - while ( _nodes.size() != visitedNodesSet.size() ) { - for ( auto const& _node : _nodes ) { - auto const& node = _node.second; - if ( visitedNodesSet.find( node->id() ) != visitedNodesSet.end() ) continue; - - auto model = node->nodeDataModel(); - - if ( areNodeInputsVisitedBefore( *node, *model ) ) { - visitor( model ); - visitedNodesSet.insert( node->id() ); - } - } - } -} - -QPointF FlowScene::getNodePosition( const Node& node ) const { - return node.nodeGraphicsObject().pos(); -} - -void FlowScene::setNodePosition( Node& node, const QPointF& pos ) const { - node.nodeGraphicsObject().setPos( pos ); - node.nodeGraphicsObject().moveConnections(); - - QJsonObject obj; - obj["x"] = node.nodeGraphicsObject().pos().x(); - obj["y"] = node.nodeGraphicsObject().pos().y(); - QJsonObject nodeJson; - nodeJson["position"] = obj; - node.nodeDataModel()->addMetaData( nodeJson ); -} - -QSizeF FlowScene::getNodeSize( const Node& node ) const { - return QSizeF( node.nodeGeometry().width(), node.nodeGeometry().height() ); -} - -std::unordered_map> const& FlowScene::nodes() const { - return _nodes; -} - -std::unordered_map> const& FlowScene::connections() const { - return _connections; -} - -std::vector FlowScene::allNodes() const { - std::vector nodes; - - std::transform( - _nodes.begin(), - _nodes.end(), - std::back_inserter( nodes ), - []( std::pair> const& p ) { return p.second.get(); } ); - - return nodes; -} - -std::vector FlowScene::selectedNodes() const { - QList graphicsItems = selectedItems(); - - std::vector ret; - ret.reserve( graphicsItems.size() ); - - for ( QGraphicsItem* item : graphicsItems ) { - auto ngo = qgraphicsitem_cast( item ); - - if ( ngo != nullptr ) { ret.push_back( &ngo->node() ); } - } - - return ret; -} - -//------------------------------------------------------------------------------ - -void FlowScene::clearScene() { - // Manual node cleanup. Simply clearing the holding datastructures doesn't work, the code - // crashes when - // there are both nodes and connections in the scene. (The data propagation internal logic tries - // to propagate data through already freed connections.) - while ( _connections.size() > 0 ) { - deleteConnection( *_connections.begin()->second ); - } - - while ( _nodes.size() > 0 ) { - removeNode( *_nodes.begin()->second ); - } -} - -void FlowScene::save() const { - QString fileName = QFileDialog::getSaveFileName( - nullptr, tr( "Open Flow Scene" ), QDir::homePath(), tr( "Flow Scene Files (*.flow)" ) ); - - if ( !fileName.isEmpty() ) { - if ( !fileName.endsWith( "flow", Qt::CaseInsensitive ) ) fileName += ".flow"; - - QFile file( fileName ); - if ( file.open( QIODevice::WriteOnly ) ) { file.write( saveToMemory() ); } - } -} - -void FlowScene::load() { - QString fileName = QFileDialog::getOpenFileName( - nullptr, tr( "Open Flow Scene" ), QDir::homePath(), tr( "Flow Scene Files (*.flow)" ) ); - - if ( !QFileInfo::exists( fileName ) ) return; - - QFile file( fileName ); - - if ( !file.open( QIODevice::ReadOnly ) ) return; - - clearScene(); - - QByteArray wholeFile = file.readAll(); - - loadFromMemory( wholeFile ); -} - -QByteArray FlowScene::saveToMemory() const { - QJsonObject sceneJson; - - QJsonArray nodesJsonArray; - - for ( auto const& pair : _nodes ) { - auto const& node = pair.second; - - nodesJsonArray.append( node->save() ); - } - - sceneJson["nodes"] = nodesJsonArray; - - QJsonArray connectionJsonArray; - for ( auto const& pair : _connections ) { - auto const& connection = pair.second; - - QJsonObject connectionJson = connection->save(); - - if ( !connectionJson.isEmpty() ) connectionJsonArray.append( connectionJson ); - } - - sceneJson["connections"] = connectionJsonArray; - - QJsonDocument document( sceneJson ); - - return document.toJson(); -} - -void FlowScene::loadFromMemory( const QByteArray& data ) { - QJsonObject const jsonDocument = QJsonDocument::fromJson( data ).object(); - - QJsonArray nodesJsonArray = jsonDocument["nodes"].toArray(); - - for ( QJsonValueRef node : nodesJsonArray ) { - restoreNode( node.toObject() ); - } - - QJsonArray connectionJsonArray = jsonDocument["connections"].toArray(); - - for ( QJsonValueRef connection : connectionJsonArray ) { - restoreConnection( connection.toObject() ); - } -} - -void FlowScene::setupConnectionSignals( Connection const& c ) { - connect( &c, - &Connection::connectionMadeIncomplete, - this, - &FlowScene::connectionDeleted, - Qt::UniqueConnection ); -} - -void FlowScene::sendConnectionCreatedToNodes( Connection const& c ) { - Node* from = c.getNode( PortType::Out ); - Node* to = c.getNode( PortType::In ); - - Q_ASSERT( from != nullptr ); - Q_ASSERT( to != nullptr ); - - from->nodeDataModel()->outputConnectionCreated( c ); - to->nodeDataModel()->inputConnectionCreated( c ); -} - -void FlowScene::sendConnectionDeletedToNodes( Connection const& c ) { - Node* from = c.getNode( PortType::Out ); - Node* to = c.getNode( PortType::In ); - - Q_ASSERT( from != nullptr ); - Q_ASSERT( to != nullptr ); - - from->nodeDataModel()->outputConnectionDeleted( c ); - to->nodeDataModel()->inputConnectionDeleted( c ); -} - -//------------------------------------------------------------------------------ -namespace QtNodes { - -Node* locateNodeAt( QPointF scenePoint, FlowScene& scene, QTransform const& viewTransform ) { - // items under cursor - QList items = - scene.items( scenePoint, Qt::IntersectsItemShape, Qt::DescendingOrder, viewTransform ); - - //// items convertable to NodeGraphicsObject - std::vector filteredItems; - - std::copy_if( - items.begin(), items.end(), std::back_inserter( filteredItems ), []( QGraphicsItem* item ) { -#if defined( __APPLE__ ) && defined( __GNUG__ ) - // Ugly workaround to prevent segfault in the following dynamic_cast - return ( typeid( *item ).hash_code() == typeid( NodeGraphicsObject ).hash_code() ); -#else - return (dynamic_cast(item) != nullptr); -#endif - } ); - - Node* resultNode = nullptr; - - if ( !filteredItems.empty() ) { - QGraphicsItem* graphicsItem = filteredItems.front(); - auto ngo = dynamic_cast( graphicsItem ); - - resultNode = &ngo->node(); - } - - return resultNode; -} -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp deleted file mode 100644 index 05fbc88472e..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/FlowView.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#include "FlowView.hpp" - -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include - -#include "ConnectionGraphicsObject.hpp" -#include "DataModelRegistry.hpp" -#include "FlowScene.hpp" -#include "Node.hpp" -#include "NodeGraphicsObject.hpp" -#include "StyleCollection.hpp" - -using QtNodes::FlowScene; -using QtNodes::FlowView; - -FlowView::FlowView( QWidget* parent ) : - QGraphicsView( parent ), - _clearSelectionAction( Q_NULLPTR ), - _deleteSelectionAction( Q_NULLPTR ), - _scene( Q_NULLPTR ) { - setDragMode( QGraphicsView::ScrollHandDrag ); - setRenderHint( QPainter::Antialiasing ); - - auto const& flowViewStyle = StyleCollection::flowViewStyle(); - - setBackgroundBrush( flowViewStyle.BackgroundColor ); - - // setViewportUpdateMode(QGraphicsView::FullViewportUpdate); - // setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); - setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); - - setTransformationAnchor( QGraphicsView::AnchorUnderMouse ); - - setCacheMode( QGraphicsView::CacheBackground ); - - // setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); -} - -FlowView::FlowView( FlowScene* scene, QWidget* parent ) : FlowView( parent ) { - setScene( scene ); -} - -QAction* FlowView::clearSelectionAction() const { - return _clearSelectionAction; -} - -QAction* FlowView::deleteSelectionAction() const { - return _deleteSelectionAction; -} - -void FlowView::setScene( FlowScene* scene ) { - _scene = scene; - QGraphicsView::setScene( _scene ); - - // setup actions - delete _clearSelectionAction; - _clearSelectionAction = new QAction( QStringLiteral( "Clear Selection" ), this ); - _clearSelectionAction->setShortcut( Qt::Key_Escape ); - connect( _clearSelectionAction, &QAction::triggered, _scene, &QGraphicsScene::clearSelection ); - addAction( _clearSelectionAction ); - - delete _deleteSelectionAction; - _deleteSelectionAction = new QAction( QStringLiteral( "Delete Selection" ), this ); - _deleteSelectionAction->setShortcut( Qt::Key_Delete ); - connect( _deleteSelectionAction, &QAction::triggered, this, &FlowView::deleteSelectedNodes ); - addAction( _deleteSelectionAction ); -} - -void FlowView::contextMenuEvent( QContextMenuEvent* event ) { - if ( itemAt( event->pos() ) ) { - QGraphicsView::contextMenuEvent( event ); - return; - } - - QMenu modelMenu; - - auto skipText = QStringLiteral( "skip me" ); - - // Add filterbox to the context menu - auto* txtBox = new QLineEdit( &modelMenu ); - - txtBox->setPlaceholderText( QStringLiteral( "Filter" ) ); - txtBox->setClearButtonEnabled( true ); - - auto* txtBoxAction = new QWidgetAction( &modelMenu ); - txtBoxAction->setDefaultWidget( txtBox ); - - modelMenu.addAction( txtBoxAction ); - - // Add result treeview to the context menu - auto* treeView = new QTreeWidget( &modelMenu ); - treeView->header()->close(); - - auto* treeViewAction = new QWidgetAction( &modelMenu ); - treeViewAction->setDefaultWidget( treeView ); - - modelMenu.addAction( treeViewAction ); - - QMap topLevelItems; - for ( auto const& cat : _scene->registry().categories() ) { - auto item = new QTreeWidgetItem( treeView ); - item->setText( 0, cat ); - item->setData( 0, Qt::UserRole, skipText ); - topLevelItems[cat] = item; - } - - for ( auto const& assoc : _scene->registry().registeredModelsCategoryAssociation() ) { - auto parent = topLevelItems[assoc.second]; - auto item = new QTreeWidgetItem( parent ); - item->setText( 0, assoc.first ); - item->setData( 0, Qt::UserRole, assoc.first ); - } - - treeView->expandAll(); - - connect( treeView, &QTreeWidget::itemClicked, [&]( QTreeWidgetItem* item, int ) { - QString modelName = item->data( 0, Qt::UserRole ).toString(); - - if ( modelName == skipText ) { return; } - - auto type = _scene->registry().create( modelName ); - - if ( type ) { - auto& node = _scene->createNode( std::move( type ) ); - - QPoint pos = event->pos(); - - QPointF posView = this->mapToScene( pos ); - - node.nodeGraphicsObject().setPos( posView ); - - _scene->nodePlaced( node ); - } - else { - qDebug() << "Model not found"; - } - - modelMenu.close(); - } ); - - // Setup filtering - connect( txtBox, &QLineEdit::textChanged, [&]( const QString& text ) { - for ( auto& topLvlItem : topLevelItems ) { - for ( int i = 0; i < topLvlItem->childCount(); ++i ) { - auto child = topLvlItem->child( i ); - auto modelName = child->data( 0, Qt::UserRole ).toString(); - const bool match = ( modelName.contains( text, Qt::CaseInsensitive ) ); - child->setHidden( !match ); - } - } - } ); - - // make sure the text box gets focus so the user doesn't have to click on it - txtBox->setFocus(); - - modelMenu.exec( event->globalPos() ); -} - -void FlowView::wheelEvent( QWheelEvent* event ) { - QPoint delta = event->angleDelta(); - - if ( delta.y() == 0 ) { - event->ignore(); - return; - } - - double const d = delta.y() / std::abs( delta.y() ); - - if ( d > 0.0 ) - scaleUp(); - else - scaleDown(); -} - -void FlowView::scaleUp() { - double const step = 1.2; - double const factor = std::pow( step, 1.0 ); - - QTransform t = transform(); - - if ( t.m11() > 2.0 ) return; - - scale( factor, factor ); -} - -void FlowView::scaleDown() { - double const step = 1.2; - double const factor = std::pow( step, -1.0 ); - - scale( factor, factor ); -} - -void FlowView::deleteSelectedNodes() { - // Delete the selected connections first, ensuring that they won't be - // automatically deleted when selected nodes are deleted (deleting a node - // deletes some connections as well) - for ( QGraphicsItem* item : _scene->selectedItems() ) { - if ( auto c = qgraphicsitem_cast( item ) ) - _scene->deleteConnection( c->connection() ); - } - - // Delete the nodes; this will delete many of the connections. - // Selected connections were already deleted prior to this loop, otherwise - // qgraphicsitem_cast(item) could be a use-after-free - // when a selected connection is deleted by deleting the node. - for ( QGraphicsItem* item : _scene->selectedItems() ) { - if ( auto n = qgraphicsitem_cast( item ) ) - _scene->removeNode( n->node() ); - } -} - -void FlowView::keyPressEvent( QKeyEvent* event ) { - switch ( event->key() ) { - case Qt::Key_Shift: - setDragMode( QGraphicsView::RubberBandDrag ); - break; - - default: - break; - } - - QGraphicsView::keyPressEvent( event ); -} - -void FlowView::keyReleaseEvent( QKeyEvent* event ) { - switch ( event->key() ) { - case Qt::Key_Shift: - setDragMode( QGraphicsView::ScrollHandDrag ); - break; - - default: - break; - } - QGraphicsView::keyReleaseEvent( event ); -} - -void FlowView::mousePressEvent( QMouseEvent* event ) { - QGraphicsView::mousePressEvent( event ); - if ( event->button() == Qt::LeftButton ) { _clickPos = mapToScene( event->pos() ); } -} - -void FlowView::mouseMoveEvent( QMouseEvent* event ) { - QGraphicsView::mouseMoveEvent( event ); - if ( scene()->mouseGrabberItem() == nullptr && event->buttons() == Qt::LeftButton ) { - // Make sure shift is not being pressed - if ( ( event->modifiers() & Qt::ShiftModifier ) == 0 ) { - QPointF difference = _clickPos - mapToScene( event->pos() ); - setSceneRect( sceneRect().translated( difference.x(), difference.y() ) ); - } - } -} - -void FlowView::drawBackground( QPainter* painter, const QRectF& r ) { - QGraphicsView::drawBackground( painter, r ); - - auto drawGrid = [&]( double gridStep ) { - QRect windowRect = rect(); - QPointF tl = mapToScene( windowRect.topLeft() ); - QPointF br = mapToScene( windowRect.bottomRight() ); - - double left = std::floor( tl.x() / gridStep - 0.5 ); - double right = std::floor( br.x() / gridStep + 1.0 ); - double bottom = std::floor( tl.y() / gridStep - 0.5 ); - double top = std::floor( br.y() / gridStep + 1.0 ); - - // vertical lines - for ( int xi = int( left ); xi <= int( right ); ++xi ) { - QLineF line( xi * gridStep, bottom * gridStep, xi * gridStep, top * gridStep ); - - painter->drawLine( line ); - } - - // horizontal lines - for ( int yi = int( bottom ); yi <= int( top ); ++yi ) { - QLineF line( left * gridStep, yi * gridStep, right * gridStep, yi * gridStep ); - painter->drawLine( line ); - } - }; - - auto const& flowViewStyle = StyleCollection::flowViewStyle(); - - QBrush bBrush = backgroundBrush(); - - QPen pfine( flowViewStyle.FineGridColor, 1.0 ); - - painter->setPen( pfine ); - drawGrid( 15 ); - - QPen p( flowViewStyle.CoarseGridColor, 1.0 ); - - painter->setPen( p ); - drawGrid( 150 ); -} - -void FlowView::showEvent( QShowEvent* event ) { - _scene->setSceneRect( this->rect() ); - QGraphicsView::showEvent( event ); -} - -FlowScene* FlowView::scene() { - return _scene; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp deleted file mode 100644 index 63e0a62d78a..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/FlowViewStyle.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "FlowViewStyle.hpp" - -#include -#include -#include -#include -#include - -#include - -#include "StyleCollection.hpp" - -using QtNodes::FlowViewStyle; - -inline void initResources() { - Q_INIT_RESOURCE( resources ); -} - -FlowViewStyle::FlowViewStyle() { - // Explicit resources inialization for preventing the static initialization - // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order - initResources(); - - // This configuration is stored inside the compiled unit and is loaded statically - loadJsonFile( ":DefaultStyle.json" ); -} - -FlowViewStyle::FlowViewStyle( QString jsonText ) { - loadJsonText( jsonText ); -} - -void FlowViewStyle::setStyle( QString jsonText ) { - FlowViewStyle style( jsonText ); - - StyleCollection::setFlowViewStyle( style ); -} - -#ifdef STYLE_DEBUG -# define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) \ - { \ - if ( v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null ) \ - qWarning() << "Undefined value for parameter:" << #variable; \ - } -#else -# define FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) -#endif - -#define FLOW_VIEW_STYLE_READ_COLOR( values, variable ) \ - { \ - auto valueRef = values[#variable]; \ - FLOW_VIEW_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ - if ( valueRef.isArray() ) { \ - auto colorArray = valueRef.toArray(); \ - std::vector rgb; \ - rgb.reserve( 3 ); \ - for ( auto it = colorArray.begin(); it != colorArray.end(); ++it ) { \ - rgb.push_back( ( *it ).toInt() ); \ - } \ - variable = QColor( rgb[0], rgb[1], rgb[2] ); \ - } \ - else { \ - variable = QColor( valueRef.toString() ); \ - } \ - } - -void FlowViewStyle::loadJsonFile( QString styleFile ) { - QFile file( styleFile ); - - if ( !file.open( QIODevice::ReadOnly ) ) { - qWarning() << "Couldn't open file " << styleFile; - - return; - } - - loadJsonFromByteArray( file.readAll() ); -} - -void FlowViewStyle::loadJsonText( QString jsonText ) { - loadJsonFromByteArray( jsonText.toUtf8() ); -} - -void FlowViewStyle::loadJsonFromByteArray( QByteArray const& byteArray ) { - QJsonDocument json( QJsonDocument::fromJson( byteArray ) ); - - QJsonObject topLevelObject = json.object(); - - QJsonValueRef nodeStyleValues = topLevelObject["FlowViewStyle"]; - - QJsonObject obj = nodeStyleValues.toObject(); - - FLOW_VIEW_STYLE_READ_COLOR( obj, BackgroundColor ); - FLOW_VIEW_STYLE_READ_COLOR( obj, FineGridColor ); - FLOW_VIEW_STYLE_READ_COLOR( obj, CoarseGridColor ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp deleted file mode 100644 index 9831e55117e..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/Node.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "Node.hpp" - -#include - -#include -#include - -#include "FlowScene.hpp" - -#include "NodeDataModel.hpp" -#include "NodeGraphicsObject.hpp" - -#include "ConnectionGraphicsObject.hpp" -#include "ConnectionState.hpp" - -using QtNodes::Node; -using QtNodes::NodeData; -using QtNodes::NodeDataModel; -using QtNodes::NodeDataType; -using QtNodes::NodeGeometry; -using QtNodes::NodeGraphicsObject; -using QtNodes::NodeState; -using QtNodes::PortIndex; -using QtNodes::PortType; - -// TODO MTHS -- get uuid from datamodel instead of generating one -Node::Node( std::unique_ptr&& dataModel ) : - _uid( QUuid::createUuid() ), - _nodeDataModel( std::move( dataModel ) ), - _nodeState( _nodeDataModel ), - _nodeGeometry( _nodeDataModel ), - _nodeGraphicsObject( nullptr ) { - _nodeGeometry.recalculateSize(); - - // propagate data: model => node - connect( _nodeDataModel.get(), &NodeDataModel::dataUpdated, this, &Node::onDataUpdated ); - - connect( _nodeDataModel.get(), - &NodeDataModel::embeddedWidgetSizeUpdated, - this, - &Node::onNodeSizeUpdated ); -} - -Node::~Node() { - // Do not delete nodeDataModel whos content is non deletable - if ( !_nodeDataModel->isDeletable() ) { _nodeDataModel.release(); } -} - -QJsonObject Node::save() const { - QJsonObject nodeJson; -#ifdef ORIGINAL_NODEEDITOR - nodeJson["id"] = _uid.toString(); - - nodeJson["model"] = _nodeDataModel->save(); -#else - nodeJson = _nodeDataModel->save(); -#endif - QJsonObject obj; - obj["x"] = _nodeGraphicsObject->pos().x(); - obj["y"] = _nodeGraphicsObject->pos().y(); - nodeJson["position"] = obj; - - return nodeJson; -} - -void Node::import() { - QJsonObject json = _nodeDataModel->save(); - _uid = QUuid( _nodeDataModel->uuid() ); - QJsonObject positionJson = json["position"].toObject(); - QPointF point( positionJson["x"].toDouble(), positionJson["y"].toDouble() ); - _nodeGraphicsObject->setPos( point ); -} - -void Node::restore( QJsonObject const& json ) { -#ifdef ORIGINAL_NODEEDITOR - _uid = QUuid( json["id"].toString() ); - QJsonObject positionJson = json["position"].toObject(); - QPointF point( positionJson["x"].toDouble(), positionJson["y"].toDouble() ); - _nodeGraphicsObject->setPos( point ); - _nodeDataModel->restore( json["model"].toObject() ); -#else - _nodeDataModel->restore( json ); - _uid = QUuid( _nodeDataModel->uuid() ); - - QJsonObject positionJson = json["position"].toObject(); - QPointF point( positionJson["x"].toDouble(), positionJson["y"].toDouble() ); - _nodeGraphicsObject->setPos( point ); -#endif -} - -QUuid Node::id() const { - return _uid; -} - -void Node::reactToPossibleConnection( PortType reactingPortType, - NodeDataType const& reactingDataType, - QPointF const& scenePoint ) { - QTransform const t = _nodeGraphicsObject->sceneTransform(); - - QPointF p = t.inverted().map( scenePoint ); - - _nodeGeometry.setDraggingPosition( p ); - - _nodeGraphicsObject->update(); - - _nodeState.setReaction( NodeState::REACTING, reactingPortType, reactingDataType ); -} - -void Node::resetReactionToConnection() { - _nodeState.setReaction( NodeState::NOT_REACTING ); - _nodeGraphicsObject->update(); -} - -NodeGraphicsObject const& Node::nodeGraphicsObject() const { - return *_nodeGraphicsObject.get(); -} - -NodeGraphicsObject& Node::nodeGraphicsObject() { - return *_nodeGraphicsObject.get(); -} - -void Node::setGraphicsObject( std::unique_ptr&& graphics ) { - _nodeGraphicsObject = std::move( graphics ); - - _nodeGeometry.recalculateSize(); -} - -NodeGeometry& Node::nodeGeometry() { - return _nodeGeometry; -} - -NodeGeometry const& Node::nodeGeometry() const { - return _nodeGeometry; -} - -NodeState const& Node::nodeState() const { - return _nodeState; -} - -NodeState& Node::nodeState() { - return _nodeState; -} - -NodeDataModel* Node::nodeDataModel() const { - return _nodeDataModel.get(); -} - -void Node::propagateData( std::shared_ptr nodeData, PortIndex inPortIndex ) const { - _nodeDataModel->setInData( std::move( nodeData ), inPortIndex ); - - // Recalculate the nodes visuals. A data change can result in the node taking more space than - // before, so this forces a recalculate+repaint on the affected node - _nodeGraphicsObject->setGeometryChanged(); - _nodeGeometry.recalculateSize(); - _nodeGraphicsObject->update(); - _nodeGraphicsObject->moveConnections(); -} - -void Node::onDataUpdated( PortIndex index ) { - auto nodeData = _nodeDataModel->outData( index ); - - auto connections = _nodeState.connections( PortType::Out, index ); - - for ( auto const& c : connections ) - c.second->propagateData( nodeData ); -} - -void Node::onNodeSizeUpdated() { - if ( nodeDataModel()->embeddedWidget() ) { nodeDataModel()->embeddedWidget()->adjustSize(); } - nodeGeometry().recalculateSize(); - for ( PortType type : { PortType::In, PortType::Out } ) { - for ( auto& conn_set : nodeState().getEntries( type ) ) { - for ( auto& pair : conn_set ) { - Connection* conn = pair.second; - conn->getConnectionGraphicsObject().move(); - } - } - } -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp deleted file mode 100644 index 71656044031..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "NodeConnectionInteraction.hpp" - -#include "ConnectionGraphicsObject.hpp" -#include "DataModelRegistry.hpp" -#include "FlowScene.hpp" -#include "NodeDataModel.hpp" -#include "NodeGraphicsObject.hpp" - -using QtNodes::Connection; -using QtNodes::FlowScene; -using QtNodes::Node; -using QtNodes::NodeConnectionInteraction; -using QtNodes::NodeDataModel; -using QtNodes::PortIndex; -using QtNodes::PortType; -using QtNodes::TypeConverter; - -NodeConnectionInteraction::NodeConnectionInteraction( Node& node, - Connection& connection, - FlowScene& scene ) : - _node( &node ), _connection( &connection ), _scene( &scene ) {} - -bool NodeConnectionInteraction::canConnect( PortIndex& portIndex, TypeConverter& converter ) const { - // 1) Connection requires a port - - PortType requiredPort = connectionRequiredPort(); - - if ( requiredPort == PortType::None ) { return false; } - - // 1.5) Forbid connecting the node to itself - Node* node = _connection->getNode( oppositePort( requiredPort ) ); - - if ( node == _node ) return false; - - // 2) connection point is on top of the node port - - QPointF connectionPoint = connectionEndScenePosition( requiredPort ); - - portIndex = nodePortIndexUnderScenePoint( requiredPort, connectionPoint ); - - if ( portIndex == INVALID ) { return false; } - - // 3) Node port is vacant - - // port should be empty - if ( !nodePortIsEmpty( requiredPort, portIndex ) ) return false; - - // 4) Connection type equals node port type, or there is a registered type conversion that can - // translate between the two - - auto connectionDataType = _connection->dataType( oppositePort( requiredPort ) ); - - auto const& modelTarget = _node->nodeDataModel(); - NodeDataType candidateNodeDataType = modelTarget->dataType( requiredPort, portIndex ); - - if ( connectionDataType.id != candidateNodeDataType.id ) { - if ( requiredPort == PortType::In ) { - converter = - _scene->registry().getTypeConverter( connectionDataType, candidateNodeDataType ); - } - else if ( requiredPort == PortType::Out ) { - converter = - _scene->registry().getTypeConverter( candidateNodeDataType, connectionDataType ); - } - - return ( converter != nullptr ); - } - - return true; -} - -bool NodeConnectionInteraction::tryConnect() const { - // 1) Check conditions from 'canConnect' - PortIndex portIndex = INVALID; - - TypeConverter converter; - - if ( !canConnect( portIndex, converter ) ) { return false; } - - // 1.5) If the connection is possible but a type conversion is needed, - // assign a convertor to connection - if ( converter ) { _connection->setTypeConverter( converter ); } - - // 2) Assign node to required port in Connection - PortType requiredPort = connectionRequiredPort(); - _node->nodeState().setConnection( requiredPort, portIndex, *_connection ); - - // 3) Assign Connection to empty port in NodeState - // The port is not longer required after this function - _connection->setNodeToPort( *_node, requiredPort, portIndex ); - - // 4) Adjust Connection geometry - - _node->nodeGraphicsObject().moveConnections(); - - // 5) Poke model to intiate data transfer - - auto outNode = _connection->getNode( PortType::Out ); - if ( outNode ) { - PortIndex outPortIndex = _connection->getPortIndex( PortType::Out ); - outNode->onDataUpdated( outPortIndex ); - } - - return true; -} - -/// 1) Node and Connection should be already connected -/// 2) If so, clear Connection entry in the NodeState -/// 3) Set Connection end to 'requiring a port' -bool NodeConnectionInteraction::disconnect( PortType portToDisconnect ) const { - PortIndex portIndex = _connection->getPortIndex( portToDisconnect ); - - NodeState& state = _node->nodeState(); - - // clear pointer to Connection in the NodeState - state.getEntries( portToDisconnect )[portIndex].clear(); - - // 4) Propagate invalid data to IN node - _connection->propagateEmptyData(); - - // clear Connection side - _connection->clearNode( portToDisconnect ); - - _connection->setRequiredPort( portToDisconnect ); - - _connection->getConnectionGraphicsObject().grabMouse(); - - return true; -} - -// ------------------ util functions below - -PortType NodeConnectionInteraction::connectionRequiredPort() const { - auto const& state = _connection->connectionState(); - - return state.requiredPort(); -} - -QPointF NodeConnectionInteraction::connectionEndScenePosition( PortType portType ) const { - auto& go = _connection->getConnectionGraphicsObject(); - - ConnectionGeometry& geometry = _connection->connectionGeometry(); - - QPointF endPoint = geometry.getEndPoint( portType ); - - return go.mapToScene( endPoint ); -} - -QPointF NodeConnectionInteraction::nodePortScenePosition( PortType portType, - PortIndex portIndex ) const { - NodeGeometry const& geom = _node->nodeGeometry(); - - QPointF p = geom.portScenePosition( portIndex, portType ); - - NodeGraphicsObject& ngo = _node->nodeGraphicsObject(); - - return ngo.sceneTransform().map( p ); -} - -PortIndex -NodeConnectionInteraction::nodePortIndexUnderScenePoint( PortType portType, - QPointF const& scenePoint ) const { - NodeGeometry const& nodeGeom = _node->nodeGeometry(); - - QTransform sceneTransform = _node->nodeGraphicsObject().sceneTransform(); - - PortIndex portIndex = nodeGeom.checkHitScenePoint( portType, scenePoint, sceneTransform ); - return portIndex; -} - -bool NodeConnectionInteraction::nodePortIsEmpty( PortType portType, PortIndex portIndex ) const { - NodeState const& nodeState = _node->nodeState(); - - auto const& entries = nodeState.getEntries( portType ); - - if ( entries[portIndex].empty() ) return true; - - const auto outPolicy = _node->nodeDataModel()->portOutConnectionPolicy( portIndex ); - return ( portType == PortType::Out && outPolicy == NodeDataModel::ConnectionPolicy::Many ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp deleted file mode 100644 index 89f43c3e0d0..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeConnectionInteraction.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include "Connection.hpp" -#include "Node.hpp" - -namespace QtNodes { - -class DataModelRegistry; -class FlowScene; -class NodeDataModel; - -/// Class performs various operations on the Node and Connection pair. -/// An instance should be created on the stack and destroyed when -/// the operation is completed -class NodeConnectionInteraction -{ - public: - NodeConnectionInteraction( Node& node, Connection& connection, FlowScene& scene ); - - /// Can connect when following conditions are met: - /// 1) Connection 'requires' a port - /// 2) Connection's vacant end is above the node port - /// 3) Node port is vacant - /// 4) Connection type equals node port type, or there is a registered type conversion that can - /// translate between the two - bool canConnect( PortIndex& portIndex, TypeConverter& converter ) const; - - /// 1) Check conditions from 'canConnect' - /// 1.5) If the connection is possible but a type conversion is needed, add a converter node to - /// the scene, and connect it properly 2) Assign node to required port in Connection 3) Assign - /// Connection to empty port in NodeState 4) Adjust Connection geometry 5) Poke model to - /// initiate data transfer - bool tryConnect() const; - - /// 1) Node and Connection should be already connected - /// 2) If so, clear Connection entry in the NodeState - /// 3) Propagate invalid data to IN node - /// 4) Set Connection end to 'requiring a port' - bool disconnect( PortType portToDisconnect ) const; - - private: - PortType connectionRequiredPort() const; - - QPointF connectionEndScenePosition( PortType ) const; - - QPointF nodePortScenePosition( PortType portType, PortIndex portIndex ) const; - - PortIndex nodePortIndexUnderScenePoint( PortType portType, QPointF const& p ) const; - - bool nodePortIsEmpty( PortType portType, PortIndex portIndex ) const; - - private: - Node* _node; - - Connection* _connection; - - FlowScene* _scene; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp deleted file mode 100644 index 3b6742cc8ec..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeDataModel.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "NodeDataModel.hpp" - -#include "StyleCollection.hpp" - -using QtNodes::NodeDataModel; -using QtNodes::NodeStyle; - -NodeDataModel::NodeDataModel() : _nodeStyle( StyleCollection::nodeStyle() ) { - // Derived classes can initialize specific style here -} - -QJsonObject NodeDataModel::save() const { - QJsonObject modelJson; - - modelJson["name"] = name(); - - return modelJson; -} - -NodeStyle const& NodeDataModel::nodeStyle() const { - return _nodeStyle; -} - -void NodeDataModel::setNodeStyle( NodeStyle const& style ) { - _nodeStyle = style; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp deleted file mode 100644 index 2fd453fb5ff..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeGeometry.cpp +++ /dev/null @@ -1,279 +0,0 @@ -#include "NodeGeometry.hpp" - -#include -#include - -#include "Node.hpp" -#include "NodeDataModel.hpp" -#include "NodeGraphicsObject.hpp" -#include "NodeState.hpp" -#include "PortType.hpp" - -#include "StyleCollection.hpp" - -using QtNodes::Node; -using QtNodes::NodeDataModel; -using QtNodes::NodeGeometry; -using QtNodes::PortIndex; -using QtNodes::PortType; - -NodeGeometry::NodeGeometry( std::unique_ptr const& dataModel ) : - _width( 100 ), - _height( 150 ), - _inputPortWidth( 70 ), - _outputPortWidth( 70 ), - _entryHeight( 20 ), - _spacing( 20 ), - _hovered( false ), - _nSources( dataModel->nPorts( PortType::Out ) ), - _nSinks( dataModel->nPorts( PortType::In ) ), - _draggingPos( -1000, -1000 ), - _dataModel( dataModel ), - _fontMetrics( QFont() ), - _boldFontMetrics( QFont() ) { - QFont f; - f.setBold( true ); - - _boldFontMetrics = QFontMetrics( f ); -} - -unsigned int NodeGeometry::nSources() const { - return _dataModel->nPorts( PortType::Out ); -} - -unsigned int NodeGeometry::nSinks() const { - return _dataModel->nPorts( PortType::In ); -} - -QRectF NodeGeometry::entryBoundingRect() const { - double const addon = 0.0; - - return QRectF( 0 - addon, 0 - addon, _entryWidth + 2 * addon, _entryHeight + 2 * addon ); -} - -QRectF NodeGeometry::boundingRect() const { - auto const& nodeStyle = StyleCollection::nodeStyle(); - - double addon = 4 * nodeStyle.ConnectionPointDiameter; - - return QRectF( 0 - addon, 0 - addon, _width + 2 * addon, _height + 2 * addon ); -} - -void NodeGeometry::recalculateSize() const { - _entryHeight = _fontMetrics.height(); - - { - unsigned int maxNumOfEntries = std::max( _nSinks, _nSources ); - unsigned int step = _entryHeight + _spacing; - _height = step * maxNumOfEntries; - } - - if ( auto w = _dataModel->embeddedWidget() ) { - _height = std::max( _height, static_cast( w->height() ) ); - } - - _height += captionHeight(); - - _inputPortWidth = portWidth( PortType::In ); - _outputPortWidth = portWidth( PortType::Out ); - - _width = _inputPortWidth + _outputPortWidth + 2 * _spacing; - - if ( auto w = _dataModel->embeddedWidget() ) { _width += w->width(); } - - _width = std::max( _width, captionWidth() ); - - if ( _dataModel->validationState() != NodeValidationState::Valid ) { - _width = std::max( _width, validationWidth() ); - _height += validationHeight() + _spacing; - } -} - -void NodeGeometry::recalculateSize( QFont const& font ) const { - QFontMetrics fontMetrics( font ); - QFont boldFont = font; - - boldFont.setBold( true ); - - QFontMetrics boldFontMetrics( boldFont ); - - if ( _boldFontMetrics != boldFontMetrics ) { - _fontMetrics = fontMetrics; - _boldFontMetrics = boldFontMetrics; - - recalculateSize(); - } -} - -QPointF -NodeGeometry::portScenePosition( PortIndex index, PortType portType, QTransform const& t ) const { - auto const& nodeStyle = StyleCollection::nodeStyle(); - - unsigned int step = _entryHeight + _spacing; - - QPointF result; - - double totalHeight = 0.0; - - totalHeight += captionHeight(); - - totalHeight += step * index; - - // TODO: why? - totalHeight += step / 2.0; - - switch ( portType ) { - case PortType::Out: { - double x = _width + nodeStyle.ConnectionPointDiameter; - - result = QPointF( x, totalHeight ); - break; - } - - case PortType::In: { - double x = 0.0 - nodeStyle.ConnectionPointDiameter; - - result = QPointF( x, totalHeight ); - break; - } - - default: - break; - } - - return t.map( result ); -} - -PortIndex NodeGeometry::checkHitScenePoint( PortType portType, - QPointF const scenePoint, - QTransform const& sceneTransform ) const { - auto const& nodeStyle = StyleCollection::nodeStyle(); - - PortIndex result = INVALID; - - if ( portType == PortType::None ) return result; - - double const tolerance = 2.0 * nodeStyle.ConnectionPointDiameter; - - unsigned int const nItems = _dataModel->nPorts( portType ); - - for ( unsigned int i = 0; i < nItems; ++i ) { - auto pp = portScenePosition( i, portType, sceneTransform ); - - QPointF p = pp - scenePoint; - auto distance = std::sqrt( QPointF::dotProduct( p, p ) ); - - if ( distance < tolerance ) { - result = PortIndex( i ); - break; - } - } - - return result; -} - -QRect NodeGeometry::resizeRect() const { - unsigned int rectSize = 7; - - return QRect( _width - rectSize, _height - rectSize, rectSize, rectSize ); -} - -QPointF NodeGeometry::widgetPosition() const { - if ( auto w = _dataModel->embeddedWidget() ) { - if ( w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag ) { - // If the widget wants to use as much vertical space as possible, place it immediately - // after the caption. - return QPointF( _spacing + portWidth( PortType::In ), captionHeight() ); - } - else { - if ( _dataModel->validationState() != NodeValidationState::Valid ) { - return QPointF( - _spacing + portWidth( PortType::In ), - ( captionHeight() + _height - validationHeight() - _spacing - w->height() ) / - 2.0 ); - } - - return QPointF( _spacing + portWidth( PortType::In ), - ( captionHeight() + _height - w->height() ) / 2.0 ); - } - } - return QPointF(); -} - -int NodeGeometry::equivalentWidgetHeight() const { - if ( _dataModel->validationState() != NodeValidationState::Valid ) { - return height() - captionHeight() + validationHeight(); - } - - return height() - captionHeight(); -} - -unsigned int NodeGeometry::captionHeight() const { - if ( !_dataModel->captionVisible() ) return 0; - - QString name = _dataModel->caption(); - - return _boldFontMetrics.boundingRect( name ).height(); -} - -unsigned int NodeGeometry::captionWidth() const { - if ( !_dataModel->captionVisible() ) return 0; - - QString name = _dataModel->caption(); - - return _boldFontMetrics.boundingRect( name ).width(); -} - -unsigned int NodeGeometry::validationHeight() const { - QString msg = _dataModel->validationMessage(); - - return _boldFontMetrics.boundingRect( msg ).height(); -} - -unsigned int NodeGeometry::validationWidth() const { - QString msg = _dataModel->validationMessage(); - - return _boldFontMetrics.boundingRect( msg ).width(); -} - -QPointF NodeGeometry::calculateNodePositionBetweenNodePorts( PortIndex targetPortIndex, - PortType targetPort, - Node* targetNode, - PortIndex sourcePortIndex, - PortType sourcePort, - Node* sourceNode, - Node& newNode ) { - // Calculating the nodes position in the scene. It'll be positioned half way between the two - // ports that it "connects". The first line calculates the halfway point between the ports (node - // position + port position on the node for both nodes averaged). The second line offsets this - // coordinate with the size of the new node, so that the new nodes center falls on the - // originally calculated coordinate, instead of it's upper left corner. - auto converterNodePos = - ( sourceNode->nodeGraphicsObject().pos() + - sourceNode->nodeGeometry().portScenePosition( sourcePortIndex, sourcePort ) + - targetNode->nodeGraphicsObject().pos() + - targetNode->nodeGeometry().portScenePosition( targetPortIndex, targetPort ) ) / - 2.0f; - converterNodePos.setX( converterNodePos.x() - newNode.nodeGeometry().width() / 2.0f ); - converterNodePos.setY( converterNodePos.y() - newNode.nodeGeometry().height() / 2.0f ); - return converterNodePos; -} - -unsigned int NodeGeometry::portWidth( PortType portType ) const { - unsigned width = 0; - - for ( auto i = 0ul; i < _dataModel->nPorts( portType ); ++i ) { - QString name; - - if ( _dataModel->portCaptionVisible( portType, i ) ) { - name = _dataModel->portCaption( portType, i ); - } - else { - name = _dataModel->dataType( portType, i ).name; - } - - width = std::max( unsigned( _fontMetrics.horizontalAdvance( name ) ), width ); - } - - return width; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp deleted file mode 100644 index 97498dea8c2..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeGraphicsObject.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "NodeGraphicsObject.hpp" - -#include -#include - -#include -#include - -#include "ConnectionGraphicsObject.hpp" -#include "ConnectionState.hpp" - -#include "FlowScene.hpp" -#include "NodePainter.hpp" - -#include "Node.hpp" -#include "NodeConnectionInteraction.hpp" -#include "NodeDataModel.hpp" - -#include "StyleCollection.hpp" - -using QtNodes::FlowScene; -using QtNodes::Node; -using QtNodes::NodeGraphicsObject; - -NodeGraphicsObject::NodeGraphicsObject( FlowScene& scene, Node& node ) : - _scene( scene ), _node( node ), _locked( false ), _proxyWidget( nullptr ) { - _scene.addItem( this ); - - setFlag( QGraphicsItem::ItemDoesntPropagateOpacityToChildren, true ); - setFlag( QGraphicsItem::ItemIsMovable, true ); - setFlag( QGraphicsItem::ItemIsFocusable, true ); - setFlag( QGraphicsItem::ItemIsSelectable, true ); - setFlag( QGraphicsItem::ItemSendsScenePositionChanges, true ); - - setCacheMode( QGraphicsItem::DeviceCoordinateCache ); - - auto const& nodeStyle = node.nodeDataModel()->nodeStyle(); - - { - auto effect = new QGraphicsDropShadowEffect; - effect->setOffset( 4, 4 ); - effect->setBlurRadius( 20 ); - effect->setColor( nodeStyle.ShadowColor ); - - setGraphicsEffect( effect ); - } - - setOpacity( nodeStyle.Opacity ); - - setAcceptHoverEvents( true ); - - setZValue( 0 ); - - embedQWidget(); - - // connect to the move signals to emit the move signals in FlowScene - auto onMoveSlot = [this] { _scene.nodeMoved( _node, pos() ); }; - connect( this, &QGraphicsObject::xChanged, this, onMoveSlot ); - connect( this, &QGraphicsObject::yChanged, this, onMoveSlot ); -} - -NodeGraphicsObject::~NodeGraphicsObject() { - _scene.removeItem( this ); -} - -Node& NodeGraphicsObject::node() { - return _node; -} - -Node const& NodeGraphicsObject::node() const { - return _node; -} - -void NodeGraphicsObject::embedQWidget() { - NodeGeometry& geom = _node.nodeGeometry(); - - if ( auto w = _node.nodeDataModel()->embeddedWidget() ) { - _proxyWidget = new QGraphicsProxyWidget( this ); - - _proxyWidget->setWidget( w ); - - _proxyWidget->setPreferredWidth( 5 ); - - geom.recalculateSize(); - - if ( w->sizePolicy().verticalPolicy() & QSizePolicy::ExpandFlag ) { - // If the widget wants to use as much vertical space as possible, set it to have the - // geom's equivalentWidgetHeight. - _proxyWidget->setMinimumHeight( geom.equivalentWidgetHeight() ); - } - - _proxyWidget->setPos( geom.widgetPosition() ); - - update(); - - _proxyWidget->setOpacity( 1.0 ); - _proxyWidget->setFlag( QGraphicsItem::ItemIgnoresParentOpacity ); - } -} - -QRectF NodeGraphicsObject::boundingRect() const { - return _node.nodeGeometry().boundingRect(); -} - -void NodeGraphicsObject::setGeometryChanged() { - prepareGeometryChange(); -} - -void NodeGraphicsObject::moveConnections() const { - NodeState const& nodeState = _node.nodeState(); - - for ( PortType portType : { PortType::In, PortType::Out } ) { - auto const& connectionEntries = nodeState.getEntries( portType ); - - for ( auto const& connections : connectionEntries ) { - for ( auto& con : connections ) - con.second->getConnectionGraphicsObject().move(); - } - } -} - -void NodeGraphicsObject::lock( bool locked ) { - _locked = locked; - - setFlag( QGraphicsItem::ItemIsMovable, !locked ); - setFlag( QGraphicsItem::ItemIsFocusable, !locked ); - setFlag( QGraphicsItem::ItemIsSelectable, !locked ); -} - -void NodeGraphicsObject::paint( QPainter* painter, - QStyleOptionGraphicsItem const* option, - QWidget* ) { - painter->setClipRect( option->exposedRect ); - - NodePainter::paint( painter, _node, _scene ); -} - -QVariant NodeGraphicsObject::itemChange( GraphicsItemChange change, const QVariant& value ) { - if ( change == ItemPositionChange && scene() ) { moveConnections(); } - - return QGraphicsItem::itemChange( change, value ); -} - -void NodeGraphicsObject::mousePressEvent( QGraphicsSceneMouseEvent* event ) { - if ( _locked ) return; - - // deselect all other items after this one is selected - if ( !isSelected() && !( event->modifiers() & Qt::ControlModifier ) ) { - _scene.clearSelection(); - } - - for ( PortType portToCheck : { PortType::In, PortType::Out } ) { - NodeGeometry const& nodeGeometry = _node.nodeGeometry(); - - // TODO do not pass sceneTransform - int const portIndex = - nodeGeometry.checkHitScenePoint( portToCheck, event->scenePos(), sceneTransform() ); - - if ( portIndex != INVALID ) { - NodeState const& nodeState = _node.nodeState(); - - std::unordered_map connections = - nodeState.connections( portToCheck, portIndex ); - - // start dragging existing connection - if ( !connections.empty() && portToCheck == PortType::In ) { - auto con = connections.begin()->second; - - NodeConnectionInteraction interaction( _node, *con, _scene ); - - interaction.disconnect( portToCheck ); - } - else // initialize new Connection - { - if ( portToCheck == PortType::Out ) { - auto const outPolicy = - _node.nodeDataModel()->portOutConnectionPolicy( portIndex ); - if ( !connections.empty() && - outPolicy == NodeDataModel::ConnectionPolicy::One ) { - _scene.deleteConnection( *connections.begin()->second ); - } - } - - // todo add to FlowScene - auto connection = _scene.createConnection( portToCheck, _node, portIndex ); - - _node.nodeState().setConnection( portToCheck, portIndex, *connection ); - - connection->getConnectionGraphicsObject().grabMouse(); - } - } - } - - auto pos = event->pos(); - auto& geom = _node.nodeGeometry(); - auto& state = _node.nodeState(); - - if ( _node.nodeDataModel()->resizable() && - geom.resizeRect().contains( QPoint( pos.x(), pos.y() ) ) ) { - state.setResizing( true ); - } -} - -void NodeGraphicsObject::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) { - auto& geom = _node.nodeGeometry(); - auto& state = _node.nodeState(); - - if ( state.resizing() ) { - auto diff = event->pos() - event->lastPos(); - - if ( auto w = _node.nodeDataModel()->embeddedWidget() ) { - prepareGeometryChange(); - - auto oldSize = w->size(); - - oldSize += QSize( diff.x(), diff.y() ); - - w->setFixedSize( oldSize ); - - _proxyWidget->setMinimumSize( oldSize ); - _proxyWidget->setMaximumSize( oldSize ); - _proxyWidget->setPos( geom.widgetPosition() ); - - geom.recalculateSize(); - update(); - - moveConnections(); - - event->accept(); - } - } - else { - QGraphicsObject::mouseMoveEvent( event ); - - if ( event->lastPos() != event->pos() ) moveConnections(); - - event->ignore(); - } - - QRectF r = scene()->sceneRect(); - - r = r.united( mapToScene( boundingRect() ).boundingRect() ); - - scene()->setSceneRect( r ); -} - -void NodeGraphicsObject::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) { - auto& state = _node.nodeState(); - - state.setResizing( false ); - - QGraphicsObject::mouseReleaseEvent( event ); - - // position connections precisely after fast node move - moveConnections(); -} - -void NodeGraphicsObject::hoverEnterEvent( QGraphicsSceneHoverEvent* event ) { - // bring all the colliding nodes to background - QList overlapItems = collidingItems(); - - for ( QGraphicsItem* item : overlapItems ) { - if ( item->zValue() > 0.0 ) { item->setZValue( 0.0 ); } - } - - // bring this node forward - setZValue( 1.0 ); - - _node.nodeGeometry().setHovered( true ); - update(); - _scene.nodeHovered( node(), event->screenPos() ); - event->accept(); -} - -void NodeGraphicsObject::hoverLeaveEvent( QGraphicsSceneHoverEvent* event ) { - _node.nodeGeometry().setHovered( false ); - update(); - _scene.nodeHoverLeft( node() ); - event->accept(); -} - -void NodeGraphicsObject::hoverMoveEvent( QGraphicsSceneHoverEvent* event ) { - auto pos = event->pos(); - auto& geom = _node.nodeGeometry(); - - if ( _node.nodeDataModel()->resizable() && - geom.resizeRect().contains( QPoint( pos.x(), pos.y() ) ) ) { - setCursor( QCursor( Qt::SizeFDiagCursor ) ); - } - else { - setCursor( QCursor() ); - } - - event->accept(); -} - -void NodeGraphicsObject::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) { - QGraphicsItem::mouseDoubleClickEvent( event ); - - _scene.nodeDoubleClicked( node() ); -} - -void NodeGraphicsObject::contextMenuEvent( QGraphicsSceneContextMenuEvent* event ) { - _scene.nodeContextMenu( node(), mapToScene( event->pos() ) ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp deleted file mode 100644 index 550466eea34..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.cpp +++ /dev/null @@ -1,341 +0,0 @@ -#include "NodePainter.hpp" - -#include - -#include - -#include "FlowScene.hpp" -#include "Node.hpp" -#include "NodeDataModel.hpp" -#include "NodeGeometry.hpp" -#include "NodeGraphicsObject.hpp" -#include "NodeState.hpp" -#include "PortType.hpp" -#include "StyleCollection.hpp" - -using QtNodes::FlowScene; -using QtNodes::Node; -using QtNodes::NodeDataModel; -using QtNodes::NodeGeometry; -using QtNodes::NodeGraphicsObject; -using QtNodes::NodePainter; -using QtNodes::NodeState; - -void NodePainter::paint( QPainter* painter, Node& node, FlowScene const& scene ) { - NodeGeometry const& geom = node.nodeGeometry(); - - NodeState const& state = node.nodeState(); - - NodeGraphicsObject const& graphicsObject = node.nodeGraphicsObject(); - - geom.recalculateSize( painter->font() ); - - //-------------------------------------------- - NodeDataModel const* model = node.nodeDataModel(); - - drawNodeRect( painter, geom, model, graphicsObject ); - - drawConnectionPoints( painter, geom, state, model, scene ); - - drawFilledConnectionPoints( painter, geom, state, model ); - - drawModelName( painter, geom, state, model ); - - drawEntryLabels( painter, geom, state, model ); - - drawResizeRect( painter, geom, model ); - - drawValidationRect( painter, geom, model, graphicsObject ); - - /// call custom painter - if ( auto painterDelegate = model->painterDelegate() ) { - painterDelegate->paint( painter, geom, model ); - } -} - -void NodePainter::drawNodeRect( QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model, - NodeGraphicsObject const& graphicsObject ) { - NodeStyle const& nodeStyle = model->nodeStyle(); - - auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor - : nodeStyle.NormalBoundaryColor; - - if ( geom.hovered() ) { - QPen p( color, nodeStyle.HoveredPenWidth ); - painter->setPen( p ); - } - else { - QPen p( color, nodeStyle.PenWidth ); - painter->setPen( p ); - } - - QLinearGradient gradient( QPointF( 0.0, 0.0 ), QPointF( 2.0, geom.height() ) ); - - gradient.setColorAt( 0.0, nodeStyle.GradientColor0 ); - gradient.setColorAt( 0.03, nodeStyle.GradientColor1 ); - gradient.setColorAt( 0.97, nodeStyle.GradientColor2 ); - gradient.setColorAt( 1.0, nodeStyle.GradientColor3 ); - - painter->setBrush( gradient ); - - float diam = nodeStyle.ConnectionPointDiameter; - - QRectF boundary( -diam, -diam, 2.0 * diam + geom.width(), 2.0 * diam + geom.height() ); - - double const radius = 3.0; - - painter->drawRoundedRect( boundary, radius, radius ); -} - -void NodePainter::drawConnectionPoints( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model, - FlowScene const& scene ) { - NodeStyle const& nodeStyle = model->nodeStyle(); - auto const& connectionStyle = StyleCollection::connectionStyle(); - - float diameter = nodeStyle.ConnectionPointDiameter; - auto reducedDiameter = diameter * 0.6; - - for ( PortType portType : { PortType::Out, PortType::In } ) { - size_t n = state.getEntries( portType ).size(); - - for ( unsigned int i = 0; i < n; ++i ) { - QPointF p = geom.portScenePosition( i, portType ); - - auto const& dataType = model->dataType( portType, i ); - - bool canConnect = - ( state.getEntries( portType )[i].empty() || - ( portType == PortType::Out && model->portOutConnectionPolicy( i ) == - NodeDataModel::ConnectionPolicy::Many ) ); - - double r = 1.0; - if ( state.isReacting() && canConnect && portType == state.reactingPortType() ) { - - auto diff = geom.draggingPos() - p; - double dist = std::sqrt( QPointF::dotProduct( diff, diff ) ); - bool typeConvertable = false; - - { - if ( portType == PortType::In ) { - typeConvertable = scene.registry().getTypeConverter( - state.reactingDataType(), dataType ) != nullptr; - } - else { - typeConvertable = scene.registry().getTypeConverter( - dataType, state.reactingDataType() ) != nullptr; - } - } - - if ( state.reactingDataType().id == dataType.id || typeConvertable ) { - double const thres = 40.0; - r = ( dist < thres ) ? ( 2.0 - dist / thres ) : 1.0; - } - else { - double const thres = 80.0; - r = ( dist < thres ) ? ( dist / thres ) : 1.0; - } - } - - if ( connectionStyle.useDataDefinedColors() ) { - painter->setBrush( connectionStyle.normalColor( dataType.id ) ); - } - else { - painter->setBrush( nodeStyle.ConnectionPointColor ); - } - - painter->drawEllipse( p, reducedDiameter * r, reducedDiameter * r ); - } - }; -} - -void NodePainter::drawFilledConnectionPoints( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model ) { - NodeStyle const& nodeStyle = model->nodeStyle(); - auto const& connectionStyle = StyleCollection::connectionStyle(); - - auto diameter = nodeStyle.ConnectionPointDiameter; - - for ( PortType portType : { PortType::Out, PortType::In } ) { - size_t n = state.getEntries( portType ).size(); - - for ( size_t i = 0; i < n; ++i ) { - QPointF p = geom.portScenePosition( i, portType ); - - if ( !state.getEntries( portType )[i].empty() ) { - auto const& dataType = model->dataType( portType, i ); - - if ( connectionStyle.useDataDefinedColors() ) { - QColor const c = connectionStyle.normalColor( dataType.id ); - painter->setPen( c ); - painter->setBrush( c ); - } - else { - painter->setPen( nodeStyle.FilledConnectionPointColor ); - painter->setBrush( nodeStyle.FilledConnectionPointColor ); - } - - painter->drawEllipse( p, diameter * 0.4, diameter * 0.4 ); - } - } - } -} - -void NodePainter::drawModelName( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model ) { - NodeStyle const& nodeStyle = model->nodeStyle(); - - Q_UNUSED( state ); - - if ( !model->captionVisible() ) return; - - QString const& name = model->caption(); - - QFont f = painter->font(); - - f.setBold( true ); - - QFontMetrics metrics( f ); - - auto rect = metrics.boundingRect( name ); - - QPointF position( ( geom.width() - rect.width() ) / 2.0, - ( geom.spacing() + geom.entryHeight() ) / 3.0 ); - - painter->setFont( f ); - painter->setPen( nodeStyle.FontColor ); - painter->drawText( position, name ); - - f.setBold( false ); - painter->setFont( f ); -} - -void NodePainter::drawEntryLabels( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model ) { - QFontMetrics const& metrics = painter->fontMetrics(); - - for ( PortType portType : { PortType::Out, PortType::In } ) { - auto const& nodeStyle = model->nodeStyle(); - - auto& entries = state.getEntries( portType ); - - size_t n = entries.size(); - - for ( size_t i = 0; i < n; ++i ) { - QPointF p = geom.portScenePosition( i, portType ); - - if ( entries[i].empty() ) - painter->setPen( nodeStyle.FontColorFaded ); - else - painter->setPen( nodeStyle.FontColor ); - - QString s; - - if ( model->portCaptionVisible( portType, i ) ) { - s = model->portCaption( portType, i ); - } - else { - s = model->dataType( portType, i ).name; - } - - auto rect = metrics.boundingRect( s ); - - p.setY( p.y() + rect.height() / 4.0 ); - - switch ( portType ) { - case PortType::In: - p.setX( 5.0 ); - break; - - case PortType::Out: - p.setX( geom.width() - 5.0 - rect.width() ); - break; - - default: - break; - } - - painter->drawText( p, s ); - } - } -} - -void NodePainter::drawResizeRect( QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model ) { - if ( model->resizable() ) { - painter->setBrush( Qt::gray ); - - painter->drawEllipse( geom.resizeRect() ); - } -} - -void NodePainter::drawValidationRect( QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model, - NodeGraphicsObject const& graphicsObject ) { - auto modelValidationState = model->validationState(); - - if ( modelValidationState != NodeValidationState::Valid ) { - NodeStyle const& nodeStyle = model->nodeStyle(); - - auto color = graphicsObject.isSelected() ? nodeStyle.SelectedBoundaryColor - : nodeStyle.NormalBoundaryColor; - - if ( geom.hovered() ) { - QPen p( color, nodeStyle.HoveredPenWidth ); - painter->setPen( p ); - } - else { - QPen p( color, nodeStyle.PenWidth ); - painter->setPen( p ); - } - - // Drawing the validation message background - if ( modelValidationState == NodeValidationState::Error ) { - painter->setBrush( nodeStyle.ErrorColor ); - } - else { - painter->setBrush( nodeStyle.WarningColor ); - } - - double const radius = 3.0; - - float diam = nodeStyle.ConnectionPointDiameter; - - QRectF boundary( -diam, - -diam + geom.height() - geom.validationHeight(), - 2.0 * diam + geom.width(), - 2.0 * diam + geom.validationHeight() ); - - painter->drawRoundedRect( boundary, radius, radius ); - - painter->setBrush( Qt::gray ); - - // Drawing the validation message itself - QString const& errorMsg = model->validationMessage(); - - QFont f = painter->font(); - - QFontMetrics metrics( f ); - - auto rect = metrics.boundingRect( errorMsg ); - - QPointF position( ( geom.width() - rect.width() ) / 2.0, - geom.height() - ( geom.validationHeight() - diam ) / 2.0 ); - - painter->setFont( f ); - painter->setPen( nodeStyle.FontColor ); - painter->drawText( position, errorMsg ); - } -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp deleted file mode 100644 index 6923a660e4b..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodePainter.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include - -namespace QtNodes { - -class Node; -class NodeState; -class NodeGeometry; -class NodeGraphicsObject; -class NodeDataModel; -class FlowItemEntry; -class FlowScene; - -class NodePainter -{ - public: - NodePainter(); - - public: - static void paint( QPainter* painter, Node& node, FlowScene const& scene ); - - static void drawNodeRect( QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model, - NodeGraphicsObject const& graphicsObject ); - - static void drawModelName( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model ); - - static void drawEntryLabels( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model ); - - static void drawConnectionPoints( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model, - FlowScene const& scene ); - - static void drawFilledConnectionPoints( QPainter* painter, - NodeGeometry const& geom, - NodeState const& state, - NodeDataModel const* model ); - - static void - drawResizeRect( QPainter* painter, NodeGeometry const& geom, NodeDataModel const* model ); - - static void drawValidationRect( QPainter* painter, - NodeGeometry const& geom, - NodeDataModel const* model, - NodeGraphicsObject const& graphicsObject ); -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp deleted file mode 100644 index f41c08f6d97..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeState.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "NodeState.hpp" - -#include "NodeDataModel.hpp" - -#include "Connection.hpp" - -using QtNodes::Connection; -using QtNodes::NodeDataModel; -using QtNodes::NodeDataType; -using QtNodes::NodeState; -using QtNodes::PortIndex; -using QtNodes::PortType; - -NodeState::NodeState( std::unique_ptr const& model ) : - _inConnections( model->nPorts( PortType::In ) ), - _outConnections( model->nPorts( PortType::Out ) ), - _reaction( NOT_REACTING ), - _reactingPortType( PortType::None ), - _resizing( false ) {} - -std::vector const& NodeState::getEntries( PortType portType ) const { - if ( portType == PortType::In ) - return _inConnections; - else - return _outConnections; -} - -std::vector& NodeState::getEntries( PortType portType ) { - if ( portType == PortType::In ) - return _inConnections; - else - return _outConnections; -} - -NodeState::ConnectionPtrSet NodeState::connections( PortType portType, PortIndex portIndex ) const { - auto const& connections = getEntries( portType ); - - return connections[portIndex]; -} - -void NodeState::setConnection( PortType portType, PortIndex portIndex, Connection& connection ) { - auto& connections = getEntries( portType ); - - connections.at( portIndex ).insert( std::make_pair( connection.id(), &connection ) ); -} - -void NodeState::eraseConnection( PortType portType, PortIndex portIndex, QUuid id ) { - getEntries( portType )[portIndex].erase( id ); -} - -NodeState::ReactToConnectionState NodeState::reaction() const { - return _reaction; -} - -PortType NodeState::reactingPortType() const { - return _reactingPortType; -} - -NodeDataType NodeState::reactingDataType() const { - return _reactingDataType; -} - -void NodeState::setReaction( ReactToConnectionState reaction, - PortType reactingPortType, - NodeDataType reactingDataType ) { - _reaction = reaction; - - _reactingPortType = reactingPortType; - - _reactingDataType = std::move( reactingDataType ); -} - -bool NodeState::isReacting() const { - return _reaction == REACTING; -} - -void NodeState::setResizing( bool resizing ) { - _resizing = resizing; -} - -bool NodeState::resizing() const { - return _resizing; -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp deleted file mode 100644 index 2fc9ef460f8..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/NodeStyle.cpp +++ /dev/null @@ -1,119 +0,0 @@ -#include "NodeStyle.hpp" - -#include - -#include -#include -#include -#include -#include - -#include - -#include "StyleCollection.hpp" - -using QtNodes::NodeStyle; - -inline void initResources() { - Q_INIT_RESOURCE( resources ); -} - -NodeStyle::NodeStyle() { - // Explicit resources inialization for preventing the static initialization - // order fiasco: https://isocpp.org/wiki/faq/ctors#static-init-order - initResources(); - - // This configuration is stored inside the compiled unit and is loaded statically - loadJsonFile( ":DefaultStyle.json" ); -} - -NodeStyle::NodeStyle( QString jsonText ) { - loadJsonText( jsonText ); -} - -void NodeStyle::setNodeStyle( QString jsonText ) { - NodeStyle style( jsonText ); - - StyleCollection::setNodeStyle( style ); -} - -#ifdef STYLE_DEBUG -# define NODE_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) \ - { \ - if ( v.type() == QJsonValue::Undefined || v.type() == QJsonValue::Null ) \ - qWarning() << "Undefined value for parameter:" << #variable; \ - } -#else -# define NODE_STYLE_CHECK_UNDEFINED_VALUE( v, variable ) -#endif - -#define NODE_STYLE_READ_COLOR( values, variable ) \ - { \ - auto valueRef = values[#variable]; \ - NODE_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ - if ( valueRef.isArray() ) { \ - auto colorArray = valueRef.toArray(); \ - std::vector rgb; \ - rgb.reserve( 3 ); \ - for ( auto it = colorArray.begin(); it != colorArray.end(); ++it ) { \ - rgb.push_back( ( *it ).toInt() ); \ - } \ - variable = QColor( rgb[0], rgb[1], rgb[2] ); \ - } \ - else { \ - variable = QColor( valueRef.toString() ); \ - } \ - } - -#define NODE_STYLE_READ_FLOAT( values, variable ) \ - { \ - auto valueRef = values[#variable]; \ - NODE_STYLE_CHECK_UNDEFINED_VALUE( valueRef, variable ) \ - variable = valueRef.toDouble(); \ - } - -void NodeStyle::loadJsonFile( QString styleFile ) { - QFile file( styleFile ); - - if ( !file.open( QIODevice::ReadOnly ) ) { - qWarning() << "Couldn't open file " << styleFile; - - return; - } - - loadJsonFromByteArray( file.readAll() ); -} - -void NodeStyle::loadJsonText( QString jsonText ) { - loadJsonFromByteArray( jsonText.toUtf8() ); -} - -void NodeStyle::loadJsonFromByteArray( QByteArray const& byteArray ) { - QJsonDocument json( QJsonDocument::fromJson( byteArray ) ); - - QJsonObject topLevelObject = json.object(); - - QJsonValueRef nodeStyleValues = topLevelObject["NodeStyle"]; - - QJsonObject obj = nodeStyleValues.toObject(); - - NODE_STYLE_READ_COLOR( obj, NormalBoundaryColor ); - NODE_STYLE_READ_COLOR( obj, SelectedBoundaryColor ); - NODE_STYLE_READ_COLOR( obj, GradientColor0 ); - NODE_STYLE_READ_COLOR( obj, GradientColor1 ); - NODE_STYLE_READ_COLOR( obj, GradientColor2 ); - NODE_STYLE_READ_COLOR( obj, GradientColor3 ); - NODE_STYLE_READ_COLOR( obj, ShadowColor ); - NODE_STYLE_READ_COLOR( obj, FontColor ); - NODE_STYLE_READ_COLOR( obj, FontColorFaded ); - NODE_STYLE_READ_COLOR( obj, ConnectionPointColor ); - NODE_STYLE_READ_COLOR( obj, FilledConnectionPointColor ); - NODE_STYLE_READ_COLOR( obj, WarningColor ); - NODE_STYLE_READ_COLOR( obj, ErrorColor ); - - NODE_STYLE_READ_FLOAT( obj, PenWidth ); - NODE_STYLE_READ_FLOAT( obj, HoveredPenWidth ); - NODE_STYLE_READ_FLOAT( obj, ConnectionPointDiameter ); - - NODE_STYLE_READ_FLOAT( obj, Opacity ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp deleted file mode 100644 index a4461ad1d59..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/Properties.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include "Properties.hpp" - -using QtNodes::Properties; - -void Properties::put( QString const& name, QVariant const& v ) { - _values.insert( name, v ); -} diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp b/src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp deleted file mode 100644 index 6aa44730393..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/Properties.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include - -#include - -#include "Export.hpp" - -namespace QtNodes { - -class NODE_EDITOR_PUBLIC Properties -{ - public: - void put( QString const& name, QVariant const& v ); - - template - bool get( QString name, T* v ) const { - QVariant const& var = _values[name]; - - if ( var.canConvert() ) { - *v = _values[name].value(); - - return true; - } - - return false; - } - - QVariantMap const& values() const { return _values; } - - QVariantMap& values() { return _values; } - - private: - QVariantMap _values; -}; -} // namespace QtNodes diff --git a/src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp b/src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp deleted file mode 100644 index f7aacf32f28..00000000000 --- a/src/Dataflow/QtGui/QtNodeEditor/src/StyleCollection.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "StyleCollection.hpp" - -using QtNodes::ConnectionStyle; -using QtNodes::FlowViewStyle; -using QtNodes::NodeStyle; -using QtNodes::StyleCollection; - -NodeStyle const& StyleCollection::nodeStyle() { - return instance()._nodeStyle; -} - -ConnectionStyle const& StyleCollection::connectionStyle() { - return instance()._connectionStyle; -} - -FlowViewStyle const& StyleCollection::flowViewStyle() { - return instance()._flowViewStyle; -} - -void StyleCollection::setNodeStyle( NodeStyle nodeStyle ) { - instance()._nodeStyle = nodeStyle; -} - -void StyleCollection::setConnectionStyle( ConnectionStyle connectionStyle ) { - instance()._connectionStyle = connectionStyle; -} - -void StyleCollection::setFlowViewStyle( FlowViewStyle flowViewStyle ) { - instance()._flowViewStyle = flowViewStyle; -} - -StyleCollection& StyleCollection::instance() { - static StyleCollection collection; - - return collection; -} From 5fe98796d3d6fd7ef19e507397b4c76264ba3f51 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Oct 2022 22:54:32 +0200 Subject: [PATCH 024/239] [dataflow] simplifies a bit Node interface --- .../GraphEditor/MainWindow.cpp | 8 +- src/Dataflow/Core/DataflowGraph.cpp | 92 +++++----- src/Dataflow/Core/DataflowGraph.hpp | 12 +- src/Dataflow/Core/Node.hpp | 163 +++++++++++------- src/Dataflow/Core/Node.inl | 41 +++-- .../Nodes/Sources/SingleDataSourceNode.inl | 4 +- src/Dataflow/Core/Port.inl | 3 +- .../QtGui/GraphEditor/GraphEditorView.cpp | 6 +- 8 files changed, 180 insertions(+), 149 deletions(-) diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/examples/DataflowExamples/GraphEditor/MainWindow.cpp index c05b4196c21..2855c876f7f 100644 --- a/examples/DataflowExamples/GraphEditor/MainWindow.cpp +++ b/examples/DataflowExamples/GraphEditor/MainWindow.cpp @@ -37,8 +37,6 @@ void MainWindow::newFile() { setCurrentFile( "" ); graph = new DataflowGraph( "untitled.flow" ); - // auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); - // graph->addFactory( defaultFactory->getName(), defaultFactory ); graphEdit->editGraph( graph ); } } @@ -196,14 +194,10 @@ void MainWindow::loadFile( const QString& fileName ) } graphEdit->editGraph( nullptr ); + delete graph; graph = new DataflowGraph( fileName.toStdString() ); graph->loadFromJson( fileName.toStdString() ); - - // Todo, always embed default factory in the graph - // auto defaultFactory = NodeFactoriesManager::getDataFlowBuiltInsFactory(); - // graph->addFactory( defaultFactory->getName(), defaultFactory ); - graphEdit->editGraph( graph ); QGuiApplication::restoreOverrideCursor(); diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 58358984a3c..8f87aedd734 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -252,43 +252,28 @@ bool DataflowGraph::addNode( Node* newNode ) { std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": trying to add node \"" << newNode->getInstanceName() << "\"..." << std::endl; #endif - // Check if the new node already exists (= same name) - if ( findNode( newNode->getInstanceName() ) == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": success adding node \"" << newNode->getInstanceName() << "\"!" - << std::endl; -#endif - if ( newNode->getInputs().size() == 0 ) { // Check if it is a source node - for ( auto& p : newNode->getOutputs() ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[33m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": reflecting OUTPUT port \"" << p->getName() << "\" from \"" - << newNode->getInstanceName() << "\"!" << std::endl; -#endif - auto portInterface = - p->reflect( this, newNode->getInstanceName() + '_' + p->getName() ); - // an interface port may not be linked. - // TODO allow nodes to override addInterface so that they can add this requirements - // portInterface->mustBeLinked(); - newNode->addInterface( portInterface ); - addSetter( portInterface ); + // Check if the new node already exists (= same name and type) + if ( findNode( newNode ) == -1 ) { + if ( newNode->getInputs().empty() ) { + // it is a source node, add its interface port as input and data setter to the graph + auto& interfaces = newNode->buildInterfaces( this ); + for ( auto p : interfaces ) { + addSetter( p ); } } - if ( newNode->getOutputs().size() == 0 ) { // Check if it is a sink node - for ( auto& p : newNode->getInputs() ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[34m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": reflecting INPUT port \"" << p->getName() << "\" from \"" - << newNode->getInstanceName() << "\"!" << std::endl; -#endif - auto portInterface = - p->reflect( this, newNode->getInstanceName() + '_' + p->getName() ); - newNode->addInterface( portInterface ); - addOutput( portInterface ); + if ( newNode->getOutputs().empty() ) { + // it is a sink node, add its interface port as output to the graph + auto& interfaces = newNode->buildInterfaces( this ); + for ( auto p : interfaces ) { + addGetter( p ); } } m_nodes.emplace_back( newNode ); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": success adding node \"" << newNode->getInstanceName() << "\"!" + << std::endl; +#endif return true; } else { @@ -308,7 +293,7 @@ bool DataflowGraph::removeNode( Node* node ) { #endif // Check if the new node already exists (= same name) int index = -1; - if ( ( index = findNode( node->getInstanceName() ) ) == -1 ) { + if ( ( index = findNode( node ) ) == -1 ) { #ifdef GRAPH_CALL_TRACE std::cerr << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": could not remove node \"" << node->getInstanceName() @@ -322,9 +307,10 @@ bool DataflowGraph::removeNode( Node* node ) { << "\": success removing node \"" << node->getInstanceName() << "\"!" << std::endl; #endif - if ( node->getInputs().size() == 0 ) { // Check if it is a source node - for ( auto& port : node->getInterface() ) { // Erase input ports of the graph associated - // to the interface ports of the node + if ( node->getInputs().empty() ) { // Check if it is a source node + for ( auto& port : + node->getInterfaces() ) { // Erase input ports of the graph associated + // to the interface ports of the node for ( auto itG = m_inputs.begin(); itG != m_inputs.end(); ++itG ) { if ( port->getName() == ( *itG )->getName() ) { // Check if these ports are the same @@ -334,9 +320,10 @@ bool DataflowGraph::removeNode( Node* node ) { } } } - if ( node->getOutputs().size() == 0 ) { // Check if it is a sink node - for ( auto& port : node->getInterface() ) { // Erase input ports of the graph associated - // to the interface ports of the node + if ( node->getOutputs().empty() ) { // Check if it is a sink node + for ( auto& port : + node->getInterfaces() ) { // Erase input ports of the graph associated + // to the interface ports of the node for ( auto itG = m_outputs.begin(); itG != m_outputs.end(); ++itG ) { if ( port->getName() == ( *itG )->getName() ) { // Check if these ports are the same @@ -363,7 +350,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, << std::endl; #endif // Check node "from" existence in the graph - if ( findNode( nodeFrom->getInstanceName() ) == -1 ) { + if ( findNode( nodeFrom ) == -1 ) { #ifdef GRAPH_CALL_TRACE std::cerr << "ADD LINK : node \"from\" \"" + nodeFrom->getInstanceName() + "\" does not exist." @@ -373,7 +360,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, } // Check node "to" existence in the graph - if ( findNode( nodeTo->getInstanceName() ) == -1 ) { + if ( findNode( nodeTo ) == -1 ) { #ifdef GRAPH_CALL_TRACE std::cerr << "ADD LINK : node \"to\" \"" + nodeTo->getInstanceName() + "\" does not exist." << std::endl; @@ -470,7 +457,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { // Check node's existence in the graph - if ( findNode( node->getInstanceName() ) == -1 ) { + if ( findNode( node ) == -1 ) { #ifdef GRAPH_CALL_TRACE std::cerr << "REMOVE LINK : node \"" + node->getInstanceName() + "\" does not exist." << std::endl; @@ -507,11 +494,11 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { return true; } -int DataflowGraph::findNode( const std::string& name ) { +// Todo, rewrite this method using std::find_if ? +int DataflowGraph::findNode( const Node* node ) { for ( size_t i = 0; i < m_nodes.size(); i++ ) { - if ( m_nodes[i]->getInstanceName() == name ) { return i; } + if ( *m_nodes[i] == *node ) { return i; } } - return -1; } @@ -666,6 +653,10 @@ int DataflowGraph::goThroughGraph( bool DataflowGraph::addSetter( PortBase* in ) { addInput( in ); + // TODO check if this verification is needed + // Danger : Doing this, two different nodes (not the same type) but with the same name and port + // name could not be added to the graph Danger : If not doing this,there will be some collisions + // on the data structure if ( m_dataSetters.find( in->getName() ) == m_dataSetters.end() ) { auto portOut = std::shared_ptr( in->reflect( this, in->getName() ) ); m_dataSetters.emplace( std::make_pair( @@ -677,6 +668,17 @@ bool DataflowGraph::addSetter( PortBase* in ) { return false; } +inline bool DataflowGraph::addGetter( PortBase* out ) { + if ( out->is_input() ) { return false; } + bool found = false; + // TODO check if this verification is needed ? + for ( auto& output : m_outputs ) { + if ( output->getName() == out->getName() ) { found = true; } + } + if ( !found ) { m_outputs.emplace_back( out ); } + return !found; +} + bool DataflowGraph::releaseDataSetter( std::string portName ) { auto setter = m_dataSetters.find( portName ); if ( setter != m_dataSetters.end() ) { diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 3f0920b3456..8707f891b88 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -182,10 +182,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// @param infoNodes The map that contains information about nodes. int goThroughGraph( Node* current, std::unordered_map>>& infoNodes ); - /// Returns the index of the node with the same name as the argument, if there is none, returns - /// -1. + /// Returns the index of the given node in the graph. + /// if there is none, returns -1. /// @param name The name of the node to find. - int findNode( const std::string& name ); + int findNode( const Node* node ); public: static const std::string& getTypename(); @@ -200,6 +200,12 @@ class RA_DATAFLOW_API DataflowGraph : public Node std::map m_dataGetters; bool addSetter( PortBase* in ); + + /// Adds an out port for a GRAPH. This port is also an interface port whose reference is stored + /// in the source and sink nodes of the graph. This function checks if there is no out port with + /// the same name already associated with the graph. + /// \param out The port to add. + bool addGetter( PortBase* out ); }; } // namespace Core diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index ad35d115881..910aabd269c 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -19,51 +19,81 @@ namespace Ra { namespace Dataflow { namespace Core { +/** \brief Base abstract class for all the nodes added and used by the node system. + * A node represent a function acting on some input data and generating some outputs. + * To build a computation graph, nodes should be added to the graph, which is itself a node + * (\see Ra::Dataflow::Core::DataflowGraph) and linked together through their input/output port. + * + * Nodes computes their function using the input data collecting from the input ports, + * in an evaluation context (possibly empty) defined byt their internal data to generate results + * sent to their output ports. + * + */ class RA_DATAFLOW_API Node { public: - /// Constructor. + /// \name Constructors + /// @{ + /// \brief delete default constructors. + /// \see https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual Node() = delete; Node( const Node& ) = delete; Node& operator=( const Node& ) = delete; - virtual ~Node() = default; + /// @} + /// \brief make Node a base abstract class + virtual ~Node() = default; + + /// \brief Two nodes are considered equal if there type and instance names are the same. + bool operator==( const Node& o_node ); + + /// \name Function execution control + /// @{ /// \brief Initializes the node content - /// The init() function is called once at the start of the application. + /// The init() function should be called once at the beginning of the lifetime of the node by + /// the owner of the node (the graph which contains the node). /// Its goal is to initialize the node's internal data if any. - /// The base version do nothing + /// The base version do nothing. virtual void init(); - /// \brief Executes the node - /// The execute() function on an active node is called each time the graph is executed. - /// Execute the node processing on the input ports and write the results to the output ports. + /// \brief Compile the node to check its validity + /// Only nodes defining a full computation graph will need to override this method. + /// The base version do nothing. + /// \return the compilation status + virtual bool compile(); + + /// \brief Executes the node. + /// Execute the node function on the input ports (to be fetched) and write the results to the + /// output ports. virtual void execute() = 0; /// \brief delete the node content - /// The destroy() function is called once at the end of the application. + /// The destroy() function is called once at the end of the lifetime of the node. /// Its goal is to free the internal data that have been allocated. virtual void destroy(); + /// @} - /// TODO : specify the json format for nodes and what is expected from the following ethods - - /// \brief serialize the content of the node. - /// Fill the given json object with the json representation of the concrete node. - virtual void toJson( nlohmann::json& data ) const; + /// \name Control the interfaces of the nodes (inputs, outputs, internal data, ...) + /// @{ + /// \brief Gets the in ports of the node. + const std::vector>& getInputs(); - /// \brief unserialized the content of the node. - /// Fill the node from its json representation - virtual void fromJson( const nlohmann::json& data ); + /// \brief Gets the out ports of the node. + const std::vector>& getOutputs(); - /// \brief Compile the node to check its validity - virtual bool compile(); + /// \brief Build the interface ports of the node + const std::vector& buildInterfaces( Node* parent ); - /// \bried Add a metadata to the node to store application specific informations. - /// used, e.g. by the node editor gui to save node position in the graphical canvas. - void addJsonMetaData( const nlohmann::json& data ); + /// \brief Get the interface ports of the node + const std::vector& getInterfaces(); - /// \bried Give access to extra json data stored on the node. - nlohmann::json& getJsonMetaData(); + /// \brief Gets the editable parameters of the node. + /// used only by the node editor gui to build the editon widget + const std::vector>& getEditableParameters(); + /// @} + /// \name Identification methods + /// @{ /// \brief Gets the type name of the node. const std::string& getTypeName() const; @@ -73,54 +103,54 @@ class RA_DATAFLOW_API Node /// \brief Sets the instance name (rename) the node void setInstanceName( const std::string& newName ); - /// \brief Generates the uuid of the node - void generateUuid(); - /// \brief Gets the UUID of the node as a string std::string getUuid() const; - /// \brief Sets the UUID of the node from a valid string string - /// \return true if the uuid is set, false if the node already have a valid uid + /// \brief Sets the UUID of the node from a valid uuid string + /// \return true if the uuid is set, false if the node already have a valid uid or the string is + /// invalid bool setUuid( const std::string& uid ); + /// @} - /// \brief Tests if the node is deletable (deprecated) - /// \return the deletable status of the node - [[deprecated]] bool isDeletable(); + /// \name Serialization of a node + /// @{ + /// TODO : specify the json format for nodes and what is expected from the following ethods - /// \brief Set the deletable status of the node - [[deprecated]] void setDeletableStatus( bool deletable = true ); + /// \brief serialize the content of the node. + /// Fill the given json object with the json representation of the concrete node. + void toJson( nlohmann::json& data ) const; - /// \brief Gets the in ports of the node. - const std::vector>& getInputs(); + /// \brief unserialized the content of the node. + /// Fill the node from its json representation + void fromJson( const nlohmann::json& data ); - /// \brief Gets the out ports of the node. - const std::vector>& getOutputs(); + /// \brief Add a metadata to the node to store application specific information. + /// used, e.g. by the node editor gui to save node position in the graphical canvas. + void addJsonMetaData( const nlohmann::json& data ); - /// \brief Get the interface ports of the node - const std::vector& getInterface(); + /// \brief Give access to extra json data stored on the node. + const nlohmann::json& getJsonMetaData(); + /// @} - /// Gets the editable parameters of the node. - const std::vector>& getEditableParameters(); + /// \brief Tests if the node is deletable (deprecated) + /// \todo remove the deletable status management of the node through the use of proper + /// ownership. This will require modifying RadiumNodeEditor external + /// \return the deletable status of the node + [[deprecated]] bool isDeletable(); - /// \brief Sets the filesystem (real or virtual) location for the pass resources - inline void setResourcesDir( std::string resourcesRootDir ); + /// \brief Set the deletable status of the node + [[deprecated]] void setDeletableStatus( bool deletable = true ); /// \brief Flag that checks if the node is already initialized bool m_initialized { false }; - /// \brief Two nodes are considered equal if there names are the same. - bool operator==( const Node& o_node ); - - /// Adds an interface port to the node. - /// This function checks if there is no interface port with the same name already associated - /// with this node. - /// \param port The interface port to add. - bool addInterface( PortBase* port ); + /// \brief Sets the filesystem (real or virtual) location for the pass resources + inline void setResourcesDir( std::string resourcesRootDir ); protected: + /// Construct the base node given its name and type /// \param instanceName The name of the node /// \param typeName The type name of the node - Node( const std::string& instanceName, const std::string& typeName ); /// internal json representation of the Node. @@ -149,30 +179,27 @@ class RA_DATAFLOW_API Node template void addOutput( PortOut* out, T* data ); - /// Adds an out port for a GRAPH. This port is also an interface port whose reference is stored - /// in the source and sink nodes of the graph. This function checks if there is no out port with - /// the same name already associated with the graph. - /// \param out The port to add. - bool addOutput( PortBase* out ); + /// Adds an interface port to the node. + /// This function checks if there is no interface port with the same name already associated + /// with this node. + /// \param port The interface port to add. + /// \deprecated, use buildInterfaces instead + + [[deprecated]] bool addInterface( PortBase* port ); /// \brief Adds an editable parameter to the node if it does not already exist. /// \note the node will take ownership of the editable object. /// \param editableParameter The editable parameter to add. - template - bool addEditableParameter( EditableParameter* editableParameter ); + bool addEditableParameter( EditableParameterBase* editableParameter ); /// Remove an editable parameter to the node if it does exist. /// \param name The name of the editable parameter to remove. /// \return true if the editable parameter is found and removed. - template bool removeEditableParameter( const std::string& name ); - /// The uuid of the node (TODO, use https://github.com/mariusbancila/stduuid instead of a - /// string) - // std::string m_uuid; - uuids::uuid m_uuid; /// The deletable status of the node bool m_isDeletable { true }; + /// The type name of the node. Initialized once at construction std::string m_typeName; /// The instance name of the node @@ -186,12 +213,14 @@ class RA_DATAFLOW_API Node /// The editable parameters of the node std::vector> m_editableParameters; - /// The base resources directory - std::string m_resourceDir { "./" }; - /// Additional data on the node, added by application or gui or ... nlohmann::json m_extraJsonData; + /// \brief Generates the uuid of the node + void generateUuid(); + /// The uuid of the node + uuids::uuid m_uuid; + /// generator for uuid static bool s_uuidGeneratorInitialized; static uuids::uuid_random_generator* s_uidGenerator; diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index 7acf57a6f0b..abcc6834096 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -21,7 +21,7 @@ inline void Node::destroy() { #endif } -inline nlohmann::json& Node::getJsonMetaData() { +inline const nlohmann::json& Node::getJsonMetaData() { return m_extraJsonData; } @@ -53,7 +53,22 @@ inline const std::vector>& Node::getOutputs() { return m_outputs; } -inline const std::vector& Node::getInterface() { +inline const std::vector& Node::buildInterfaces( Node* parent ) { + m_interface.clear(); + m_interface.shrink_to_fit(); + std::vector>* readFrom; + if ( m_inputs.empty() ) { readFrom = &m_outputs; } + else { + readFrom = &m_inputs; + } + m_interface.reserve( readFrom->size() ); + for ( const auto& p : *readFrom ) { + // Todo, ensure thereis no twice the same port .... + m_interface.emplace_back( p->reflect( parent, getInstanceName() + '_' + p->getName() ) ); + } + return m_interface; +} +inline const std::vector& Node::getInterfaces() { return m_interface; } @@ -61,12 +76,8 @@ inline const std::vector>& Node::getEdita return m_editableParameters; } -inline void Node::setResourcesDir( std::string resourcesRootDir ) { - m_resourceDir = std::move( resourcesRootDir ); -} - inline bool Node::operator==( const Node& o_node ) { - return m_typeName == o_node.getTypeName(); + return ( m_typeName == o_node.getTypeName() ) && ( m_instanceName == o_node.getInstanceName() ); } inline bool Node::addInput( PortBase* in ) { @@ -79,16 +90,6 @@ inline bool Node::addInput( PortBase* in ) { return !found; } -inline bool Node::addOutput( PortBase* out ) { - if ( out->is_input() ) { return false; } - bool found = false; - for ( auto& output : m_outputs ) { - if ( output->getName() == out->getName() ) { found = true; } - } - if ( !found ) { m_outputs.emplace_back( out ); } - return !found; -} - template void Node::addOutput( PortOut* out, T* data ) { bool found = false; @@ -110,8 +111,7 @@ inline bool Node::addInterface( PortBase* ports ) { return !found; } -template -bool Node::addEditableParameter( EditableParameter* editableParameter ) { +inline bool Node::addEditableParameter( EditableParameterBase* editableParameter ) { bool found = false; for ( auto& edit : m_editableParameters ) { if ( edit.get()->getName() == editableParameter->getName() ) { found = true; } @@ -120,8 +120,7 @@ bool Node::addEditableParameter( EditableParameter* editableParameter ) { return !found; } -template -bool Node::removeEditableParameter( const std::string& name ) { +inline bool Node::removeEditableParameter( const std::string& name ) { bool found = false; auto it = m_editableParameters.begin(); while ( it != m_editableParameters.end() ) { diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 5da2c80003b..60f68458559 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -56,12 +56,12 @@ T* SingleDataSourceNode::getData() const { template void SingleDataSourceNode::setEditable( const std::string name ) { - Node::addEditableParameter( new EditableParameter( name, m_localData ) ); + Node::addEditableParameter( new EditableParameter( name, m_localData ) ); } template void SingleDataSourceNode::removeEditable( const std::string& name ) { - Node::removeEditableParameter( name ); + Node::removeEditableParameter( name ); } template diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl index 161f98b82c8..892df3ae382 100644 --- a/src/Dataflow/Core/Port.inl +++ b/src/Dataflow/Core/Port.inl @@ -147,8 +147,7 @@ bool PortOut::disconnect() { template PortBase* PortOut::reflect( Node* node, std::string name ) { - auto port = new PortIn( name, node ); - return port; + return new PortIn( name, node ); } /** diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index f76e4e6de04..b347d29395f 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -81,13 +81,15 @@ void GraphEditorView::buildAdapterRegistry( const NodeFactorySet& factories ) { m_editorRegistry.reset( new QtNodes::DataModelRegistry ); for ( const auto& [factoryName, factory] : factories ) { for ( const auto& [typeName, creator] : factory->getFactoryMap() ) { - auto f = creator.first; + auto f = creator.first; + auto creatorFactory = factory; m_editorRegistry->registerModel( typeName.c_str(), - [f, this]() -> std::unique_ptr { + [f, this, creatorFactory]() -> std::unique_ptr { nlohmann::json data; auto node = f( data ); this->m_dataflowGraph->addNode( node ); + this->m_dataflowGraph->addFactory( creatorFactory ); return std::make_unique( this->m_dataflowGraph, node ); }, creator.second.c_str() ); From bc8db75afa623cc2f1d6cb8d05d57ac770f32cd3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Oct 2022 23:07:03 +0200 Subject: [PATCH 025/239] [dataflow] make port construction more robust --- src/Dataflow/Core/Port.hpp | 30 +++++++++++++++++++++++++++++- src/Dataflow/Core/Port.inl | 2 -- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index a573f56670e..89e51bc80d6 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -35,12 +35,24 @@ class RA_DATAFLOW_API PortBase bool m_isLinkMandatory { false }; public: - /// Constructor. + /// \name Constructors + /// @{ + /// \brief delete default constructors. + /// \see https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual + /// Constructors. + PortBase() = delete; + PortBase( const PortBase& ) = delete; + PortBase& operator=( const PortBase& ) = delete; + /// @param name The name of the port. /// @param type The data's type's hash. /// @param node The pointer to the node associated with the port. PortBase( const std::string& name, size_t type, Node* node ); + /// @} + + /// \brief make PortBase a base abstract class virtual ~PortBase() = default; + /// Gets the port's name. const std::string& getName(); /// Gets the hash of the type of the data. @@ -95,10 +107,18 @@ class PortOut : public PortBase public: using DataType = T; + /// \name Constructors + /// @{ + /// \brief delete default constructors. + PortOut() = delete; + PortOut( const PortOut& ) = delete; + PortOut& operator=( const PortOut& ) = delete; /// Constructor. /// @param name The name of the port. /// @param node The pointer to the node associated with the port. PortOut( const std::string& name, Node* node ); + /// @} + /// Gets a reference to the data this ports points to. T& getData(); /// Takes a pointer to the data this port will point to. @@ -137,10 +157,18 @@ class PortIn : public PortBase, public: using DataType = T; + /// \name Constructors + /// @{ + /// \brief delete default constructors. + PortIn() = delete; + PortIn( const PortIn& ) = delete; + PortIn& operator=( const PortIn& ) = delete; /// Constructor. /// @param name The name of the port. /// @param node The pointer to the node associated with the port. PortIn( const std::string& name, Node* node ); + /// @} + /// Gets the out port this port is connected to. PortBase* getLink() override; /// Gets a reference to the data pointed by the connected out port. diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl index 892df3ae382..20916ad98da 100644 --- a/src/Dataflow/Core/Port.inl +++ b/src/Dataflow/Core/Port.inl @@ -80,13 +80,11 @@ bool PortOut::connect( PortBase* o ) { template std::string PortOut::getTypeName() { - // return Ra::Core::Utils::demangleType(); return simplifiedDemangledType(); } template std::string PortIn::getTypeName() { - // return Ra::Core::Utils::demangleType(); return simplifiedDemangledType(); } From 50dcb5e1dfaaa2607ab9c49eb2503baaabba1da9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Oct 2022 23:45:44 +0200 Subject: [PATCH 026/239] [dataflow] accelerate compilation --- src/Dataflow/Core/EditableParameter.hpp | 34 ++++++++++++++- src/Dataflow/Core/Enumerator.hpp | 4 ++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 31 +++++++++----- ...oreDataSources.inl => CoreDataSources.cpp} | 42 +++++++++---------- .../Core/Nodes/Sources/CoreDataSources.hpp | 33 +++++++++++---- src/Dataflow/Core/filelist.cmake | 5 ++- 6 files changed, 107 insertions(+), 42 deletions(-) rename src/Dataflow/Core/Nodes/Sources/{CoreDataSources.inl => CoreDataSources.cpp} (61%) diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp index 25111062384..a852fedf100 100644 --- a/src/Dataflow/Core/EditableParameter.hpp +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -9,8 +9,22 @@ namespace Ra { namespace Dataflow { namespace Core { +/// \brief Basic introspection for Node internal data edition +/// This base class gives key information to associate editing capabilities (and gui) to +/// an node internal data. +/// struct RA_DATAFLOW_API EditableParameterBase { + /// \name Constructors + /// @{ + /// \brief delete default constructors. + EditableParameterBase() = delete; + EditableParameterBase( const EditableParameterBase& ) = delete; + EditableParameterBase& operator=( const EditableParameterBase& ) = delete; + + /// Construct an base editable parameter from its name and type hash EditableParameterBase( std::string& name, size_t hashedType ); + ///@} + virtual ~EditableParameterBase() = default; std::string getName(); std::string m_name { "" }; @@ -19,11 +33,27 @@ struct RA_DATAFLOW_API EditableParameterBase { template struct EditableParameter : public EditableParameterBase { - EditableParameter() = delete; - explicit EditableParameter( std::string name, T& data ); + /// \name Constructors + /// @{ + /// \brief delete default constructors. + EditableParameter() = delete; + EditableParameter( const EditableParameter& ) = delete; + EditableParameter& operator=( const EditableParameter& ) = delete; + + /// Construct an editable parameter from its name and type hash + EditableParameter( std::string name, T& data ); + ///@} + + /// Add constraints or associated data to the editable. + /// \todo, replace this with a json object describing the constraints + /// with value convertible to T .... void addAdditionalData( T newData ); + /// The data to edit. + /// This is a reference to any data stored in a node and that the user could change T& m_data; + + /// Constraints on the edited data std::vector additionalData; }; diff --git a/src/Dataflow/Core/Enumerator.hpp b/src/Dataflow/Core/Enumerator.hpp index 27b16acd5cc..6e54e8f036d 100644 --- a/src/Dataflow/Core/Enumerator.hpp +++ b/src/Dataflow/Core/Enumerator.hpp @@ -9,6 +9,10 @@ namespace Ra { namespace Dataflow { namespace Core { +/** + * \brief Allow to manage + * \tparam T + */ template class Enumerator : public Ra::Core::Utils::Observable&> { diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 0d2785127a3..54bea78d40b 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -16,6 +16,10 @@ namespace NodeFactoriesManager { FACTORY->registerNodeCreator( \ NAMESPACE::ArrayMapper##SUFFIX::getTypename() + "_", #NAMESPACE ) +#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayDataSource##SUFFIX::getTypename() + "_", #NAMESPACE ) + void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; @@ -31,16 +35,23 @@ void registerStandardFactories() { Sources::ScalarValueSource::getTypename() + "_", "Source" ); coreFactory->registerNodeCreator( Sources::ColorSourceNode::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::FloatArrayDataSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::DoubleArrayDataSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::IntArrayDataSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::UIntArrayDataSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::ColorArrayDataSource::getTypename() + "_", "Source" ); + + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Float ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Int ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, UInt ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Color ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2f ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2d ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3f ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3d ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4f ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4d ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2i ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2ui ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3i ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3ui ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4i ); + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4ui ); /* --- Sinks --- */ coreFactory->registerNodeCreator( Sinks::BooleanSink::getTypename() + "_", diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp similarity index 61% rename from src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl rename to src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp index 77dae2109fa..248c60b2f3d 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.inl +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp @@ -1,4 +1,4 @@ -#pragma once + #include #include @@ -9,87 +9,87 @@ namespace Core { namespace Sources { /* Source bool */ -inline BooleanValueSource::BooleanValueSource( const std::string& name ) : +BooleanValueSource::BooleanValueSource( const std::string& name ) : SingleDataSourceNode( name, BooleanValueSource::getTypename() ) { setEditable( "boolean" ); } -inline void BooleanValueSource::toJsonInternal( nlohmann::json& data ) const { +void BooleanValueSource::toJsonInternal( nlohmann::json& data ) const { data["boolean"] = *getData(); } -inline void BooleanValueSource::fromJsonInternal( const nlohmann::json& data ) { +void BooleanValueSource::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "boolean" ) ) { bool v = data["boolean"]; setData( v ); } } -inline const std::string& BooleanValueSource::getTypename() { +const std::string& BooleanValueSource::getTypename() { static std::string demangledTypeName { "Source" }; return demangledTypeName; } /* Source int */ -inline IntValueSource::IntValueSource( const std::string& name ) : +IntValueSource::IntValueSource( const std::string& name ) : SingleDataSourceNode( name, IntValueSource::getTypename() ) { setEditable( "value" ); } -inline void IntValueSource::toJsonInternal( nlohmann::json& data ) const { +void IntValueSource::toJsonInternal( nlohmann::json& data ) const { data["value"] = *getData(); } -inline void IntValueSource::fromJsonInternal( const nlohmann::json& data ) { +void IntValueSource::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "value" ) ) { int v = data["value"]; setData( v ); } } -inline const std::string& IntValueSource::getTypename() { +const std::string& IntValueSource::getTypename() { static std::string demangledTypeName { "Source" }; return demangledTypeName; } /* Source uint */ -inline UIntValueSource::UIntValueSource( const std::string& name ) : +UIntValueSource::UIntValueSource( const std::string& name ) : SingleDataSourceNode( name, UIntValueSource::getTypename() ) { setEditable( "value" ); } -inline void UIntValueSource::toJsonInternal( nlohmann::json& data ) const { +void UIntValueSource::toJsonInternal( nlohmann::json& data ) const { data["value"] = *getData(); } -inline void UIntValueSource::fromJsonInternal( const nlohmann::json& data ) { +void UIntValueSource::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "value" ) ) { unsigned int v = data["value"]; setData( v ); } } -inline const std::string& UIntValueSource::getTypename() { +const std::string& UIntValueSource::getTypename() { static std::string demangledTypeName { "Source" }; return demangledTypeName; } /* Source Scalar */ -inline const std::string& ScalarValueSource::getTypename() { +const std::string& ScalarValueSource::getTypename() { static std::string demangledTypeName { "Source" }; return demangledTypeName; } -inline ScalarValueSource::ScalarValueSource( const std::string& name ) : +ScalarValueSource::ScalarValueSource( const std::string& name ) : SingleDataSourceNode( name, ScalarValueSource::getTypename() ) { setEditable( "number" ); } -inline void ScalarValueSource::toJsonInternal( nlohmann::json& data ) const { +void ScalarValueSource::toJsonInternal( nlohmann::json& data ) const { data["number"] = *getData(); } -inline void ScalarValueSource::fromJsonInternal( const nlohmann::json& data ) { +void ScalarValueSource::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "number" ) ) { Scalar v = data["number"]; setData( v ); @@ -97,23 +97,23 @@ inline void ScalarValueSource::fromJsonInternal( const nlohmann::json& data ) { } /* Source Color */ -inline const std::string& ColorSourceNode::getTypename() { +const std::string& ColorSourceNode::getTypename() { static std::string demangledTypeName { "Source" }; return demangledTypeName; } -inline ColorSourceNode::ColorSourceNode( const std::string& name ) : +ColorSourceNode::ColorSourceNode( const std::string& name ) : SingleDataSourceNode( name, ColorSourceNode::getTypename() ) { setEditable( "color" ); } -inline void ColorSourceNode::toJsonInternal( nlohmann::json& data ) const { +void ColorSourceNode::toJsonInternal( nlohmann::json& data ) const { auto c = Ra::Core::Utils::Color::linearRGBTosRGB( *getData() ); std::array color { { c.x(), c.y(), c.z() } }; data["color"] = color; } -inline void ColorSourceNode::fromJsonInternal( const nlohmann::json& data ) { +void ColorSourceNode::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "color" ) ) { std::array c = data["color"]; auto v = diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 0ba8929c577..56e33db773b 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -91,15 +92,33 @@ class RA_DATAFLOW_API ColorSourceNode : public SingleDataSourceNode>; -using DoubleArrayDataSource = SingleDataSourceNode>; -using IntArrayDataSource = SingleDataSourceNode>; -using UIntArrayDataSource = SingleDataSourceNode>; -using ColorArrayDataSource = SingleDataSourceNode>; +// This macro does not end with semicolon. To be added when callin it +#define DECLARE_ARRAYSOURCES( SUFFIX, TYPE ) \ + using ArrayDataSource##SUFFIX = SingleDataSourceNode> + +using namespace Ra::Core; + +DECLARE_ARRAYSOURCES( Float, float ); +DECLARE_ARRAYSOURCES( Double, double ); +DECLARE_ARRAYSOURCES( Int, int ); +DECLARE_ARRAYSOURCES( UInt, unsigned int ); +DECLARE_ARRAYSOURCES( Color, Utils::Color ); +DECLARE_ARRAYSOURCES( Vector2f, Vector2f ); +DECLARE_ARRAYSOURCES( Vector2d, Vector2d ); +DECLARE_ARRAYSOURCES( Vector3f, Vector3f ); +DECLARE_ARRAYSOURCES( Vector3d, Vector3d ); +DECLARE_ARRAYSOURCES( Vector4f, Vector4f ); +DECLARE_ARRAYSOURCES( Vector4d, Vector4d ); +DECLARE_ARRAYSOURCES( Vector2i, Vector2i ); +DECLARE_ARRAYSOURCES( Vector3i, Vector3i ); +DECLARE_ARRAYSOURCES( Vector4i, Vector4i ); +DECLARE_ARRAYSOURCES( Vector2ui, Vector2ui ); +DECLARE_ARRAYSOURCES( Vector3ui, Vector3ui ); +DECLARE_ARRAYSOURCES( Vector4ui, Vector4ui ); + +#undef DECLARE_ARRAYSOURCES } // namespace Sources } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index 0c3b60ff7df..9bbb8be5f7c 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -4,7 +4,9 @@ # from ./scripts directory # ---------------------------------------------------- -set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp Nodes/CoreBuiltInsNodes.cpp) +set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp Nodes/CoreBuiltInsNodes.cpp + Nodes/Sources/CoreDataSources.cpp +) set(dataflow_core_headers DataflowGraph.hpp @@ -35,7 +37,6 @@ set(dataflow_core_inlines Nodes/Functionals/MapNode.inl Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl - Nodes/Sources/CoreDataSources.inl Nodes/Sources/FunctionSource.inl Nodes/Sources/SingleDataSourceNode.inl Port.inl From f1c205a64e0da26cc556aab2171a3f7a069ca0e4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 09:27:57 +0200 Subject: [PATCH 027/239] [doc] update dependencies --- doc/basics/dependencies.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/basics/dependencies.md b/doc/basics/dependencies.md index a34affe78eb..6e8eb1ac1cb 100644 --- a/doc/basics/dependencies.md +++ b/doc/basics/dependencies.md @@ -7,7 +7,7 @@ Radium relies on several external libraries to load files or to represent some d * [Engine] glm, globjects, glbindings, tinyEXR * [IO] Assimp * [Gui] Qt Core, Qt Widgets and Qt OpenGL v5.5+ (5.14 at least, Qt6 support is experimental), PowerSlider -* [Dataflow] stduuid +* [Dataflow] stduuid, RadiumNodeEditor * [doc] Doxygen-awesome-css * stb_image @@ -102,6 +102,7 @@ set(assimp_DIR "/path/to/external/install/lib/cmake/assimp-5.0/" CACHE PATH "My set(tinyply_DIR "/path/to/external/install/lib/cmake/tinyply/" CACHE PATH "My tinyply location") set(PowerSlider_DIR "/path/to/external/install/lib/cmake/PowerSlider/" CACHE PATH "My PowerSlider location") set(stduuid_DIR "/path/to/external/install/lib/cmake/stduuid/" CACHE PATH "My stduuid") +set(RadiumNodeEditor_DIR "/path/to/external/install/lib/cmake/RadiumNodeEditor/" CACHE PATH "My NodeEditor") set(RADIUM_IO_ASSIMP ON CACHE BOOL "Radium uses assimp io") set(RADIUM_IO_TINYPLY ON CACHE BOOL "Radium uses tinyply io") ~~~ From 145e552e1bc4042a949f315699b248dbb67f772f Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 16:56:58 +0200 Subject: [PATCH 028/239] [dataflow] disable rendering graph --- src/Dataflow/CMakeLists.txt | 8 +++----- src/Dataflow/Rendering/Renderer/RenderingGraph.cpp | 7 ++++--- src/Dataflow/Rendering/Renderer/RenderingGraph.hpp | 1 - 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Dataflow/CMakeLists.txt b/src/Dataflow/CMakeLists.txt index 2360159307a..14e45548a65 100644 --- a/src/Dataflow/CMakeLists.txt +++ b/src/Dataflow/CMakeLists.txt @@ -12,11 +12,9 @@ if(RADIUM_GENERATE_LIB_CORE) target_link_libraries(${ra_dataflow_target} INTERFACE DataflowCore) endif() -if(RADIUM_GENERATE_LIB_ENGINE) - add_subdirectory(Rendering) - add_dependencies(${ra_dataflow_target} DataflowRendering) - target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) -endif() +# if(RADIUM_GENERATE_LIB_ENGINE) add_subdirectory(Rendering) add_dependencies(${ra_dataflow_target} +# DataflowRendering) target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) +# endif() if(RADIUM_GENERATE_LIB_GUI) add_subdirectory(QtGui) diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp index a01b7d898ac..8d93bfc4867 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp @@ -32,6 +32,7 @@ bool RenderingGraph::removeNode( Node* node ) { return removed; } +/* bool RenderingGraph::postCompilationOperation() { #if 0 m_renderingNodes.clear(); @@ -78,9 +79,9 @@ bool RenderingGraph::postCompilationOperation() { m_dataProviders.size() << " scene nodes. \n"; */ #endif - return true; +return true; } - +* / #if 0 void RenderingGraph::observeSinks( const std::vector& graphOutput ) { m_outputTextures = graphOutput; @@ -98,7 +99,7 @@ void RenderingGraph::observeSinks( const std::vector& graphOutput */ } #endif -const std::vector& RenderingGraph::getImagesOutput() const { + const std::vector& RenderingGraph::getImagesOutput() const { return m_outputTextures; } diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index 6fbdb743d41..5599b2358c1 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -50,7 +50,6 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph static const std::string& getTypename(); protected: - bool postCompilationOperation() override; void fromJsonInternal( const nlohmann::json& ) override; void toJsonInternal( nlohmann::json& ) const override; From ac93522a68211b85769a0ed51a6c40c2e7b5e159 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 16:58:02 +0200 Subject: [PATCH 029/239] [dataflow] improve graph compilation/execution robustness --- src/Dataflow/Core/DataflowGraph.cpp | 47 ++++++++++++++--------------- src/Dataflow/Core/DataflowGraph.hpp | 5 --- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 8f87aedd734..be6b4ca1ac9 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -19,17 +19,9 @@ DataflowGraph::DataflowGraph( const std::string& instanceName, const std::string } void DataflowGraph::init() { - Node::init(); - auto compileOK = compile(); - if ( compileOK ) { -#ifdef GRAPH_CALL_TRACE - int i = 0; - std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&i]( const auto& level ) { - std::cout << "- \e[1mLevel " << i++ << "\e[0m" << std::endl; -#else - + if ( m_ready ) { + Node::init(); std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { -#endif std::for_each( level.begin(), level.end(), []( auto node ) { if ( !node->m_initialized ) { node->init(); @@ -38,26 +30,27 @@ void DataflowGraph::init() { } ); } ); } - m_recompile = !compileOK; } void DataflowGraph::execute() { - if ( m_ready ) { + if ( !m_ready ) { #ifdef GRAPH_CALL_TRACE std::cout << std::endl - << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": execute." - << std::endl; - - int i = 0; - std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&i]( const auto& level ) { - std::cout << "- \e[1mLevel " << i++ << "\e[0m" << std::endl; -#else - - std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { + << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": not ready to execute, recompile the graph." << std::endl; #endif - std::for_each( level.begin(), level.end(), []( auto node ) { node->execute(); } ); - } ); + if ( !compile() ) { +#ifdef GRAPH_CALL_TRACE + std::cout << std::endl + << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName + << "\": unable tocompile the graph." << std::endl; +#endif + return; + } } + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { + std::for_each( level.begin(), level.end(), []( auto node ) { node->execute(); } ); + } ); } void DataflowGraph::destroy() { @@ -274,6 +267,7 @@ bool DataflowGraph::addNode( Node* newNode ) { << "\": success adding node \"" << newNode->getInstanceName() << "\"!" << std::endl; #endif + m_ready = false; return true; } else { @@ -452,6 +446,8 @@ bool DataflowGraph::addLink( Node* nodeFrom, nodeToInputName + "\" of node \"" + nodeTo->getInstanceName() + "\"." << std::endl; #endif + // The state of the graph changes, set it to not ready + m_ready = false; return true; } @@ -575,7 +571,10 @@ bool DataflowGraph::compile() { << std::endl << std::endl; #endif - return m_ready = postCompilationOperation(); + m_recompile = false; + m_ready = true; + this->init(); + return m_ready; } void DataflowGraph::clearNodes() { diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 8707f891b88..e91a292ca8d 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -150,11 +150,6 @@ class RA_DATAFLOW_API DataflowGraph : public Node /** Allow derived class to construct the graph with their own static type */ DataflowGraph( const std::string& instanceName, const std::string& typeName ); - /** - * Allow derived class to add operations on a compiled graph - * @return - */ - virtual bool postCompilationOperation() { return true; } void fromJsonInternal( const nlohmann::json& ) override; void toJsonInternal( nlohmann::json& ) const override; From de345461360c51d0b8a9f166bd5505155e92967a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 16:58:28 +0200 Subject: [PATCH 030/239] [dataflow] improve functionals --- .../Nodes/Functionals/CoreDataFunctionals.hpp | 4 +- .../Core/Nodes/Functionals/FilterNode.hpp | 2 +- .../Core/Nodes/Functionals/MapNode.hpp | 67 ---------------- .../Core/Nodes/Functionals/TransformNode.hpp | 76 +++++++++++++++++++ .../{MapNode.inl => TransformNode.inl} | 55 +++++++------- src/Dataflow/Core/filelist.cmake | 4 +- 6 files changed, 109 insertions(+), 99 deletions(-) delete mode 100644 src/Dataflow/Core/Nodes/Functionals/MapNode.hpp create mode 100644 src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp rename src/Dataflow/Core/Nodes/Functionals/{MapNode.inl => TransformNode.inl} (51%) diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp index 48049844c7d..65fb5bee74b 100644 --- a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include #include @@ -16,7 +16,7 @@ using namespace Ra::Core; // This macro does not end with semicolon. To be added when callin it #define DECLARE_FUNCTIONALS( SUFFIX, TYPE ) \ using ArrayFilter##SUFFIX = FilterNode>; \ - using ArrayMapper##SUFFIX = MapNode> + using ArrayMapper##SUFFIX = TransformNode> DECLARE_FUNCTIONALS( Float, float ); DECLARE_FUNCTIONALS( Double, double ); diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index ff6b7926b30..38c0fa5e9bc 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -12,7 +12,7 @@ namespace Functionals { /** \brief Filter on iterable collection * \tparam coll_t the collection to filter. Must respect the SequenceContainer requirements - * \tparam v_t (optional, type of the element in the collection) + * \tparam v_t (optional), type of the element in the collection. Default to coll_t::value_type * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer */ template diff --git a/src/Dataflow/Core/Nodes/Functionals/MapNode.hpp b/src/Dataflow/Core/Nodes/Functionals/MapNode.hpp deleted file mode 100644 index 26b13252440..00000000000 --- a/src/Dataflow/Core/Nodes/Functionals/MapNode.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -#include - -#include - -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Functionals { - -/** \brief Filter on iterable collection - * \tparam coll_t the collection to filter. Must respect the SequenceContainer requirements - * \tparam v_t (optional, type of the element in the collection) - * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer - */ -template -class MapNode : public Node -{ - public: - /** - * unaryPredicate Type - */ - using MapOperator = std::function; - - /** - * \brief Construct a filter accepting all its input ( true() lambda ) - * \param instanceName - */ - explicit MapNode( const std::string& instanceName ); - - /** - * \brief Construct a filter with the given predicate - * \param instanceName - * \param filterFunction - */ - MapNode( const std::string& instanceName, MapOperator mapOperator ); - - void init() override; - void execute() override; - - /// Sets the filtering predicate on the node - void setFilterFunction( MapOperator mapOperator ); - - protected: - MapNode( const std::string& instanceName, - const std::string& typeName, - MapOperator mapOperator ); - - void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; - - private: - MapOperator m_mapOperator; - coll_t m_elements; - - public: - static const std::string& getTypename(); -}; - -} // namespace Functionals -} // namespace Core -} // namespace Dataflow -} // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp new file mode 100644 index 00000000000..64981d2799c --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp @@ -0,0 +1,76 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +/** \brief Transform an iterable collection + * \tparam coll_t the collection to transform. Must respect the SequenceContainer requirements + * \tparam v_t (optional), type of the element in the collection. Default to coll_t::value_type + * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer + * + * This node has two inputs : + * - in : port accepting a coll_t data. Linking to this port is mandatory + * - f : port accepting an operator with profile std::function. + * Linking to this port is not mandatory, the operator might be set once for the node. + * If this port is linked, the operator will be taken from the port. + * + * This node has one output : + * - out : port giving a coll_t such that out = std::transform(in, f) + */ +template +class TransformNode : public Node +{ + public: + /** + * Trasformation operator profile + */ + using TransformOperator = std::function; + + /** + * \brief Construct an identity transformer + * \param instanceName + */ + explicit TransformNode( const std::string& instanceName ); + + /** + * \brief Construct a transformer with the given operator + * \param instanceName + * \param filterFunction + */ + TransformNode( const std::string& instanceName, TransformOperator op ); + + void init() override; + void execute() override; + + /// Sets the operator on the node + void setOperator( TransformOperator op ); + + protected: + TransformNode( const std::string& instanceName, + const std::string& typeName, + TransformOperator op ); + + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + private: + TransformOperator m_operator; + coll_t m_elements; + + public: + static const std::string& getTypename(); +}; + +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Functionals/MapNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl similarity index 51% rename from src/Dataflow/Core/Nodes/Functionals/MapNode.inl rename to src/Dataflow/Core/Nodes/Functionals/TransformNode.inl index 626678d83b0..26b465572ad 100644 --- a/src/Dataflow/Core/Nodes/Functionals/MapNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl @@ -1,5 +1,5 @@ #pragma once -#include +#include #include @@ -9,28 +9,28 @@ namespace Core { namespace Functionals { template -MapNode::MapNode( const std::string& instanceName ) : - MapNode( instanceName, getTypename(), []( v_t ) { return v_t {}; } ) {} +TransformNode::TransformNode( const std::string& instanceName ) : + TransformNode( instanceName, getTypename(), []( v_t ) { return v_t {}; } ) {} template -MapNode::MapNode( const std::string& instanceName, MapOperator mapOperator ) : - MapNode( instanceName, getTypename(), mapOperator ) {} +TransformNode::TransformNode( const std::string& instanceName, TransformOperator op ) : + TransformNode( instanceName, getTypename(), op ) {} template -void MapNode::setFilterFunction( MapOperator mapOperator ) { - m_mapOperator = mapOperator; +void TransformNode::setOperator( TransformOperator op ) { + m_operator = op; } template -void MapNode::init() { +void TransformNode::init() { Node::init(); m_elements.clear(); } template -void MapNode::execute() { - auto predPort = dynamic_cast*>( m_inputs[1].get() ); - auto f = m_mapOperator; +void TransformNode::execute() { + auto predPort = dynamic_cast*>( m_inputs[1].get() ); + auto f = m_operator; if ( predPort->isLinked() ) { f = predPort->getData(); } auto input = dynamic_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { @@ -48,37 +48,38 @@ void MapNode::execute() { } template -const std::string& MapNode::getTypename() { +const std::string& TransformNode::getTypename() { static std::string demangledName = - std::string { "Map<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + std::string { "Transform<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; return demangledName; } template -MapNode::MapNode( const std::string& instanceName, - const std::string& typeName, - MapOperator mapOperator ) : - Node( instanceName, typeName ), m_mapOperator( mapOperator ) { - auto portIn = new PortIn( "in", this ); - addInput( portIn ); - portIn->mustBeLinked(); +TransformNode::TransformNode( const std::string& instanceName, + const std::string& typeName, + TransformOperator op ) : + Node( instanceName, typeName ), m_operator( op ) { + auto in = new PortIn( "in", this ); + addInput( in ); + in->mustBeLinked(); - auto op = new PortIn( "f", this ); - addInput( op ); + auto f = new PortIn( "f", this ); + addInput( f ); - auto portOut = new PortOut( "out", this ); - addOutput( portOut, &m_elements ); + auto out = new PortOut( "out", this ); + addOutput( out, &m_elements ); } template -void MapNode::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = std::string { "Map operator could not be serialized for " } + getTypeName(); +void TransformNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string { "Transform operator could not be serialized for " } + getTypeName(); LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to save data when serializing a " << getTypeName() << "."; } template -void MapNode::fromJsonInternal( const nlohmann::json& ) { +void TransformNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to read data when un-serializing a " << getTypeName() << "."; } diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index 9bbb8be5f7c..d616d19fe6a 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -17,7 +17,7 @@ set(dataflow_core_headers Nodes/CoreBuiltInsNodes.hpp Nodes/Functionals/CoreDataFunctionals.hpp Nodes/Functionals/FilterNode.hpp - Nodes/Functionals/MapNode.hpp + Nodes/Functionals/TransformNode.hpp Nodes/Sinks/CoreDataSinks.hpp Nodes/Sinks/SinkNode.hpp Nodes/Sources/CoreDataSources.hpp @@ -34,7 +34,7 @@ set(dataflow_core_inlines Node.inl NodeFactory.inl Nodes/Functionals/FilterNode.inl - Nodes/Functionals/MapNode.inl + Nodes/Functionals/TransformNode.inl Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl Nodes/Sources/FunctionSource.inl From 4b4e0c03027a8e5aa1d92ffdd3179dc98b25ad45 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 16:59:29 +0200 Subject: [PATCH 031/239] [example] add dataflow functionals example --- examples/DataflowExamples/CMakeLists.txt | 2 +- .../FunctionalsGraph/CMakeLists.txt | 46 ++++++ .../FunctionalsGraph/main.cpp | 146 ++++++++++++++++++ 3 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt create mode 100644 examples/DataflowExamples/FunctionalsGraph/main.cpp diff --git a/examples/DataflowExamples/CMakeLists.txt b/examples/DataflowExamples/CMakeLists.txt index 5d087fa6c61..63c722d05e5 100644 --- a/examples/DataflowExamples/CMakeLists.txt +++ b/examples/DataflowExamples/CMakeLists.txt @@ -5,7 +5,7 @@ project(DataflowExamples VERSION 1.0.0) add_custom_target(${PROJECT_NAME}) add_custom_target(Install_${PROJECT_NAME}) -foreach(APP GraphSerialization HelloGraph GraphEditor) +foreach(APP GraphSerialization HelloGraph GraphEditor FunctionalsGraph) add_subdirectory(${APP}) add_dependencies(${PROJECT_NAME} ${APP}) add_dependencies(Install_${PROJECT_NAME} Install_${APP}) diff --git a/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt b/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt new file mode 100644 index 00000000000..8a16f43290f --- /dev/null +++ b/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.6) +if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") + cmake_policy(SET CMP0071 NEW) +endif() +if(APPLE) + cmake_policy(SET CMP0042 NEW) +endif(APPLE) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(FunctionalsGraph VERSION 0.0.1) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Set default install location to installed- folder in build dir we do not want to +# install to /usr by default +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX + "${CMAKE_CURRENT_BINARY_DIR}/installed-${CMAKE_CXX_COMPILER_ID}-${CMAKE_BUILD_TYPE}" + CACHE PATH "Install path prefix, prepended onto install directories." FORCE + ) + message(STATUS "Set install prefix to ${CMAKE_INSTALL_PREFIX}") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# /////////////////////////////// +find_package(Radium REQUIRED COMPONENTS Dataflow) + +# -------------------------------------------------------------------------------------------------- + +set(app_sources main.cpp) +# set(app_headers) + +# delete ${app_headers} +add_executable(${PROJECT_NAME} ${app_sources}) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + +target_link_libraries(${PROJECT_NAME} PUBLIC Radium::Dataflow) + +configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/DataflowExamples/FunctionalsGraph/main.cpp b/examples/DataflowExamples/FunctionalsGraph/main.cpp new file mode 100644 index 00000000000..29be66359dd --- /dev/null +++ b/examples/DataflowExamples/FunctionalsGraph/main.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include + +using namespace Ra::Dataflow::Core; + +/* ----------------------------------------------------------------------------------- */ +/** + * \brief Demonstrate how to use a graph to filter a collection + */ +int main( int argc, char* argv[] ) { + //! [Creating an empty graph] + DataflowGraph g { "helloGraph" }; + //! [Creating an empty graph] + + //! [Creating Nodes] + using TransformOperatorSource = Sources::FunctionSourceNode; + auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); + auto mapSource = new TransformOperatorSource( "MapOperator" ); + auto transformNode = new Functionals::TransformNode>( "Transformer" ); + // can't use auto here, must explicitely define the type + Functionals::TransformNode>::TransformOperator oneMinusMe = + []( const Scalar& i ) -> Scalar { return 1_ra - i; }; + transformNode->setOperator( oneMinusMe ); + auto sinkNode = new Sinks::SinkNode>( "Sink" ); + //! [Creating Nodes] + + //! [Adding Nodes to the graph] + g.addNode( sourceNode ); + g.addNode( mapSource ); + g.addNode( transformNode ); + g.addNode( sinkNode ); + //! [Adding Nodes to the graph] + + //! [Creating links between Nodes] + g.addLink( sourceNode, "to", transformNode, "in" ); + g.addLink( transformNode, "out", sinkNode, "from" ); + //! [Creating links between Nodes] + + //! [Verifing the graph can be compiled] + if ( !g.compile() ) { + std::cout << " compilation failed"; + return 1; + } + //! [Verifing the graph can be compiled] + + ///! [Configure the interface ports (input and output of the graph) + auto input = g.getDataSetter( "Source_to" ); + std::vector test; + input->setData( &test ); + + auto opSetter = g.getDataSetter( "MapOperator_f" ); + // can't use auto here, must explicitely define the type + TransformOperatorSource::function_type doubleMe = []( const Scalar& i ) -> Scalar { + return 2_ra * i; + }; + opSetter->setData( &doubleMe ); + + auto output = g.getDataGetter( "Sink_from" ); + // The reference to the result will not be available before the first run + // auto& result = output->getData>(); + ///! [Configure the interface ports (input and output of the graph) + + //! [Initializing input variable to test the graph] + test.reserve( 10 ); + std::mt19937 gen( 0 ); + std::uniform_real_distribution<> dis( 0.0, 1.0 ); + // Fill the vector with random numbers between 0 and 1 + for ( int n = 0; n < test.capacity(); ++n ) { + test.push_back( dis( gen ) ); + } + + std::cout << "Input values : \n\t"; + for ( auto ord : test ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Initializing input variable to test the graph] + + //! [Execute the graph] + g.execute(); + // The reference to the result is now available + auto& result = output->getData>(); + //! [Execute the graph] + + //! [Print the output result] + std::cout << "Output values (oneMinusMe): " << result.size() << "\n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + //! [Print the output result] + + //! [Verify the result] + std::cout << "Verifying the result : \n\t"; + + bool execOK { true }; + for ( int i = 0; i < 10; ++i ) { + if ( result[i] != oneMinusMe( test[i] ) ) { + std::cout << "Error at index " << i << " : " << result[i] << " should be " + << oneMinusMe( test[i] ) << "\n\t"; + execOK = false; + } + } + if ( execOK ) { std::cout << "OK\n"; } + else { + std::cout << "NOK\n"; + } + //! [Verify the result] + + //! [Modifying the graph to add link to map operator] + if ( !g.addLink( mapSource, "f", transformNode, "f" ) ) { + std::cout << "Unable to link port f (" + << ") from " << mapSource->getInstanceName() << " to port f(" + << ") of " << transformNode->getInstanceName() << "\n"; + return 1; + } + //! [Modifying the graph to add link to map operator] + + //! [Execute and test the result] + std::cout << "Rerun the graph with different operator : \n"; + g.execute(); + std::cout << "Output values (doubleMe): " << result.size() << "\n\t"; + for ( auto ord : result ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + execOK = true; + for ( int i = 0; i < 10; ++i ) { + if ( result[i] != doubleMe( test[i] ) ) { + std::cout << "Error at index " << i << " : " << result[i] << " should be " + << doubleMe( test[i] ) << "\n\t"; + execOK = false; + } + } + if ( execOK ) { std::cout << "OK\n"; } + else { + std::cout << "NOK\n"; + } + //! [Execute and test the result] + + return 0; +} From c739b495d41a1b4fe2f02c66346d3fbe549a2fc4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 18:37:51 +0200 Subject: [PATCH 032/239] [dataflow] remove uneeded includes --- src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp | 2 -- src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp | 2 -- src/Dataflow/Core/Nodes/Functionals/TransformNode.inl | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index 38c0fa5e9bc..fc4a13c56cf 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -1,8 +1,6 @@ #pragma once #include -#include - #include namespace Ra { diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp index 64981d2799c..a157ee7d279 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp @@ -1,8 +1,6 @@ #pragma once #include -#include - #include namespace Ra { diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl index 26b465572ad..88d2e3a07aa 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl @@ -1,8 +1,6 @@ #pragma once #include -#include - namespace Ra { namespace Dataflow { namespace Core { From a0dc72444407fb78fe5b1c0a52956949e1601c7b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 18:38:23 +0200 Subject: [PATCH 033/239] [dataflow] improve compilation robustness --- src/Dataflow/Core/DataflowGraph.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index be6b4ca1ac9..e9e7d7d1fa7 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -509,10 +509,16 @@ bool DataflowGraph::compile() { for ( auto const& n : m_nodes ) { // Find all sinks if ( n->getOutputs().size() == 0 ) { - // Add the sink in the useful nodes set - infoNodes.emplace( n.get(), std::pair>( 0, {} ) ); - // recursively add the predecessors of the sink - backtrackGraph( n.get(), infoNodes ); + // Add the sink in the useful nodes set if any of his port is linked + bool activeSink { false }; + for ( const auto& p : n->getInputs() ) { + activeSink |= p->isLinked(); + } + if ( activeSink ) { + infoNodes.emplace( n.get(), std::pair>( 0, {} ) ); + // recursively add the predecessors of the sink + backtrackGraph( n.get(), infoNodes ); + } } } #ifdef GRAPH_CALL_TRACE @@ -559,7 +565,9 @@ bool DataflowGraph::compile() { !lvl[j]->getInputs()[k]->isLinked() ) { #ifdef GRAPH_CALL_TRACE std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": compilation failed." << std::endl; + << "\": compilation failed, node " << lvl[j]->getInstanceName() + << " has mandatory port " << lvl[j]->getInputs()[k]->getName() + << " not linked." << std::endl; #endif return m_ready = false; } From 563c0b53733ba6674d63f1a56ae1ccb5570fa3c7 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 18:38:48 +0200 Subject: [PATCH 034/239] [dataflow] add reduce operator --- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 12 ++- .../Nodes/Functionals/CoreDataFunctionals.hpp | 8 +- .../Core/Nodes/Functionals/ReduceNode.hpp | 81 +++++++++++++++ .../Core/Nodes/Functionals/ReduceNode.inl | 99 +++++++++++++++++++ src/Dataflow/Core/filelist.cmake | 2 + 5 files changed, 194 insertions(+), 8 deletions(-) create mode 100644 src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp create mode 100644 src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 54bea78d40b..eb9503e444b 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -10,11 +10,13 @@ namespace Core { namespace NodeFactoriesManager { /** TODO : replace this by factory autoregistration at compile time */ -#define ADD_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ - FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayMapper##SUFFIX::getTypename() + "_", #NAMESPACE ) +#define ADD_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayTransformer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayReducer##SUFFIX::getTypename() + "_", #NAMESPACE ) #define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ FACTORY->registerNodeCreator( \ diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp index 65fb5bee74b..940e2c81267 100644 --- a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include @@ -14,9 +15,10 @@ namespace Functionals { using namespace Ra::Core; // This macro does not end with semicolon. To be added when callin it -#define DECLARE_FUNCTIONALS( SUFFIX, TYPE ) \ - using ArrayFilter##SUFFIX = FilterNode>; \ - using ArrayMapper##SUFFIX = TransformNode> +#define DECLARE_FUNCTIONALS( SUFFIX, TYPE ) \ + using ArrayFilter##SUFFIX = FilterNode>; \ + using ArrayTransformer##SUFFIX = TransformNode>; \ + using ArrayReducer##SUFFIX = ReduceNode> DECLARE_FUNCTIONALS( Float, float ); DECLARE_FUNCTIONALS( Double, double ); diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp new file mode 100644 index 00000000000..f5180c4340b --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp @@ -0,0 +1,81 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +/** \brief Reduce an iterable collection using a given operator + * \tparam coll_t the collection to reduce. Must respect the SequenceContainer requirements + * \tparam v_t (optional), type of the element in the collection. Default to coll_t::value_type + * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer + * + * This node has three inputs : + * - in : port accepting a coll_t data. Linking to this port is mandatory + * - f : port accepting an operator with profile std::function. + * Linking to this port is not mandatory, the operator might be set once for the node or is the + * default value for v_t. If this port is linked, the operator will be taken from the port. + * - init : port accepting a v_t to serve as initial value. + * Linking to this port is not mandatory. + * If this port is linked, the initial value will be taken from the port. + * + * This node has one output : + * - out : port giving a v_t such that out = std::reduce(in, f, init) (std::accumulate if less + * than C++17) + */ +template +class ReduceNode : public Node +{ + public: + /** + * Trasformation operator profile + */ + using ReduceOperator = std::function; + + /** + * \brief Construct an identity transformer + * \param instanceName + */ + explicit ReduceNode( const std::string& instanceName ); + + /** + * \brief Construct a transformer with the given operator + * \param instanceName + * \param op + * \param initialValue + */ + ReduceNode( const std::string& instanceName, ReduceOperator op, v_t initialValue = v_t {} ); + + void init() override; + void execute() override; + + /// Sets the operator on the node + void setOperator( ReduceOperator op, v_t initialValue = v_t {} ); + + protected: + ReduceNode( const std::string& instanceName, + const std::string& typeName, + ReduceOperator op, + v_t initialValue ); + + void toJsonInternal( nlohmann::json& data ) const override; + void fromJsonInternal( const nlohmann::json& data ) override; + + private: + ReduceOperator m_operator; + v_t m_result; + v_t m_init; + + public: + static const std::string& getTypename(); +}; + +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra + +#include diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl new file mode 100644 index 00000000000..3609389e40e --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl @@ -0,0 +1,99 @@ +#pragma once +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +template +ReduceNode::ReduceNode( const std::string& instanceName ) : + ReduceNode( + instanceName, + getTypename(), + []( const v_t& a, const v_t& ) -> v_t { return a; }, + v_t {} ) {} + +template +ReduceNode::ReduceNode( const std::string& instanceName, + ReduceOperator op, + v_t initialValue ) : + ReduceNode( instanceName, getTypename(), op, initialValue ) {} + +template +void ReduceNode::setOperator( ReduceOperator op, v_t initialValue ) { + m_operator = op; + m_init = initialValue; +} + +template +void ReduceNode::init() { + Node::init(); + m_result = m_init; +} + +template +void ReduceNode::execute() { + auto f = m_operator; + auto iv = m_init; + auto ivp = dynamic_cast*>( m_inputs[2].get() ); + if ( ivp->isLinked() ) { iv = ivp->getData(); } + m_result = iv; + auto predPort = dynamic_cast*>( m_inputs[1].get() ); + if ( predPort->isLinked() ) { f = predPort->getData(); } + auto input = dynamic_cast*>( m_inputs[0].get() ); + if ( input->isLinked() ) { + const auto& inData = input->getData(); + m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); +#ifdef GRAPH_CALL_TRACE + std::cout << "\e[36m\e[1mMReduceNode \e[0m \"" << m_instanceName << "\": execute, from " + << input->getData().size() << " " << typeid( T ).name() << "." << std::endl; +#endif + } +} + +template +const std::string& ReduceNode::getTypename() { + static std::string demangledName = + std::string { "Reduce<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; +} + +template +ReduceNode::ReduceNode( const std::string& instanceName, + const std::string& typeName, + ReduceOperator op, + v_t initialValue ) : + Node( instanceName, typeName ), m_operator( op ), m_init( initialValue ) { + auto in = new PortIn( "in", this ); + addInput( in ); + in->mustBeLinked(); + + auto f = new PortIn( "f", this ); + addInput( f ); + + auto iv = new PortIn( "init", this ); + addInput( iv ); + + auto out = new PortOut( "out", this ); + addOutput( out, &m_result ); +} + +template +void ReduceNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string { "Reduce operator could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +void ReduceNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; +} + +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index d616d19fe6a..e8cbd4ee429 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -17,6 +17,7 @@ set(dataflow_core_headers Nodes/CoreBuiltInsNodes.hpp Nodes/Functionals/CoreDataFunctionals.hpp Nodes/Functionals/FilterNode.hpp + Nodes/Functionals/ReduceNode.hpp Nodes/Functionals/TransformNode.hpp Nodes/Sinks/CoreDataSinks.hpp Nodes/Sinks/SinkNode.hpp @@ -34,6 +35,7 @@ set(dataflow_core_inlines Node.inl NodeFactory.inl Nodes/Functionals/FilterNode.inl + Nodes/Functionals/ReduceNode.inl Nodes/Functionals/TransformNode.inl Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl From 1dfed689fe6c0d6be13dcb010d4fa888bcafd1cb Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 6 Oct 2022 18:39:04 +0200 Subject: [PATCH 035/239] [example] add test on reduce operator --- .../FunctionalsGraph/main.cpp | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/examples/DataflowExamples/FunctionalsGraph/main.cpp b/examples/DataflowExamples/FunctionalsGraph/main.cpp index 29be66359dd..506de95baba 100644 --- a/examples/DataflowExamples/FunctionalsGraph/main.cpp +++ b/examples/DataflowExamples/FunctionalsGraph/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -25,6 +26,14 @@ int main( int argc, char* argv[] ) { Functionals::TransformNode>::TransformOperator oneMinusMe = []( const Scalar& i ) -> Scalar { return 1_ra - i; }; transformNode->setOperator( oneMinusMe ); + + auto reduceNode = new Functionals::ReduceNode>( "Minimum" ); + Functionals::ReduceNode>::ReduceOperator getMin = + []( const Scalar& a, const Scalar& b ) -> Scalar { return std::min( a, b ); }; + reduceNode->setOperator( getMin, std::numeric_limits::max() ); + + auto scalarSinkNode = new Sinks::SinkNode( "ScalarSink" ); + auto sinkNode = new Sinks::SinkNode>( "Sink" ); //! [Creating Nodes] @@ -32,12 +41,16 @@ int main( int argc, char* argv[] ) { g.addNode( sourceNode ); g.addNode( mapSource ); g.addNode( transformNode ); + g.addNode( reduceNode ); g.addNode( sinkNode ); + g.addNode( scalarSinkNode ); //! [Adding Nodes to the graph] //! [Creating links between Nodes] g.addLink( sourceNode, "to", transformNode, "in" ); g.addLink( transformNode, "out", sinkNode, "from" ); + g.addLink( transformNode, "out", reduceNode, "in" ); + g.addLink( reduceNode, "out", scalarSinkNode, "from" ); //! [Creating links between Nodes] //! [Verifing the graph can be compiled] @@ -59,7 +72,8 @@ int main( int argc, char* argv[] ) { }; opSetter->setData( &doubleMe ); - auto output = g.getDataGetter( "Sink_from" ); + auto output = g.getDataGetter( "Sink_from" ); + auto minimum = g.getDataGetter( "ScalarSink_from" ); // The reference to the result will not be available before the first run // auto& result = output->getData>(); ///! [Configure the interface ports (input and output of the graph) @@ -83,7 +97,8 @@ int main( int argc, char* argv[] ) { //! [Execute the graph] g.execute(); // The reference to the result is now available - auto& result = output->getData>(); + auto& result = output->getData>(); + auto& minValue = minimum->getData(); //! [Execute the graph] //! [Print the output result] @@ -92,6 +107,7 @@ int main( int argc, char* argv[] ) { std::cout << ord << ' '; } std::cout << '\n'; + std::cout << "Min value is : " << minValue << "\n"; //! [Print the output result] //! [Verify the result] @@ -128,6 +144,7 @@ int main( int argc, char* argv[] ) { std::cout << ord << ' '; } std::cout << '\n'; + std::cout << "Min value is : " << minValue << "\n"; execOK = true; for ( int i = 0; i < 10; ++i ) { if ( result[i] != doubleMe( test[i] ) ) { @@ -142,5 +159,7 @@ int main( int argc, char* argv[] ) { } //! [Execute and test the result] + //! [ModifyGraph to get the min value] + //! [ModifyGraph to get the min value] return 0; } From 7373dbafad213b66e63460ac0ca661fabe6f7a48 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 10 Oct 2022 10:08:10 +0200 Subject: [PATCH 036/239] [dataflow] improve node's type readability --- src/Dataflow/Core/TypeDemangler.cpp | 41 +++++++++++++++++++++++++++++ src/Dataflow/Core/TypeDemangler.inl | 17 +++++++----- src/Dataflow/Core/filelist.cmake | 2 +- 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/Dataflow/Core/TypeDemangler.cpp diff --git a/src/Dataflow/Core/TypeDemangler.cpp b/src/Dataflow/Core/TypeDemangler.cpp new file mode 100644 index 00000000000..707d128b15d --- /dev/null +++ b/src/Dataflow/Core/TypeDemangler.cpp @@ -0,0 +1,41 @@ +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +namespace TypeInternal { +std::string makeTypeReadable( std::string fullType ) { + static Ra::Core::Utils::BijectiveAssociation knownTypes { + { "std::", "" }, + { ", std::allocator", "" }, + { ", std::allocator", "" }, + { ", std::allocator", "" }, + { ", std::allocator", "" }, + { "Ra::Core::VectorArray", "RaVector" }, + { "Ra::Core::Utils::ColorBase", "Color" }, + { "Ra::Core::Utils::ColorBase", "Color" }, + { "Eigen::Matrix", "Vector2" }, + { "Eigen::Matrix", "Vector2d" }, + { "Eigen::Matrix", "Vector2i" }, + { "Eigen::Matrix", "Vector2ui" }, + { "Eigen::Matrix", "Vector3" }, + { "Eigen::Matrix", "Vector3d" }, + { "Eigen::Matrix", "Vector3i" }, + { "Eigen::Matrix", "Vector3ui" }, + { "Eigen::Matrix", "Vector4" }, + { "Eigen::Matrix", "Vector4d" }, + { "Eigen::Matrix", "Vector4i" }, + { "Eigen::Matrix", "Vector4ui" } }; + + for ( const auto& [key, value] : knownTypes ) { + Ra::Core::Utils::replaceAllInString( fullType, key, value ); + } + return fullType; +} + +} // namespace TypeInternal +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/TypeDemangler.inl b/src/Dataflow/Core/TypeDemangler.inl index 650e7feb61b..29a1e325515 100644 --- a/src/Dataflow/Core/TypeDemangler.inl +++ b/src/Dataflow/Core/TypeDemangler.inl @@ -6,16 +6,19 @@ namespace Ra { namespace Dataflow { namespace Core { +namespace TypeInternal { +std::string makeTypeReadable( std::string ); + +} /// Return the human readable version of the type name T with simplified radium types template const char* simplifiedDemangledType() noexcept { - static std::string demangledType = Ra::Core::Utils::demangleType(); - Ra::Core::Utils::replaceAllInString( demangledType, "Ra::Core::VectorArray", "RaVector" ); - Ra::Core::Utils::replaceAllInString( - demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); - Ra::Core::Utils::replaceAllInString( - demangledType, "Ra::Core::Utils::ColorBase", "RaColor" ); - return demangledType.c_str(); + static auto demangled_name = []() { + std::string demangledType = + TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); + return demangledType; + }(); + return demangled_name.data(); } } // namespace Core diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index e8cbd4ee429..ff0e59dd9d8 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -5,7 +5,7 @@ # ---------------------------------------------------- set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp Nodes/CoreBuiltInsNodes.cpp - Nodes/Sources/CoreDataSources.cpp + Nodes/Sources/CoreDataSources.cpp TypeDemangler.cpp ) set(dataflow_core_headers From 59b8219146e08b648186b86582a043b828c0d79c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 10 Oct 2022 10:08:54 +0200 Subject: [PATCH 037/239] [examples][dataflow] remove unneeded include --- examples/DataflowExamples/FunctionalsGraph/main.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/DataflowExamples/FunctionalsGraph/main.cpp b/examples/DataflowExamples/FunctionalsGraph/main.cpp index 506de95baba..f4758de48ca 100644 --- a/examples/DataflowExamples/FunctionalsGraph/main.cpp +++ b/examples/DataflowExamples/FunctionalsGraph/main.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -159,7 +158,5 @@ int main( int argc, char* argv[] ) { } //! [Execute and test the result] - //! [ModifyGraph to get the min value] - //! [ModifyGraph to get the min value] return 0; } From 100d6546857e060bf30a3fac1c608bc1901cef09 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 10 Oct 2022 23:20:53 +0200 Subject: [PATCH 038/239] [dataflow] unify Sources, Sinks and Functional nodes declaration and registration --- examples/DataflowExamples/HelloGraph/main.cpp | 8 +- src/Dataflow/Core/DataflowGraph.cpp | 20 ++- src/Dataflow/Core/DataflowGraph.hpp | 5 +- src/Dataflow/Core/Node.hpp | 7 + src/Dataflow/Core/Node.inl | 18 +++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 128 ++++++++++-------- .../Nodes/Functionals/CoreDataFunctionals.hpp | 3 + .../Core/Nodes/Functionals/FilterNode.inl | 4 +- .../Core/Nodes/Functionals/ReduceNode.inl | 6 +- .../Core/Nodes/Functionals/TransformNode.inl | 4 +- .../Core/Nodes/Sinks/CoreDataSinks.hpp | 41 ++++-- .../Core/Nodes/Sinks/CoreDataSinks.inl | 10 -- .../Core/Nodes/Sources/CoreDataSources.cpp | 128 ------------------ .../Core/Nodes/Sources/CoreDataSources.hpp | 105 +++++++++++--- .../Core/Nodes/Sources/FunctionSource.hpp | 4 +- src/Dataflow/Core/Port.inl | 2 +- src/Dataflow/Core/filelist.cmake | 3 +- 17 files changed, 242 insertions(+), 254 deletions(-) delete mode 100644 src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl delete mode 100644 src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index 329fe7ebb3e..2b924ca3866 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -17,7 +17,7 @@ int main( int argc, char* argv[] ) { //! [Creating Nodes] auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); - auto predicateNode = new Sources::ScalarUnaryPredicate( "Selector" ); + auto predicateNode = new Sources::ScalarUnaryPredicateSource( "Selector" ); auto filterNode = new Functionals::FilterNode>( "Filter" ); auto sinkNode = new Sinks::SinkNode>( "Sink" ); //! [Creating Nodes] @@ -63,8 +63,8 @@ int main( int argc, char* argv[] ) { std::vector test; input->setData( &test ); - auto selector = g.getDataSetter( "Selector_f" ); - Sources::ScalarUnaryPredicate::function_type pred = []( Scalar x ) { return x < 0.5; }; + auto selector = g.getDataSetter( "Selector_f" ); + Sources::ScalarUnaryPredicateSource::function_type pred = []( Scalar x ) { return x < 0.5; }; selector->setData( &pred ); auto output = g.getDataGetter( "Sink_from" ); @@ -103,7 +103,7 @@ int main( int argc, char* argv[] ) { //! [Print the output result] //! [Modify input and rerun the graph] - Sources::ScalarUnaryPredicate::function_type predbig = []( Scalar x ) { return x > 0.5; }; + Sources::ScalarUnaryPredicateSource::function_type predbig = []( Scalar x ) { return x > 0.5; }; selector->setData( &predbig ); g.execute(); std::cout << "Output values after second execution: " << result.size() << "\n\t"; diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index e9e7d7d1fa7..b94ed2c2eae 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -23,10 +23,7 @@ void DataflowGraph::init() { Node::init(); std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { std::for_each( level.begin(), level.end(), []( auto node ) { - if ( !node->m_initialized ) { - node->init(); - node->m_initialized = true; - } + if ( !node->m_initialized ) { node->init(); } } ); } ); } @@ -54,15 +51,16 @@ void DataflowGraph::execute() { } void DataflowGraph::destroy() { - Node::destroy(); - int i = 0; - std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&i]( const auto& level ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "- \e[1mLevel " << i << "\e[0m" << std::endl; -#endif - i++; + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( auto& level ) { std::for_each( level.begin(), level.end(), []( auto node ) { node->destroy(); } ); + level.clear(); } ); + m_nodesByLevel.clear(); + m_nodes.clear(); + m_factories.reset(); + m_dataSetters.clear(); + m_dataGetters.clear(); + Node::destroy(); } void DataflowGraph::saveToJson( const std::string& jsonFilePath ) { diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index e91a292ca8d..897ed693497 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -114,10 +114,9 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Creates an output port connected to the named input port of the graph. /// Return the connected output port if success, transferring the ownership to the caller. - /// Allows to set data to the graph from the caller. + /// Allows to set data to the graph from the caller . /// \note As ownership is transferred to the caller, the graph must survive the returned - /// pointer. \note If called multiple times for the same port, only the last returned result is - /// usable. + /// pointer (but tested more robust than that). /// @params portName The name of the input port of the graph /// TODO : Thereis a bug ???? When listing the data setters, they are connected ... /// TODO : setters (and getters) Should be created once and activated/deactivated ??? diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 910aabd269c..efbdd0ab9dc 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -197,6 +197,13 @@ class RA_DATAFLOW_API Node /// \return true if the editable parameter is found and removed. bool removeEditableParameter( const std::string& name ); + /// \brief get a typed reference to the editable parameter. + /// \tparam E The type of the expected editable parameter. + /// \param name The name of the editable parameter to get. + /// \return the pointer to the editable parameter if any, nullptr if not. + template + EditableParameter* getEditableParameter( const std::string& name ); + /// The deletable status of the node bool m_isDeletable { true }; diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index abcc6834096..8216ff76f5b 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -12,6 +12,7 @@ inline void Node::init() { std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName << "\": initialization." << std::endl; #endif + m_initialized = true; } inline void Node::destroy() { @@ -19,6 +20,10 @@ inline void Node::destroy() { std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName << "\": destroy." << std::endl; #endif + m_interface.clear(); + m_inputs.clear(); + m_outputs.clear(); + m_editableParameters.clear(); } inline const nlohmann::json& Node::getJsonMetaData() { @@ -134,6 +139,19 @@ inline bool Node::removeEditableParameter( const std::string& name ) { return found; } +template +inline EditableParameter* Node::getEditableParameter( const std::string& name ) { + auto it = m_editableParameters.begin(); + while ( it != m_editableParameters.end() ) { + if ( ( *it ).get()->getName() == name ) { + auto p = dynamic_cast*>( ( *it ).get() ); + if ( p != nullptr ) { return *p; } + } + ++it; + } + return nullptr; +} + inline const std::string& Node::getTypename() { static std::string demangledTypeName { "Abstract Node" }; return demangledTypeName; diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index eb9503e444b..56cebde5e18 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -10,7 +10,7 @@ namespace Core { namespace NodeFactoriesManager { /** TODO : replace this by factory autoregistration at compile time */ -#define ADD_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ +#define ADD_FUNCTIONALS_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ FACTORY->registerNodeCreator( \ NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ FACTORY->registerNodeCreator( \ @@ -18,27 +18,36 @@ namespace NodeFactoriesManager { FACTORY->registerNodeCreator( \ NAMESPACE::ArrayReducer##SUFFIX::getTypename() + "_", #NAMESPACE ) -#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ - FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayDataSource##SUFFIX::getTypename() + "_", #NAMESPACE ) +#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##Source::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##ArraySource::getTypename() + "_", #NAMESPACE ) + +#define ADD_SINKS_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##Sink::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##ArraySink::getTypename() + "_", #NAMESPACE ) void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; /* --- Sources --- */ - coreFactory->registerNodeCreator( - Sources::BooleanValueSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::IntValueSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::UIntValueSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::ScalarValueSource::getTypename() + "_", "Source" ); - coreFactory->registerNodeCreator( - Sources::ColorSourceNode::getTypename() + "_", "Source" ); - + // bool could not be declared as others, because of the specificity of std::vector that is + // not compatible with Ra::Core::VectorArray implementation see + // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no + // Ra::Core::VectorArray of bool + coreFactory->registerNodeCreator( + Sources::BooleanSource::getTypename() + "_", "Sources" ); + // prevent Scalar type collision with float or double in factory +#ifdef CORE_USE_DOUBLE ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Float ); +#else + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Double ); +#endif + ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Scalar ); ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Int ); ADD_SOURCES_TO_FACTORY( coreFactory, Sources, UInt ); ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Color ); @@ -56,49 +65,62 @@ void registerStandardFactories() { ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4ui ); /* --- Sinks --- */ + // bool could not be declared as others, because of the specificity of std::vector that is + // not compatible with Ra::Core::VectorArray implementation see + // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no + // Ra::Core::VectorArray of bool coreFactory->registerNodeCreator( Sinks::BooleanSink::getTypename() + "_", - "Sink" ); - coreFactory->registerNodeCreator( Sinks::IntSink::getTypename() + "_", "Sink" ); - coreFactory->registerNodeCreator( Sinks::UIntSink::getTypename() + "_", - "Sink" ); - coreFactory->registerNodeCreator( Sinks::ScalarSink::getTypename() + "_", - "Sink" ); - coreFactory->registerNodeCreator( Sinks::ColorSink::getTypename() + "_", - "Sink" ); - coreFactory->registerNodeCreator( - Sinks::FloatArraySink::getTypename() + "_", "Sink" ); - coreFactory->registerNodeCreator( - Sinks::DoubleArraySink::getTypename() + "_", "Sink" ); - coreFactory->registerNodeCreator( Sinks::IntArraySink::getTypename() + "_", - "Sink" ); - coreFactory->registerNodeCreator( - Sinks::UIntArraySink::getTypename() + "_", "Sink" ); - coreFactory->registerNodeCreator( - Sinks::ColorArraySink::getTypename() + "_", "Sink" ); + "Sinks" ); +#ifdef CORE_USE_DOUBLE + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Float ); +#else + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Double ); +#endif + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Scalar ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Int ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, UInt ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Color ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2f ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2d ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3f ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3d ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4f ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4d ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2i ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2ui ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3i ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3ui ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4i ); + ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4ui ); /* --- Functionals */ - ADD_TO_FACTORY( coreFactory, Functionals, Float ); - ADD_TO_FACTORY( coreFactory, Functionals, Int ); - ADD_TO_FACTORY( coreFactory, Functionals, UInt ); - ADD_TO_FACTORY( coreFactory, Functionals, Color ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector2f ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector2d ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector3f ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector3d ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector4f ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector4d ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector2i ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector2ui ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector3i ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector3ui ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector4i ); - ADD_TO_FACTORY( coreFactory, Functionals, Vector4ui ); +#ifdef CORE_USE_DOUBLE + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Float ); +#else + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Double ); +#endif + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Scalar ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Int ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, UInt ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Color ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2f ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2d ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3f ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3d ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4f ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4d ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2i ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2ui ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3i ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3ui ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4i ); + ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4ui ); /* --- Functions --- */ - coreFactory->registerNodeCreator( - Sources::ScalarBinaryPredicate::getTypename() + "_", "Functions" ); - coreFactory->registerNodeCreator( - Sources::ScalarUnaryPredicate::getTypename() + "_", "Functions" ); + coreFactory->registerNodeCreator( + Sources::ScalarBinaryPredicateSource::getTypename() + "_", "Functions" ); + coreFactory->registerNodeCreator( + Sources::ScalarUnaryPredicateSource::getTypename() + "_", "Functions" ); /* --- Graphs --- */ coreFactory->registerNodeCreator( DataflowGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp index 940e2c81267..4d2e32f9b49 100644 --- a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include #include @@ -22,6 +24,7 @@ using namespace Ra::Core; DECLARE_FUNCTIONALS( Float, float ); DECLARE_FUNCTIONALS( Double, double ); +DECLARE_FUNCTIONALS( Scalar, Scalar ); DECLARE_FUNCTIONALS( Int, int ); DECLARE_FUNCTIONALS( UInt, unsigned int ); DECLARE_FUNCTIONALS( Color, Utils::Color ); diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl index cb8c3aeeb42..b41094ef1aa 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl @@ -29,10 +29,10 @@ void FilterNode::init() { template void FilterNode::execute() { - auto predPort = dynamic_cast*>( m_inputs[1].get() ); + auto predPort = static_cast*>( m_inputs[1].get() ); auto f = m_predicate; if ( predPort->isLinked() ) { f = predPort->getData(); } - auto input = dynamic_cast*>( m_inputs[0].get() ); + auto input = static_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { const auto& inData = input->getData(); m_elements.clear(); diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl index 3609389e40e..49de249e37c 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl @@ -36,12 +36,12 @@ template void ReduceNode::execute() { auto f = m_operator; auto iv = m_init; - auto ivp = dynamic_cast*>( m_inputs[2].get() ); + auto ivp = static_cast*>( m_inputs[2].get() ); if ( ivp->isLinked() ) { iv = ivp->getData(); } m_result = iv; - auto predPort = dynamic_cast*>( m_inputs[1].get() ); + auto predPort = static_cast*>( m_inputs[1].get() ); if ( predPort->isLinked() ) { f = predPort->getData(); } - auto input = dynamic_cast*>( m_inputs[0].get() ); + auto input = static_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { const auto& inData = input->getData(); m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl index 88d2e3a07aa..2e61229985d 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl @@ -27,10 +27,10 @@ void TransformNode::init() { template void TransformNode::execute() { - auto predPort = dynamic_cast*>( m_inputs[1].get() ); + auto predPort = static_cast*>( m_inputs[1].get() ); auto f = m_operator; if ( predPort->isLinked() ) { f = predPort->getData(); } - auto input = dynamic_cast*>( m_inputs[0].get() ); + auto input = static_cast*>( m_inputs[0].get() ); if ( input->isLinked() ) { const auto& inData = input->getData(); m_elements.clear(); diff --git a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp index b78770c9074..cb252c70038 100644 --- a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp @@ -9,21 +9,38 @@ namespace Dataflow { namespace Core { namespace Sinks { -using BooleanSink = SinkNode; -using IntSink = SinkNode; -using UIntSink = SinkNode; -using ScalarSink = SinkNode; -using ColorSink = SinkNode; +using namespace Ra::Core; + +#define DECLARE_SINKS( PREFIX, TYPE ) \ + using PREFIX##Sink = SinkNode; \ + using PREFIX##ArraySink = SinkNode> -using FloatArraySink = SinkNode>; -using DoubleArraySink = SinkNode>; -using IntArraySink = SinkNode>; -using UIntArraySink = SinkNode>; -using ColorArraySink = SinkNode>; +// bool could not be declared as others, because of the specificity of std::vector that is not +// compatible with Ra::Core::VectorArray implementation see +// https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no +// Ra::Core::VectorArray of bool +using BooleanSink = SinkNode; +DECLARE_SINKS( Float, float ); +DECLARE_SINKS( Double, double ); +DECLARE_SINKS( Scalar, Scalar ); +DECLARE_SINKS( Int, int ); +DECLARE_SINKS( UInt, unsigned int ); +DECLARE_SINKS( Color, Utils::Color ); +DECLARE_SINKS( Vector2f, Vector2f ); +DECLARE_SINKS( Vector2d, Vector2d ); +DECLARE_SINKS( Vector3f, Vector3f ); +DECLARE_SINKS( Vector3d, Vector3d ); +DECLARE_SINKS( Vector4f, Vector4f ); +DECLARE_SINKS( Vector4d, Vector4d ); +DECLARE_SINKS( Vector2i, Vector2i ); +DECLARE_SINKS( Vector3i, Vector3i ); +DECLARE_SINKS( Vector4i, Vector4i ); +DECLARE_SINKS( Vector2ui, Vector2ui ); +DECLARE_SINKS( Vector3ui, Vector3ui ); +DECLARE_SINKS( Vector4ui, Vector4ui ); +#undef DECLARE_SINKS } // namespace Sinks } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl deleted file mode 100644 index 45c67380a9d..00000000000 --- a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.inl +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Sinks {} // namespace Sinks -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp deleted file mode 100644 index 248c60b2f3d..00000000000 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.cpp +++ /dev/null @@ -1,128 +0,0 @@ - -#include - -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Sources { - -/* Source bool */ -BooleanValueSource::BooleanValueSource( const std::string& name ) : - SingleDataSourceNode( name, BooleanValueSource::getTypename() ) { - setEditable( "boolean" ); -} - -void BooleanValueSource::toJsonInternal( nlohmann::json& data ) const { - data["boolean"] = *getData(); -} - -void BooleanValueSource::fromJsonInternal( const nlohmann::json& data ) { - if ( data.contains( "boolean" ) ) { - bool v = data["boolean"]; - setData( v ); - } -} - -const std::string& BooleanValueSource::getTypename() { - static std::string demangledTypeName { "Source" }; - return demangledTypeName; -} - -/* Source int */ -IntValueSource::IntValueSource( const std::string& name ) : - SingleDataSourceNode( name, IntValueSource::getTypename() ) { - setEditable( "value" ); -} - -void IntValueSource::toJsonInternal( nlohmann::json& data ) const { - data["value"] = *getData(); -} - -void IntValueSource::fromJsonInternal( const nlohmann::json& data ) { - if ( data.contains( "value" ) ) { - int v = data["value"]; - setData( v ); - } -} - -const std::string& IntValueSource::getTypename() { - static std::string demangledTypeName { "Source" }; - return demangledTypeName; -} - -/* Source uint */ -UIntValueSource::UIntValueSource( const std::string& name ) : - SingleDataSourceNode( name, UIntValueSource::getTypename() ) { - setEditable( "value" ); -} - -void UIntValueSource::toJsonInternal( nlohmann::json& data ) const { - data["value"] = *getData(); -} - -void UIntValueSource::fromJsonInternal( const nlohmann::json& data ) { - if ( data.contains( "value" ) ) { - unsigned int v = data["value"]; - setData( v ); - } -} - -const std::string& UIntValueSource::getTypename() { - static std::string demangledTypeName { "Source" }; - return demangledTypeName; -} - -/* Source Scalar */ -const std::string& ScalarValueSource::getTypename() { - static std::string demangledTypeName { "Source" }; - return demangledTypeName; -} - -ScalarValueSource::ScalarValueSource( const std::string& name ) : - SingleDataSourceNode( name, ScalarValueSource::getTypename() ) { - setEditable( "number" ); -} - -void ScalarValueSource::toJsonInternal( nlohmann::json& data ) const { - data["number"] = *getData(); -} - -void ScalarValueSource::fromJsonInternal( const nlohmann::json& data ) { - if ( data.contains( "number" ) ) { - Scalar v = data["number"]; - setData( v ); - } -} - -/* Source Color */ -const std::string& ColorSourceNode::getTypename() { - static std::string demangledTypeName { "Source" }; - return demangledTypeName; -} - -ColorSourceNode::ColorSourceNode( const std::string& name ) : - SingleDataSourceNode( name, ColorSourceNode::getTypename() ) { - setEditable( "color" ); -} - -void ColorSourceNode::toJsonInternal( nlohmann::json& data ) const { - auto c = Ra::Core::Utils::Color::linearRGBTosRGB( *getData() ); - std::array color { { c.x(), c.y(), c.z() } }; - data["color"] = color; -} - -void ColorSourceNode::fromJsonInternal( const nlohmann::json& data ) { - if ( data.contains( "color" ) ) { - std::array c = data["color"]; - auto v = - Ra::Core::Utils::Color::sRGBToLinearRGB( Ra::Core::Utils::Color( c[0], c[1], c[2] ) ); - setData( v ); - } -} - -} // namespace Sources -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 56e33db773b..6a4dc8779f6 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -92,31 +92,94 @@ class RA_DATAFLOW_API ColorSourceNode : public SingleDataSourceNode> +#define DECLARE_COREDATA_SOURCES( PREFIX, TYPE ) \ + using PREFIX##Source = SingleDataSourceNode; \ + using PREFIX##ArraySource = SingleDataSourceNode> using namespace Ra::Core; -DECLARE_ARRAYSOURCES( Float, float ); -DECLARE_ARRAYSOURCES( Double, double ); -DECLARE_ARRAYSOURCES( Int, int ); -DECLARE_ARRAYSOURCES( UInt, unsigned int ); -DECLARE_ARRAYSOURCES( Color, Utils::Color ); -DECLARE_ARRAYSOURCES( Vector2f, Vector2f ); -DECLARE_ARRAYSOURCES( Vector2d, Vector2d ); -DECLARE_ARRAYSOURCES( Vector3f, Vector3f ); -DECLARE_ARRAYSOURCES( Vector3d, Vector3d ); -DECLARE_ARRAYSOURCES( Vector4f, Vector4f ); -DECLARE_ARRAYSOURCES( Vector4d, Vector4d ); -DECLARE_ARRAYSOURCES( Vector2i, Vector2i ); -DECLARE_ARRAYSOURCES( Vector3i, Vector3i ); -DECLARE_ARRAYSOURCES( Vector4i, Vector4i ); -DECLARE_ARRAYSOURCES( Vector2ui, Vector2ui ); -DECLARE_ARRAYSOURCES( Vector3ui, Vector3ui ); -DECLARE_ARRAYSOURCES( Vector4ui, Vector4ui ); - -#undef DECLARE_ARRAYSOURCES +// bool could not be declared as others, because of the specificity of std::vector that is not +// compatible with Ra::Core::VectorArray implementation see +// https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no +// Ra::Core::VectorArray of bool +using BooleanSource = SingleDataSourceNode; +DECLARE_COREDATA_SOURCES( Float, float ); +DECLARE_COREDATA_SOURCES( Double, double ); +DECLARE_COREDATA_SOURCES( Scalar, Scalar ); +DECLARE_COREDATA_SOURCES( Int, int ); +DECLARE_COREDATA_SOURCES( UInt, unsigned int ); +DECLARE_COREDATA_SOURCES( Color, Utils::Color ); +DECLARE_COREDATA_SOURCES( Vector2f, Vector2f ); +DECLARE_COREDATA_SOURCES( Vector2d, Vector2d ); +DECLARE_COREDATA_SOURCES( Vector3f, Vector3f ); +DECLARE_COREDATA_SOURCES( Vector3d, Vector3d ); +DECLARE_COREDATA_SOURCES( Vector4f, Vector4f ); +DECLARE_COREDATA_SOURCES( Vector4d, Vector4d ); +DECLARE_COREDATA_SOURCES( Vector2i, Vector2i ); +DECLARE_COREDATA_SOURCES( Vector3i, Vector3i ); +DECLARE_COREDATA_SOURCES( Vector4i, Vector4i ); +DECLARE_COREDATA_SOURCES( Vector2ui, Vector2ui ); +DECLARE_COREDATA_SOURCES( Vector3ui, Vector3ui ); +DECLARE_COREDATA_SOURCES( Vector4ui, Vector4ui ); + +#undef DECLARE_COREDATA_SOURCES + +// Partial specialisation for editable data sources +#define SPECIALIZE_EDITABLE_SOURCE( TYPE, NAME ) \ + template <> \ + inline SingleDataSourceNode::SingleDataSourceNode( const std::string& name ) : \ + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { \ + setEditable( #NAME ); \ + } \ + \ + template <> \ + inline void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { \ + data[#NAME] = *getData(); \ + } \ + \ + template <> \ + inline void SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { \ + if ( data.contains( #NAME ) ) { \ + TYPE v = data[#NAME]; \ + setData( v ); \ + } \ + } + +SPECIALIZE_EDITABLE_SOURCE( bool, boolean ); +SPECIALIZE_EDITABLE_SOURCE( float, number ); +SPECIALIZE_EDITABLE_SOURCE( double, number ); +SPECIALIZE_EDITABLE_SOURCE( int, value ); +SPECIALIZE_EDITABLE_SOURCE( unsigned int, value ); + +// Color specialization need different implementation (as well as any Ra::Vectorxx) +template <> +inline SingleDataSourceNode::SingleDataSourceNode( + const std::string& name ) : + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { + setEditable( "color" ); +} + +template <> +inline void +SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["color"] = *getData(); +} + +template <> +inline void +SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "color" ) ) { + std::array c = data["color"]; + auto v = + Ra::Core::Utils::Color::sRGBToLinearRGB( Ra::Core::Utils::Color( c[0], c[1], c[2] ) ); + setData( v ); + } +} + +#undef SPECIALIZE_EDITABLE_SOURCE } // namespace Sources } // namespace Core diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index 5cde9652bcc..0662e60beef 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -51,8 +51,8 @@ class FunctionSourceNode : public Node static const std::string& getTypename(); }; -using ScalarBinaryPredicate = FunctionSourceNode; -using ScalarUnaryPredicate = FunctionSourceNode; +using ScalarBinaryPredicateSource = FunctionSourceNode; +using ScalarUnaryPredicateSource = FunctionSourceNode; } // namespace Sources } // namespace Core } // namespace Dataflow diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl index 20916ad98da..2e1fe3f4254 100644 --- a/src/Dataflow/Core/Port.inl +++ b/src/Dataflow/Core/Port.inl @@ -175,7 +175,7 @@ bool PortIn::accept( PortBase* other ) { template bool PortIn::connect( PortBase* other ) { if ( accept( other ) ) { - m_from = dynamic_cast*>( other ); + m_from = static_cast*>( other ); m_isLinked = true; // notify after connect this->notify( getName(), *this, true ); diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index ff0e59dd9d8..7304b436cbb 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -5,7 +5,7 @@ # ---------------------------------------------------- set(dataflow_core_sources DataflowGraph.cpp Node.cpp NodeFactory.cpp Nodes/CoreBuiltInsNodes.cpp - Nodes/Sources/CoreDataSources.cpp TypeDemangler.cpp + TypeDemangler.cpp ) set(dataflow_core_headers @@ -37,7 +37,6 @@ set(dataflow_core_inlines Nodes/Functionals/FilterNode.inl Nodes/Functionals/ReduceNode.inl Nodes/Functionals/TransformNode.inl - Nodes/Sinks/CoreDataSinks.inl Nodes/Sinks/SinkNode.inl Nodes/Sources/FunctionSource.inl Nodes/Sources/SingleDataSourceNode.inl From 7e671a725b84c46b2b8543c525aa4f48e4cd492c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 10 Oct 2022 23:21:58 +0200 Subject: [PATCH 039/239] [dataflow-core] add generic binary operator node --- .../Core/Nodes/Functionals/BinaryOpNode.hpp | 187 ++++++++++++++++++ src/Dataflow/Core/filelist.cmake | 1 + 2 files changed, 188 insertions(+) create mode 100644 src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp diff --git a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp new file mode 100644 index 00000000000..e8508c98fcb --- /dev/null +++ b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp @@ -0,0 +1,187 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace Functionals { + +namespace internal { +template +struct ArgTypeHelperInternal { + using value_type = A; + using const_value_ref = const A&; +}; + +template +struct ArgTypeHelperInternal { + using value_type = typename A::value_type; + using const_value_ref = const typename A::value_type&; +}; + +template +struct ArgTypeHelper : public ArgTypeHelperInternal::value> {}; + +// Find a way to evaluate the copy/move semantic of t_out +template ::value, + bool it_b = Ra::Core::Utils::is_container::value, + bool it_out = Ra::Core::Utils::is_container::value> +struct ExecutorHelper { + static t_out executeInternal( t_a&, t_b&, funcType ) { + static_assert( ( ( it_a || it_b ) ? it_out : !it_out ), "Invalid template parameter " ); + } +}; + +template +struct ExecutorHelper { + static t_out executeInternal( t_a& a, t_b& b, funcType f ) { + t_out res; + std::transform( a.begin(), + a.end(), + b.begin(), + std::back_inserter( res ), + [f]( typename ArgTypeHelper::const_value_ref x, + typename ArgTypeHelper::const_value_ref y ) -> + typename ArgTypeHelper::value_type { return f( x, y ); } ); + return res; + } +}; + +template +struct ExecutorHelper { + static t_out executeInternal( t_a& a, t_b& b, funcType f ) { + t_out res; + std::transform( a.begin(), + a.end(), + std::back_inserter( res ), + [&b, f]( typename ArgTypeHelper::const_value_ref x ) -> + typename ArgTypeHelper::value_type { return f( x, b ); } ); + return res; + } +}; + +template +struct ExecutorHelper { + static t_out executeInternal( t_a& a, t_b& b, funcType f ) { + t_out res; + std::transform( b.begin(), + b.end(), + std::back_inserter( res ), + [&a, f]( typename ArgTypeHelper::const_value_ref x ) -> + typename ArgTypeHelper::value_type { return f( a, x ); } ); + return res; + } +}; + +template +struct ExecutorHelper { + static t_out executeInternal( t_a& a, t_b& b, funcType f ) { return f( a, b ); } +}; +} // namespace internal +/** \brief Apply a binary operation on its input + * \tparam v_t type of the element to apply the operator on + */ +template +class BinaryOpNode : public Node +{ + public: + /** + * BinaryOp operator profile + */ + using Arg1_type = typename internal::ArgTypeHelper::const_value_ref; + using Arg2_type = typename internal::ArgTypeHelper::const_value_ref; + using Res_type = typename internal::ArgTypeHelper::value_type; + using BinaryOperator = std::function; + + /** + * \brief Construct an additioner + * \param instanceName + */ + explicit BinaryOpNode( const std::string& instanceName ) : + BinaryOpNode( instanceName, getTypename(), []( Arg1_type, Arg2_type ) -> Res_type { + return Res_type {}; + } ) {} + + /** + * \brief Construct a BinaryOpNode with the given operator + * \param instanceName + * \param filterFunction + */ + BinaryOpNode( const std::string& instanceName, BinaryOperator op ) : + BinaryOpNode( instanceName, getTypename(), op ) {} + + void init() override { + Node::init(); + m_result = t_out {}; + } + void execute() override { + auto f = m_operator; + auto p_f = static_cast*>( m_inputs[2].get() ); + if ( p_f->isLinked() ) { f = p_f->getData(); } + auto p_a = static_cast*>( m_inputs[0].get() ); + auto p_b = static_cast*>( m_inputs[1].get() ); + if ( p_a->isLinked() && p_b->isLinked() ) { + m_result = internal::ExecutorHelper::executeInternal( + p_a->getData(), p_b->getData(), f ); + } + } + + /// Sets the operator on the node + void setOperator( BinaryOperator op ) { m_operator = op; } + + protected: + BinaryOpNode( const std::string& instanceName, + const std::string& typeName, + BinaryOperator op ) : + Node( instanceName, typeName ), m_operator( op ) { + auto in_a = new PortIn( "a", this ); + addInput( in_a ); + in_a->mustBeLinked(); + auto in_b = new PortIn( "b", this ); + addInput( in_b ); + in_b->mustBeLinked(); + auto f = new PortIn( "f", this ); + addInput( f ); + auto out = new PortOut( "r", this ); + addOutput( out, &m_result ); + } + + void toJsonInternal( nlohmann::json& data ) const override { + data["comment"] = + std::string { "Binary operator could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; + } + + void fromJsonInternal( const nlohmann::json& ) override { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; + } + + private: + BinaryOperator m_operator = []( Arg1_type, Arg2_type ) -> Res_type { return Res_type {}; }; + t_out m_result; + + public: + static const std::string& getTypename() { + static std::string demangledName = + std::string { "BinaryOp<" } + Ra::Dataflow::Core::simplifiedDemangledType() + + " x " + Ra::Dataflow::Core::simplifiedDemangledType() + " -> " + + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; + } +}; + +// implementation of the methods +// see issue .inl coding style #1011 + +} // namespace Functionals +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index 7304b436cbb..a15e7bab858 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -15,6 +15,7 @@ set(dataflow_core_headers Node.hpp NodeFactory.hpp Nodes/CoreBuiltInsNodes.hpp + Nodes/Functionals/BinaryOpNode.hpp Nodes/Functionals/CoreDataFunctionals.hpp Nodes/Functionals/FilterNode.hpp Nodes/Functionals/ReduceNode.hpp From 6524ca40bf477cd1ad702e9c048387cfceb74c68 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 10 Oct 2022 23:22:15 +0200 Subject: [PATCH 040/239] [unittests][dataflow] add first dataflow node unit tests --- tests/unittest/CMakeLists.txt | 3 +- tests/unittest/Dataflow/nodes.cpp | 313 ++++++++++++++++++++++++++++++ 2 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 tests/unittest/Dataflow/nodes.cpp diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index 24b0e020cf8..48e545f901c 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -31,6 +31,7 @@ set(test_src Core/topomesh.cpp Core/variableset.cpp Core/vectorarray.cpp + Dataflow/nodes.cpp Engine/environmentmap.cpp Engine/renderparameters.cpp Engine/signalmanager.cpp @@ -55,7 +56,7 @@ target_include_directories(unittests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_compile_options(unittests PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) target_compile_definitions(unittests PRIVATE UNIT_TESTS) # add -DUNIT_TESTS define -target_link_libraries(unittests PRIVATE Catch2::Catch2 Core Engine Gui) +target_link_libraries(unittests PRIVATE Catch2::Catch2 Core Engine Gui Dataflow) add_dependencies(unittests Catch2 Core Engine Gui) find_package(Filesystem COMPONENTS Final Experimental REQUIRED) diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp new file mode 100644 index 00000000000..f12c281cde2 --- /dev/null +++ b/tests/unittest/Dataflow/nodes.cpp @@ -0,0 +1,313 @@ +#include + +#include +#include + +#include + +#include +#include + +TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { + SECTION( "Operations on Scalar" ) { + using namespace Ra::Dataflow::Core; + using DataType = Scalar; + DataflowGraph g { "testBinOp" }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g.addNode( source_a ); + auto a = g.getDataSetter( "a_to" ); + REQUIRE( a->getNode() == &g ); + + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g.addNode( source_b ); + auto b = g.getDataSetter( "b_to" ); + REQUIRE( b->getNode() == &g ); + + auto sink = new Sinks::SinkNode( "r" ); + g.addNode( sink ); + auto r = g.getDataGetter( "r_from" ); + REQUIRE( r->getNode() == &g ); + + using TestNode = Functionals::BinaryOpNode; + TestNode::BinaryOperator add = []( TestNode::Arg1_type a, + TestNode::Arg2_type b ) -> TestNode::Res_type { + return a + b; + }; + auto op = new TestNode( "additionner" ); + op->setOperator( add ); + g.addNode( op ); + + REQUIRE( g.addLink( source_a, "to", op, "a" ) ); + REQUIRE( g.addLink( source_b, "to", op, "b" ) ); + REQUIRE( g.addLink( op, "r", sink, "from" ) ); + REQUIRE( g.compile() ); + + DataType x { 1_ra }; + a->setData( &x ); + REQUIRE( a->getData() == x ); + + DataType y { 2_ra }; + b->setData( &y ); + REQUIRE( b->getData() == y ); + + g.execute(); + + auto& z = r->getData(); + REQUIRE( z == x + y ); + + std::cout << x << " + " << y << " == " << z << "\n"; + + g.destroy(); + } + + SECTION( "Operations on Vectors" ) { + using namespace Ra::Dataflow::Core; + using DataType = Ra::Core::Vector3; + DataflowGraph g { "testBinOp" }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g.addNode( source_a ); + auto a = g.getDataSetter( "a_to" ); + REQUIRE( a->getNode() == &g ); + + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g.addNode( source_b ); + auto b = g.getDataSetter( "b_to" ); + REQUIRE( b->getNode() == &g ); + + auto sink = new Sinks::SinkNode( "r" ); + g.addNode( sink ); + auto r = g.getDataGetter( "r_from" ); + REQUIRE( r->getNode() == &g ); + + using TestNode = Functionals::BinaryOpNode; + TestNode::BinaryOperator add = []( TestNode::Arg1_type a, + TestNode::Arg2_type b ) -> TestNode::Res_type { + return a + b; + }; + auto op = new TestNode( "additionner" ); + op->setOperator( add ); + g.addNode( op ); + + REQUIRE( g.addLink( source_a, "to", op, "a" ) ); + REQUIRE( g.addLink( source_b, "to", op, "b" ) ); + REQUIRE( g.addLink( op, "r", sink, "from" ) ); + REQUIRE( g.compile() ); + + DataType x { 1_ra, 2_ra, 3_ra }; + a->setData( &x ); + REQUIRE( a->getData() == x ); + + DataType y { 3_ra, 2_ra, 1_ra }; + b->setData( &y ); + REQUIRE( b->getData() == y ); + + g.execute(); + + auto& z = r->getData(); + REQUIRE( z == x + y ); + + std::cout << "[" << x.transpose() << "] + [" << y.transpose() << "] == [" << z.transpose() + << "]\n"; + + g.destroy(); + } + + SECTION( "Operations on VectorArrays" ) { + using namespace Ra::Dataflow::Core; + using DataType = Ra::Core::VectorArray; + DataflowGraph g { "testBinOp" }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g.addNode( source_a ); + auto a = g.getDataSetter( "a_to" ); + REQUIRE( a->getNode() == &g ); + + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g.addNode( source_b ); + auto b = g.getDataSetter( "b_to" ); + REQUIRE( b->getNode() == &g ); + + auto sink = new Sinks::SinkNode( "r" ); + g.addNode( sink ); + auto r = g.getDataGetter( "r_from" ); + REQUIRE( r->getNode() == &g ); + + using TestNode = Functionals::BinaryOpNode; + TestNode::BinaryOperator add = []( TestNode::Arg1_type a, + TestNode::Arg2_type b ) -> TestNode::Res_type { + return a + b; + }; + auto op = new TestNode( "additionner" ); + op->setOperator( add ); + g.addNode( op ); + + REQUIRE( g.addLink( source_a, "to", op, "a" ) ); + REQUIRE( g.addLink( source_b, "to", op, "b" ) ); + REQUIRE( g.addLink( op, "r", sink, "from" ) ); + REQUIRE( g.compile() ); + + DataType x { { 1_ra, 2_ra }, { 3_ra, 4_ra } }; + a->setData( &x ); + REQUIRE( a->getData() == x ); + + DataType y { { 5_ra, 6_ra }, { 7_ra, 8_ra } }; + b->setData( &y ); + REQUIRE( b->getData() == y ); + + g.execute(); + + auto& z = r->getData(); + for ( size_t i = 0; i < z.size(); i++ ) { + REQUIRE( z[i] == x[i] + y[i] ); + } + + std::cout << "{ "; + for ( const auto& t : x ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "} + { "; + for ( const auto& t : y ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "} = { "; + for ( const auto& t : z ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "}\n"; + g.destroy(); + } + + SECTION( "Operations between VectorArray and Scalar" ) { + using namespace Ra::Dataflow::Core; + using DataType_a = Ra::Core::VectorArray; + using DataType_b = Scalar; + + // How to do this ? Eigen generates an error due to align allocation + // using DataType_r = Ra::Core::VectorArray< decltype( std::declval() * + // std::declval() ) >; + using DataType_r = Ra::Core::VectorArray; + + DataflowGraph g { "testBinOp" }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g.addNode( source_a ); + auto a = g.getDataSetter( "a_to" ); + REQUIRE( a->getNode() == &g ); + + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g.addNode( source_b ); + auto b = g.getDataSetter( "b_to" ); + REQUIRE( b->getNode() == &g ); + + auto sink = new Sinks::SinkNode( "r" ); + g.addNode( sink ); + auto r = g.getDataGetter( "r_from" ); + REQUIRE( r->getNode() == &g ); + + using TestNode = Functionals::BinaryOpNode; + TestNode::BinaryOperator add = []( TestNode::Arg1_type a, + TestNode::Arg2_type b ) -> TestNode::Res_type { + return a * b; + }; + auto op = new TestNode( "additionner" ); + op->setOperator( add ); + g.addNode( op ); + + REQUIRE( g.addLink( source_a, "to", op, "a" ) ); + REQUIRE( g.addLink( source_b, "to", op, "b" ) ); + REQUIRE( g.addLink( op, "r", sink, "from" ) ); + REQUIRE( g.compile() ); + + DataType_a x { { 1_ra, 2_ra }, { 3_ra, 4_ra } }; + a->setData( &x ); + REQUIRE( a->getData() == x ); + + DataType_b y { 5_ra }; + b->setData( &y ); + REQUIRE( b->getData() == y ); + + g.execute(); + + auto& z = r->getData(); + for ( size_t i = 0; i < z.size(); i++ ) { + REQUIRE( z[i] == x[i] * y ); + } + + std::cout << "{ "; + for ( const auto& t : x ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "} * " << y << " = { "; + for ( const auto& t : z ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "}\n"; + g.destroy(); + } + + SECTION( "Operations between VectorArray and Scalar" ) { + using namespace Ra::Dataflow::Core; + using DataType_b = Ra::Core::VectorArray; + using DataType_a = Scalar; + using DataType_r = Ra::Core::VectorArray; + + DataflowGraph g { "testBinOp" }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g.addNode( source_a ); + auto a = g.getDataSetter( "a_to" ); + REQUIRE( a->getNode() == &g ); + + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g.addNode( source_b ); + auto b = g.getDataSetter( "b_to" ); + REQUIRE( b->getNode() == &g ); + + auto sink = new Sinks::SinkNode( "r" ); + g.addNode( sink ); + auto r = g.getDataGetter( "r_from" ); + REQUIRE( r->getNode() == &g ); + + using TestNode = Functionals::BinaryOpNode; + TestNode::BinaryOperator add = []( TestNode::Arg1_type a, + TestNode::Arg2_type b ) -> TestNode::Res_type { + return a * b; + }; + auto op = new TestNode( "additionner" ); + op->setOperator( add ); + g.addNode( op ); + + REQUIRE( g.addLink( source_a, "to", op, "a" ) ); + REQUIRE( g.addLink( source_b, "to", op, "b" ) ); + REQUIRE( g.addLink( op, "r", sink, "from" ) ); + REQUIRE( g.compile() ); + + DataType_a x { 4_ra }; + a->setData( &x ); + REQUIRE( a->getData() == x ); + + DataType_b y { { 1_ra, 2_ra }, { 3_ra, 4_ra } }; + b->setData( &y ); + REQUIRE( b->getData() == y ); + + g.execute(); + + auto& z = r->getData(); + for ( size_t i = 0; i < z.size(); i++ ) { + REQUIRE( z[i] == x * y[i] ); + } + + std::cout << x << " * { "; + for ( const auto& t : y ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "} = { "; + for ( const auto& t : z ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "}\n"; + g.destroy(); + } +} From 303dae059ce346d4070074f59172ee0bc737b682 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 06:50:29 +0200 Subject: [PATCH 041/239] [dataflow] remove dead code and fix some method profile --- src/Dataflow/Core/DataflowGraph.cpp | 17 +--- .../Core/Nodes/Sources/CoreDataSources.hpp | 80 ------------------- .../Nodes/Sources/SingleDataSourceNode.hpp | 2 +- .../Nodes/Sources/SingleDataSourceNode.inl | 2 +- 4 files changed, 6 insertions(+), 95 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index b94ed2c2eae..516a791d4d1 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -61,6 +61,7 @@ void DataflowGraph::destroy() { m_dataSetters.clear(); m_dataGetters.clear(); Node::destroy(); + m_ready = false; } void DataflowGraph::saveToJson( const std::string& jsonFilePath ) { @@ -143,10 +144,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { auto factories = data["graph"]["factories"]; for ( const auto& factoryName : factories ) { // Do not add factories already registered for the graph. - if ( m_factories->hasFactory( factoryName ) ) { - std::cerr << "PLOPOPOPOO !!!!\n"; - continue; - } + if ( m_factories->hasFactory( factoryName ) ) { continue; } auto factory = NodeFactoriesManager::getFactory( factoryName ); if ( factory ) { addFactory( factory ); } else { @@ -694,16 +692,9 @@ bool DataflowGraph::releaseDataSetter( std::string portName ) { return false; } +// Why is this method useful if it is the same than getDataSetter ? bool DataflowGraph::activateDataSetter( std::string portName ) { - auto setter = m_dataSetters.find( portName ); - if ( setter != m_dataSetters.end() ) { - auto [desc, in] = setter->second; - auto p = std::get<0>( desc ); - in->disconnect(); - p->connect( in ); - return true; - } - return false; + return getDataSetter( portName ) != nullptr; } std::shared_ptr DataflowGraph::getDataSetter( std::string portName ) { diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 6a4dc8779f6..fc3543b80be 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -12,86 +12,6 @@ namespace Ra { namespace Dataflow { namespace Core { namespace Sources { -/** - * Specialization of SingleDataSourceNode for boolean value - */ -class RA_DATAFLOW_API BooleanValueSource : public SingleDataSourceNode -{ - public: - explicit BooleanValueSource( const std::string& name ); - - protected: - void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; - - public: - static const std::string& getTypename(); -}; - -/** - * Specialization of SingleDataSourceNode for int value - */ -class RA_DATAFLOW_API IntValueSource : public SingleDataSourceNode -{ - public: - explicit IntValueSource( const std::string& name ); - - protected: - void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; - - public: - static const std::string& getTypename(); -}; - -/** - * Specialization of SingleDataSourceNode for unsigned int value - */ -class RA_DATAFLOW_API UIntValueSource : public SingleDataSourceNode -{ - public: - explicit UIntValueSource( const std::string& name ); - - protected: - void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; - - public: - static const std::string& getTypename(); -}; - -/** - * Specialization of SingleDataSourceNode for scalar value - */ -class RA_DATAFLOW_API ScalarValueSource : public SingleDataSourceNode -{ - public: - explicit ScalarValueSource( const std::string& name ); - - protected: - void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; - - public: - static const std::string& getTypename(); -}; - -/** - * Specialization of SingleDataSourceNode for Core::Utils::Color value - */ -class RA_DATAFLOW_API ColorSourceNode : public SingleDataSourceNode -{ - public: - explicit ColorSourceNode( const std::string& name ); - - protected: - void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; - - public: - static const std::string& getTypename(); -}; - // TODO unify editable and non editable data sources // This macro does not end with semicolon. To be added when callin it diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index ec0a8e02bb3..58fc42b663c 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -48,7 +48,7 @@ class SingleDataSourceNode : public Node * @param name Name of the data as it will appear on edition gui. If not given, the default * name "Data" will be used. */ - void setEditable( const std::string name = "Data" ); + void setEditable( const std::string& name = "Data" ); /** * \brief Remove the delivered data from being editable diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 60f68458559..b279a4aa31d 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -55,7 +55,7 @@ T* SingleDataSourceNode::getData() const { } template -void SingleDataSourceNode::setEditable( const std::string name ) { +void SingleDataSourceNode::setEditable( const std::string& name ) { Node::addEditableParameter( new EditableParameter( name, m_localData ) ); } From f93494b4774a38c967a636a4d5c83ebdc2e5d267 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 11:23:59 +0200 Subject: [PATCH 042/239] [dataflow] add homogeneous BinaryOpNode on CoreDataType to standard factory --- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 6 +++++- src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 56cebde5e18..a61467efcee 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -16,7 +16,11 @@ namespace NodeFactoriesManager { FACTORY->registerNodeCreator( \ NAMESPACE::ArrayTransformer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayReducer##SUFFIX::getTypename() + "_", #NAMESPACE ) + NAMESPACE::ArrayReducer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryOp##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryOp##SUFFIX##Array::getTypename() + "_", #NAMESPACE ) #define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ FACTORY->registerNodeCreator( \ diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp index 4d2e32f9b49..568284d68e2 100644 --- a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -20,7 +20,9 @@ using namespace Ra::Core; #define DECLARE_FUNCTIONALS( SUFFIX, TYPE ) \ using ArrayFilter##SUFFIX = FilterNode>; \ using ArrayTransformer##SUFFIX = TransformNode>; \ - using ArrayReducer##SUFFIX = ReduceNode> + using ArrayReducer##SUFFIX = ReduceNode>; \ + using BinaryOp##SUFFIX = BinaryOpNode; \ + using BinaryOp##SUFFIX##Array = BinaryOpNode> DECLARE_FUNCTIONALS( Float, float ); DECLARE_FUNCTIONALS( Double, double ); From 1a46b66482a9ccd36f20a73038fb94be4f793d8f Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 11:24:36 +0200 Subject: [PATCH 043/239] [unittests][dataflow] add graph load/save tests --- tests/unittest/CMakeLists.txt | 1 + tests/unittest/Dataflow/nodes.cpp | 10 ++- tests/unittest/Dataflow/serialization.cpp | 85 +++++++++++++++++++++++ 3 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 tests/unittest/Dataflow/serialization.cpp diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index 48e545f901c..3b402c9486d 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -32,6 +32,7 @@ set(test_src Core/variableset.cpp Core/vectorarray.cpp Dataflow/nodes.cpp + Dataflow/serialization.cpp Engine/environmentmap.cpp Engine/renderparameters.cpp Engine/signalmanager.cpp diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index f12c281cde2..7ff9750bebc 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -39,9 +39,14 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { g.addNode( op ); REQUIRE( g.addLink( source_a, "to", op, "a" ) ); - REQUIRE( g.addLink( source_b, "to", op, "b" ) ); REQUIRE( g.addLink( op, "r", sink, "from" ) ); - REQUIRE( g.compile() ); + REQUIRE( !g.compile() ); + // this will not execute the graph as it do not compiles + g.execute(); + REQUIRE( !g.m_ready ); + + // add missing link + REQUIRE( g.addLink( source_b, "to", op, "b" ) ); DataType x { 1_ra }; a->setData( &x ); @@ -51,6 +56,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { b->setData( &y ); REQUIRE( b->getData() == y ); + // As graph was modified since last compilation, this will recompile the graph g.execute(); auto& z = r->getData(); diff --git a/tests/unittest/Dataflow/serialization.cpp b/tests/unittest/Dataflow/serialization.cpp new file mode 100644 index 00000000000..72035b5351a --- /dev/null +++ b/tests/unittest/Dataflow/serialization.cpp @@ -0,0 +1,85 @@ +#include + +#include +#include + +#include + +#include +#include + +TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { + SECTION( "Serialization of a graph" ) { + + using namespace Ra::Dataflow::Core; + using DataType = Scalar; + DataflowGraph g { "original graph" }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g.addNode( source_a ); + auto a = g.getDataSetter( "a_to" ); + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g.addNode( source_b ); + auto b = g.getDataSetter( "b_to" ); + auto sink = new Sinks::SinkNode( "r" ); + g.addNode( sink ); + auto r = g.getDataGetter( "r_from" ); + using TestNode = Functionals::BinaryOpNode; + TestNode::BinaryOperator add = []( TestNode::Arg1_type a, + TestNode::Arg2_type b ) -> TestNode::Res_type { + return a + b; + }; + auto op = new TestNode( "addition" ); + op->setOperator( add ); + g.addNode( op ); + g.addLink( source_a, "to", op, "a" ); + g.addLink( op, "r", sink, "from" ); + g.addLink( source_b, "to", op, "b" ); + + // execution of the original graph + DataType x { 1_ra }; + a->setData( &x ); + DataType y { 2_ra }; + b->setData( &y ); + g.execute(); + auto z = r->getData(); + REQUIRE( z == x + y ); + + // Save the graph + std::string tmpdir { "tmpDir4Tests" }; + + std::cout << "Graph tmp dir : " << tmpdir << "\n"; + std::filesystem::create_directories( tmpdir ); + g.saveToJson( tmpdir + "/GraphSerializationTest.json" ); + g.destroy(); + // this does nothing as g was destroyed + g.execute(); + + // Create a new graph and load from the saved graph + DataflowGraph g1 { "loaded graph" }; + g1.loadFromJson( tmpdir + "/GraphSerializationTest.json" ); + + // Setting the unserializable data on nodes (functions) + auto addition = g1.getNode( "addition" ); + REQUIRE( addition != nullptr ); + REQUIRE( addition->getTypeName() == Functionals::BinaryOpScalar::getTypename() ); + auto typedAddition = dynamic_cast( addition ); + REQUIRE( typedAddition != nullptr ); + typedAddition->setOperator( add ); + // execute loaded graph + g1.execute(); + auto r_loaded = g1.getDataGetter( "r_from" ); + auto& z_loaded = r_loaded->getData(); + // Data loaded for source nodes are the one saved by the original graph + REQUIRE( z_loaded == z ); + auto a_loaded = g1.getDataSetter( "a_to" ); + auto b_loaded = g1.getDataSetter( "b_to" ); + DataType xp { 2_ra }; + a_loaded->setData( &xp ); + DataType yp { 3_ra }; + b_loaded->setData( &yp ); + g1.execute(); + REQUIRE( z_loaded == 5 ); + std::filesystem::remove_all( tmpdir ); + } +} From 0732c5d556ec8a7768cf6b01cc51e462362476c1 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 12:37:03 +0200 Subject: [PATCH 044/239] [dataflow][rendering] improve codacy report --- src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp | 6 +++--- src/Dataflow/Rendering/Renderer/RenderingGraph.cpp | 9 +++++---- src/Dataflow/Rendering/Renderer/RenderingGraph.hpp | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp index 853a0440ec5..fe2a637137f 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp @@ -83,11 +83,11 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer [[nodiscard]] virtual std::string getRendererName() const { return "Dataflow Renderer"; } - void loadGraph( const std::string filename ); - void saveGraph( const std::string filename ); + void loadGraph( const std::string& filename ); + void saveGraph( const std::string& filename ); void resetGraph(); /// Call this to set a graph to load before OpenGL is OK - void defferedLoadGraph( const std::string filename ); + void defferedLoadGraph( const std::string& filename ); /// The controlled graph. /// The controller own the graph and manage loading/saving of the renderer diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp index 8d93bfc4867..5f21dd94eb4 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp @@ -72,16 +72,16 @@ bool RenderingGraph::postCompilationOperation() { } } } - /* +#if 0 std::cout << "RenderingGraph::postCompilationOperation : got " << m_renderingNodes.size() << " compiled rendering nodes with " << m_rtIndexedNodes.size() << " render-passes, " << m_dataProviders.size() << " scene nodes. \n"; - */ +#endif #endif return true; } -* / +*/ #if 0 void RenderingGraph::observeSinks( const std::vector& graphOutput ) { m_outputTextures = graphOutput; @@ -99,7 +99,8 @@ void RenderingGraph::observeSinks( const std::vector& graphOutput */ } #endif - const std::vector& RenderingGraph::getImagesOutput() const { + +const std::vector& RenderingGraph::getImagesOutput() const { return m_outputTextures; } diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index 5599b2358c1..9a0afe064fe 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -20,7 +20,7 @@ using namespace Ra::Dataflow::Core; class RA_DATAFLOW_API RenderingGraph : public DataflowGraph { public: - RenderingGraph( const std::string& name ); + explicit RenderingGraph( const std::string& name ); ~RenderingGraph() override = default; void init() override; From 5b1e53ed6188633f2081b5c8e83badf83117fdab Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 12:37:20 +0200 Subject: [PATCH 045/239] [dataflow][examples] improve codacy report --- .../GraphEditor/MainWindow.cpp | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/examples/DataflowExamples/GraphEditor/MainWindow.cpp index 2855c876f7f..d037cae584c 100644 --- a/examples/DataflowExamples/GraphEditor/MainWindow.cpp +++ b/examples/DataflowExamples/GraphEditor/MainWindow.cpp @@ -67,8 +67,7 @@ void MainWindow::about() { QMessageBox::about( this, tr( "About Application" ), tr( "The Application example demonstrates how to " - "write modern GUI applications using Qt, with a menu bar, " - "toolbars, and a status bar." ) ); + "edit a Radium dataflow graph." ) ); } void MainWindow::documentWasModified() { @@ -77,10 +76,10 @@ void MainWindow::documentWasModified() { void MainWindow::createActions() { - QMenu* fileMenu = menuBar()->addMenu( tr( "&File" ) ); - QToolBar* fileToolBar = addToolBar( tr( "File" ) ); - const QIcon newIcon = QIcon::fromTheme( "document-new", QIcon( ":/images/new.png" ) ); - QAction* newAct = new QAction( newIcon, tr( "&New" ), this ); + auto fileMenu = menuBar()->addMenu( tr( "&File" ) ); + auto fileToolBar = addToolBar( tr( "File" ) ); + const QIcon newIcon = QIcon::fromTheme( "document-new", QIcon( ":/images/new.png" ) ); + auto newAct = new QAction( newIcon, tr( "&New" ), this ); newAct->setShortcuts( QKeySequence::New ); newAct->setStatusTip( tr( "Create a new file" ) ); connect( newAct, &QAction::triggered, this, &MainWindow::newFile ); @@ -88,7 +87,7 @@ void MainWindow::createActions() { fileToolBar->addAction( newAct ); const QIcon openIcon = QIcon::fromTheme( "document-open", QIcon( ":/images/open.png" ) ); - QAction* openAct = new QAction( openIcon, tr( "&Open..." ), this ); + auto openAct = new QAction( openIcon, tr( "&Open..." ), this ); openAct->setShortcuts( QKeySequence::Open ); openAct->setStatusTip( tr( "Open an existing file" ) ); connect( openAct, &QAction::triggered, this, &MainWindow::open ); @@ -96,15 +95,15 @@ void MainWindow::createActions() { fileToolBar->addAction( openAct ); const QIcon saveIcon = QIcon::fromTheme( "document-save", QIcon( ":/images/save.png" ) ); - QAction* saveAct = new QAction( saveIcon, tr( "&Save" ), this ); + auto saveAct = new QAction( saveIcon, tr( "&Save" ), this ); saveAct->setShortcuts( QKeySequence::Save ); saveAct->setStatusTip( tr( "Save the document to disk" ) ); connect( saveAct, &QAction::triggered, this, &MainWindow::save ); fileMenu->addAction( saveAct ); fileToolBar->addAction( saveAct ); - const QIcon saveAsIcon = QIcon::fromTheme( "document-save-as" ); - QAction* saveAsAct = + const auto saveAsIcon = QIcon::fromTheme( "document-save-as" ); + auto saveAsAct = fileMenu->addAction( saveAsIcon, tr( "Save &As..." ), this, &MainWindow::saveAs ); saveAsAct->setShortcuts( QKeySequence::SaveAs ); saveAsAct->setStatusTip( tr( "Save the document under a new name" ) ); @@ -112,18 +111,21 @@ void MainWindow::createActions() { fileMenu->addSeparator(); const QIcon exitIcon = QIcon::fromTheme( "application-exit" ); - QAction* exitAct = fileMenu->addAction( exitIcon, tr( "E&xit" ), this, &QWidget::close ); + auto exitAct = fileMenu->addAction( exitIcon, tr( "E&xit" ), this, &QWidget::close ); exitAct->setShortcuts( QKeySequence::Quit ); exitAct->setStatusTip( tr( "Exit the application" ) ); - QMenu* editMenu = menuBar()->addMenu( tr( "&Edit" ) ); - QToolBar* editToolBar = addToolBar( tr( "Edit" ) ); +#if 0 + // Activite this section when editMenu might be filled + auto editMenu = menuBar()->addMenu( tr( "&Edit" ) ); + auto editToolBar = addToolBar( tr( "Edit" ) ); +#endif - QMenu* helpMenu = menuBar()->addMenu( tr( "&Help" ) ); - QAction* aboutAct = helpMenu->addAction( tr( "&About" ), this, &MainWindow::about ); + auto helpMenu = menuBar()->addMenu( tr( "&Help" ) ); + auto aboutAct = helpMenu->addAction( tr( "&About" ), this, &MainWindow::about ); aboutAct->setStatusTip( tr( "Show the application's About box" ) ); - QAction* aboutQtAct = helpMenu->addAction( tr( "About &Qt" ), qApp, &QApplication::aboutQt ); + auto aboutQtAct = helpMenu->addAction( tr( "About &Qt" ), qApp, &QApplication::aboutQt ); aboutQtAct->setStatusTip( tr( "Show the Qt library's About box" ) ); } From b045281f74fad012013e37ef54242cfa4392b25a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 12:38:53 +0200 Subject: [PATCH 046/239] [unittests][dataflow] improve codacy report --- tests/unittest/Dataflow/serialization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unittest/Dataflow/serialization.cpp b/tests/unittest/Dataflow/serialization.cpp index 72035b5351a..43802439288 100644 --- a/tests/unittest/Dataflow/serialization.cpp +++ b/tests/unittest/Dataflow/serialization.cpp @@ -65,7 +65,7 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { REQUIRE( addition->getTypeName() == Functionals::BinaryOpScalar::getTypename() ); auto typedAddition = dynamic_cast( addition ); REQUIRE( typedAddition != nullptr ); - typedAddition->setOperator( add ); + if ( typedAddition != nullptr ) { typedAddition->setOperator( add ); } // execute loaded graph g1.execute(); auto r_loaded = g1.getDataGetter( "r_from" ); From 169011e324fee63f28549e1b198d216c104512ad Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 13:28:21 +0200 Subject: [PATCH 047/239] [unittests][dataflow] improve codacy report --- tests/unittest/Dataflow/nodes.cpp | 287 ++++++++++++------------------ 1 file changed, 113 insertions(+), 174 deletions(-) diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 7ff9750bebc..87bf9154414 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -8,45 +8,55 @@ #include #include +using namespace Ra::Dataflow::Core; +template +std::tuple, std::shared_ptr, PortBase*> +createGraph( + const std::string& name, + typename Functionals::BinaryOpNode::BinaryOperator f ) { + using TestNode = Functionals::BinaryOpNode; + auto g = new DataflowGraph { name }; + + auto source_a = new Sources::SingleDataSourceNode( "a" ); + g->addNode( source_a ); + auto a = g->getDataSetter( "a_to" ); + REQUIRE( a->getNode() == g ); + + auto source_b = new Sources::SingleDataSourceNode( "b" ); + g->addNode( source_b ); + auto b = g->getDataSetter( "b_to" ); + REQUIRE( b->getNode() == g ); + + auto sink = new Sinks::SinkNode( "r" ); + g->addNode( sink ); + auto r = g->getDataGetter( "r_from" ); + REQUIRE( r->getNode() == g ); + + auto op = new TestNode( "operator", f ); + // op->setOperator( f ); + g->addNode( op ); + + REQUIRE( g->addLink( source_a, "to", op, "a" ) ); + REQUIRE( g->addLink( op, "r", sink, "from" ) ); + REQUIRE( !g->compile() ); + // this will not execute the graph as it do not compiles + g->execute(); + REQUIRE( !g->m_ready ); + // add missing link + REQUIRE( g->addLink( source_b, "to", op, "b" ) ); + + return { g, a, b, r }; +} + TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { SECTION( "Operations on Scalar" ) { - using namespace Ra::Dataflow::Core; using DataType = Scalar; - DataflowGraph g { "testBinOp" }; - - auto source_a = new Sources::SingleDataSourceNode( "a" ); - g.addNode( source_a ); - auto a = g.getDataSetter( "a_to" ); - REQUIRE( a->getNode() == &g ); - - auto source_b = new Sources::SingleDataSourceNode( "b" ); - g.addNode( source_b ); - auto b = g.getDataSetter( "b_to" ); - REQUIRE( b->getNode() == &g ); - - auto sink = new Sinks::SinkNode( "r" ); - g.addNode( sink ); - auto r = g.getDataGetter( "r_from" ); - REQUIRE( r->getNode() == &g ); - - using TestNode = Functionals::BinaryOpNode; - TestNode::BinaryOperator add = []( TestNode::Arg1_type a, - TestNode::Arg2_type b ) -> TestNode::Res_type { - return a + b; - }; - auto op = new TestNode( "additionner" ); - op->setOperator( add ); - g.addNode( op ); - - REQUIRE( g.addLink( source_a, "to", op, "a" ) ); - REQUIRE( g.addLink( op, "r", sink, "from" ) ); - REQUIRE( !g.compile() ); - // this will not execute the graph as it do not compiles - g.execute(); - REQUIRE( !g.m_ready ); - - // add missing link - REQUIRE( g.addLink( source_b, "to", op, "b" ) ); + using TestNode = Functionals::BinaryOpNode; + typename TestNode::BinaryOperator add = []( typename TestNode::Arg1_type a, + typename TestNode::Arg2_type b ) -> + typename TestNode::Res_type { return a + b; }; + + auto [g, a, b, r] = createGraph( "test scalar binary op", add ); DataType x { 1_ra }; a->setData( &x ); @@ -57,49 +67,25 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { REQUIRE( b->getData() == y ); // As graph was modified since last compilation, this will recompile the graph - g.execute(); + g->execute(); auto& z = r->getData(); REQUIRE( z == x + y ); std::cout << x << " + " << y << " == " << z << "\n"; - g.destroy(); + g->destroy(); + delete g; } SECTION( "Operations on Vectors" ) { - using namespace Ra::Dataflow::Core; using DataType = Ra::Core::Vector3; - DataflowGraph g { "testBinOp" }; - - auto source_a = new Sources::SingleDataSourceNode( "a" ); - g.addNode( source_a ); - auto a = g.getDataSetter( "a_to" ); - REQUIRE( a->getNode() == &g ); - - auto source_b = new Sources::SingleDataSourceNode( "b" ); - g.addNode( source_b ); - auto b = g.getDataSetter( "b_to" ); - REQUIRE( b->getNode() == &g ); - - auto sink = new Sinks::SinkNode( "r" ); - g.addNode( sink ); - auto r = g.getDataGetter( "r_from" ); - REQUIRE( r->getNode() == &g ); - - using TestNode = Functionals::BinaryOpNode; - TestNode::BinaryOperator add = []( TestNode::Arg1_type a, - TestNode::Arg2_type b ) -> TestNode::Res_type { - return a + b; - }; - auto op = new TestNode( "additionner" ); - op->setOperator( add ); - g.addNode( op ); - - REQUIRE( g.addLink( source_a, "to", op, "a" ) ); - REQUIRE( g.addLink( source_b, "to", op, "b" ) ); - REQUIRE( g.addLink( op, "r", sink, "from" ) ); - REQUIRE( g.compile() ); + using TestNode = Functionals::BinaryOpNode; + typename TestNode::BinaryOperator add = []( typename TestNode::Arg1_type a, + typename TestNode::Arg2_type b ) -> + typename TestNode::Res_type { return a + b; }; + + auto [g, a, b, r] = createGraph( "test Vector3 binary op", add ); DataType x { 1_ra, 2_ra, 3_ra }; a->setData( &x ); @@ -109,7 +95,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { b->setData( &y ); REQUIRE( b->getData() == y ); - g.execute(); + g->execute(); auto& z = r->getData(); REQUIRE( z == x + y ); @@ -117,42 +103,18 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { std::cout << "[" << x.transpose() << "] + [" << y.transpose() << "] == [" << z.transpose() << "]\n"; - g.destroy(); + g->destroy(); + delete g; } SECTION( "Operations on VectorArrays" ) { - using namespace Ra::Dataflow::Core; using DataType = Ra::Core::VectorArray; - DataflowGraph g { "testBinOp" }; - - auto source_a = new Sources::SingleDataSourceNode( "a" ); - g.addNode( source_a ); - auto a = g.getDataSetter( "a_to" ); - REQUIRE( a->getNode() == &g ); - - auto source_b = new Sources::SingleDataSourceNode( "b" ); - g.addNode( source_b ); - auto b = g.getDataSetter( "b_to" ); - REQUIRE( b->getNode() == &g ); - - auto sink = new Sinks::SinkNode( "r" ); - g.addNode( sink ); - auto r = g.getDataGetter( "r_from" ); - REQUIRE( r->getNode() == &g ); - - using TestNode = Functionals::BinaryOpNode; - TestNode::BinaryOperator add = []( TestNode::Arg1_type a, - TestNode::Arg2_type b ) -> TestNode::Res_type { - return a + b; - }; - auto op = new TestNode( "additionner" ); - op->setOperator( add ); - g.addNode( op ); - - REQUIRE( g.addLink( source_a, "to", op, "a" ) ); - REQUIRE( g.addLink( source_b, "to", op, "b" ) ); - REQUIRE( g.addLink( op, "r", sink, "from" ) ); - REQUIRE( g.compile() ); + using TestNode = Functionals::BinaryOpNode; + typename TestNode::BinaryOperator add = []( typename TestNode::Arg1_type a, + typename TestNode::Arg2_type b ) -> + typename TestNode::Res_type { return a + b; }; + + auto [g, a, b, r] = createGraph( "test Vector3 binary op", add ); DataType x { { 1_ra, 2_ra }, { 3_ra, 4_ra } }; a->setData( &x ); @@ -162,7 +124,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { b->setData( &y ); REQUIRE( b->getData() == y ); - g.execute(); + g->execute(); auto& z = r->getData(); for ( size_t i = 0; i < z.size(); i++ ) { @@ -182,49 +144,24 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { std::cout << "[" << t.transpose() << "] "; } std::cout << "}\n"; - g.destroy(); + + g->destroy(); + delete g; } SECTION( "Operations between VectorArray and Scalar" ) { - using namespace Ra::Dataflow::Core; using DataType_a = Ra::Core::VectorArray; using DataType_b = Scalar; - // How to do this ? Eigen generates an error due to align allocation // using DataType_r = Ra::Core::VectorArray< decltype( std::declval() * // std::declval() ) >; using DataType_r = Ra::Core::VectorArray; - - DataflowGraph g { "testBinOp" }; - - auto source_a = new Sources::SingleDataSourceNode( "a" ); - g.addNode( source_a ); - auto a = g.getDataSetter( "a_to" ); - REQUIRE( a->getNode() == &g ); - - auto source_b = new Sources::SingleDataSourceNode( "b" ); - g.addNode( source_b ); - auto b = g.getDataSetter( "b_to" ); - REQUIRE( b->getNode() == &g ); - - auto sink = new Sinks::SinkNode( "r" ); - g.addNode( sink ); - auto r = g.getDataGetter( "r_from" ); - REQUIRE( r->getNode() == &g ); - - using TestNode = Functionals::BinaryOpNode; - TestNode::BinaryOperator add = []( TestNode::Arg1_type a, - TestNode::Arg2_type b ) -> TestNode::Res_type { - return a * b; - }; - auto op = new TestNode( "additionner" ); - op->setOperator( add ); - g.addNode( op ); - - REQUIRE( g.addLink( source_a, "to", op, "a" ) ); - REQUIRE( g.addLink( source_b, "to", op, "b" ) ); - REQUIRE( g.addLink( op, "r", sink, "from" ) ); - REQUIRE( g.compile() ); + using TestNode = Functionals::BinaryOpNode; + typename TestNode::BinaryOperator op = []( typename TestNode::Arg1_type a, + typename TestNode::Arg2_type b ) -> + typename TestNode::Res_type { return a * b; }; + auto [g, a, b, r] = createGraph( + "test Vector2 x Scalar binary op", op ); DataType_a x { { 1_ra, 2_ra }, { 3_ra, 4_ra } }; a->setData( &x ); @@ -234,7 +171,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { b->setData( &y ); REQUIRE( b->getData() == y ); - g.execute(); + g->execute(); auto& z = r->getData(); for ( size_t i = 0; i < z.size(); i++ ) { @@ -250,45 +187,46 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { std::cout << "[" << t.transpose() << "] "; } std::cout << "}\n"; - g.destroy(); + + // change operator + auto opNode = dynamic_cast( g->getNode( "operator" ) ); + REQUIRE( opNode != nullptr ); + if ( opNode ) { + typename TestNode::BinaryOperator f = []( typename TestNode::Arg1_type a, + typename TestNode::Arg2_type b ) -> + typename TestNode::Res_type { return a / b; }; + opNode->setOperator( f ); + } + g->execute(); + + for ( size_t i = 0; i < z.size(); i++ ) { + REQUIRE( z[i] == x[i] / y ); + } + + std::cout << "{ "; + for ( const auto& t : x ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "} / " << y << " = { "; + for ( const auto& t : z ) { + std::cout << "[" << t.transpose() << "] "; + } + std::cout << "}\n"; + g->destroy(); + delete g; } - SECTION( "Operations between VectorArray and Scalar" ) { + SECTION( "Operations between Scalar and VectorArray" ) { using namespace Ra::Dataflow::Core; - using DataType_b = Ra::Core::VectorArray; using DataType_a = Scalar; + using DataType_b = Ra::Core::VectorArray; using DataType_r = Ra::Core::VectorArray; - - DataflowGraph g { "testBinOp" }; - - auto source_a = new Sources::SingleDataSourceNode( "a" ); - g.addNode( source_a ); - auto a = g.getDataSetter( "a_to" ); - REQUIRE( a->getNode() == &g ); - - auto source_b = new Sources::SingleDataSourceNode( "b" ); - g.addNode( source_b ); - auto b = g.getDataSetter( "b_to" ); - REQUIRE( b->getNode() == &g ); - - auto sink = new Sinks::SinkNode( "r" ); - g.addNode( sink ); - auto r = g.getDataGetter( "r_from" ); - REQUIRE( r->getNode() == &g ); - - using TestNode = Functionals::BinaryOpNode; - TestNode::BinaryOperator add = []( TestNode::Arg1_type a, - TestNode::Arg2_type b ) -> TestNode::Res_type { - return a * b; - }; - auto op = new TestNode( "additionner" ); - op->setOperator( add ); - g.addNode( op ); - - REQUIRE( g.addLink( source_a, "to", op, "a" ) ); - REQUIRE( g.addLink( source_b, "to", op, "b" ) ); - REQUIRE( g.addLink( op, "r", sink, "from" ) ); - REQUIRE( g.compile() ); + using TestNode = Functionals::BinaryOpNode; + typename TestNode::BinaryOperator op = []( typename TestNode::Arg1_type a, + typename TestNode::Arg2_type b ) -> + typename TestNode::Res_type { return a * b; }; + auto [g, a, b, r] = createGraph( + "test Vector2 x Scalar binary op", op ); DataType_a x { 4_ra }; a->setData( &x ); @@ -298,7 +236,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { b->setData( &y ); REQUIRE( b->getData() == y ); - g.execute(); + g->execute(); auto& z = r->getData(); for ( size_t i = 0; i < z.size(); i++ ) { @@ -314,6 +252,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { std::cout << "[" << t.transpose() << "] "; } std::cout << "}\n"; - g.destroy(); + g->destroy(); + delete g; } } From dae930f7919f3fa952f25446bf6679e6805bc56d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 15:30:09 +0200 Subject: [PATCH 048/239] [dataflow] fix wrong log message --- src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl index 49de249e37c..8344d1ba744 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl @@ -47,7 +47,7 @@ void ReduceNode::execute() { m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); #ifdef GRAPH_CALL_TRACE std::cout << "\e[36m\e[1mMReduceNode \e[0m \"" << m_instanceName << "\": execute, from " - << input->getData().size() << " " << typeid( T ).name() << "." << std::endl; + << input->getData().size() << " " << getTypename() << "." << std::endl; #endif } } From 3ca1ca30c4f05a8b06b159009bdd58b0b660c3c3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 15:30:56 +0200 Subject: [PATCH 049/239] [tests] improve coverage of builtins node --- tests/unittest/Dataflow/nodes.cpp | 193 ++++++++++++++++++++++ tests/unittest/Dataflow/serialization.cpp | 22 ++- 2 files changed, 213 insertions(+), 2 deletions(-) diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 87bf9154414..4b91140da17 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -7,6 +7,7 @@ #include #include +#include using namespace Ra::Dataflow::Core; template @@ -255,4 +256,196 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { g->destroy(); delete g; } + + SECTION( "Transform/reduce/filter/test" ) { + auto g = new DataflowGraph( "Complex graph" ); + using VectorType = Ra::Core::VectorArray; + + // Source of a vector of Scalar : random vector + auto nodeS = new Sources::ScalarArraySource( "s" ); + + // Source of an operator on scalars : f(x) = 2*x + using DoubleFunction = Sources::FunctionSourceNode::function_type; + DoubleFunction doubleMe = []( const Scalar& x ) -> Scalar { return 2_ra * x; }; + auto nodeD = new Sources::FunctionSourceNode( "d" ); + nodeD->setData( &doubleMe ); + + // Source of a Scalar : mean neutral element 0_ra + auto nodeN = new Sources::ScalarSource( "n" ); + Scalar zero { 0_ra }; + nodeN->setData( &zero ); + + // Source of a reduction operator : compute the mean using Welford online algo + using ReduceOperator = Sources::FunctionSourceNode; + struct MeanOperator { + size_t n { 0 }; + Scalar operator()( const Scalar& m, const Scalar& x ) { + return m + ( ( x - m ) / ( ++n ) ); + } + }; + auto nodeM = new ReduceOperator( "m" ); + ReduceOperator::function_type m = MeanOperator(); + + // Reduce node : will compute the mean + using MeanCalculator = Functionals::ReduceNode; + auto meanCalculator = new MeanCalculator( "mean" ); + + // Sink for the mean + auto nodeR = new Sinks::ScalarSink( "r" ); + + // Transform operator, will double the vectors' values + auto nodeT = new Functionals::ArrayTransformerScalar( "twice" ); + + // Will compute the mean on the doubled vector + auto doubleMeanCalculator = new MeanCalculator( "double mean" ); + + // Sink for the double mean + auto nodeRD = new Sinks::ScalarSink( "rd" ); + + // Source for a comparison functor , eg f(x, y) -> 2*x == y + auto nodePred = new Sources::ScalarBinaryPredicateSource( "predicate" ); + Sources::ScalarBinaryPredicateSource::function_type predicate = + []( const Scalar& a, const Scalar& b ) -> bool { return 2_ra * a == b; }; + nodePred->setData( &predicate ); + + // Boolean sink for the validation result + auto sinkB = new Sinks::BooleanSink( "test" ); + + // Node for coparing the results of the computation graph + auto validator = new Functionals::BinaryOpNode( "validator" ); + + g->addNode( nodeS ); + g->addNode( nodeD ); + g->addNode( nodeN ); + g->addNode( nodeM ); + g->addNode( nodeR ); + g->addNode( meanCalculator ); + g->addNode( doubleMeanCalculator ); + g->addNode( nodeT ); + g->addNode( nodeRD ); + + if ( !g->addLink( nodeS, "to", meanCalculator, "in" ) ) { + std::cout << "Unable to link " << nodeS->getInstanceName() << ":to and " + << meanCalculator->getInstanceName() << ":in !!!\n"; + } + if ( !g->addLink( nodeM, "f", meanCalculator, "f" ) ) { + std::cout << "Unable to link " << nodeM->getInstanceName() << ":f and " + << meanCalculator->getInstanceName() << ":f !!!\n"; + } + if ( !g->addLink( nodeN, "to", meanCalculator, "init" ) ) { + std::cout << "Unable to link " << nodeN->getInstanceName() << ":to and " + << meanCalculator->getInstanceName() << ":init !!!\n"; + } + if ( !g->addLink( meanCalculator, "out", nodeR, "from" ) ) { + std::cout << "Unable to link " << meanCalculator->getInstanceName() << ":out and " + << nodeR->getInstanceName() << ":from !!!\n"; + } + + if ( !g->addLink( nodeS, "to", nodeT, "in" ) ) { + std::cout << "Unable to link " << nodeS->getInstanceName() << ":to and " + << nodeT->getInstanceName() << ":in !!!\n"; + } + if ( !g->addLink( nodeD, "f", nodeT, "f" ) ) { + std::cout << "Unable to link " << nodeD->getInstanceName() << ":f and " + << nodeT->getInstanceName() << ":f !!!\n"; + } + if ( !g->addLink( nodeT, "out", doubleMeanCalculator, "in" ) ) { + std::cout << "Unable to link " << nodeT->getInstanceName() << ":out and " + << doubleMeanCalculator->getInstanceName() << ":in !!!\n"; + } + if ( !g->addLink( doubleMeanCalculator, "out", nodeRD, "from" ) ) { + std::cout << "Unable to link " << doubleMeanCalculator->getInstanceName() << ":out and " + << nodeRD->getInstanceName() << ":from !!!\n"; + } + if ( !g->addLink( nodeM, "f", doubleMeanCalculator, "f" ) ) { + std::cout << "Unable to link " << nodeM->getInstanceName() << ":f and " + << doubleMeanCalculator->getInstanceName() << ":f !!!\n"; + } + + g->addNode( nodePred ); + g->addNode( sinkB ); + g->addNode( validator ); + if ( !g->addLink( meanCalculator, "out", validator, "a" ) ) { + std::cout << "Unable to link " << meanCalculator->getInstanceName() << ":out and " + << validator->getInstanceName() << ":a !!!\n"; + } + if ( !g->addLink( doubleMeanCalculator, "out", validator, "b" ) ) { + std::cout << "Unable to link " << doubleMeanCalculator->getInstanceName() << ":out and " + << validator->getInstanceName() << ":b !!!\n"; + } + if ( !g->addLink( nodePred, "f", validator, "f" ) ) { + std::cout << "Unable to link " << nodePred->getInstanceName() << ":f and " + << validator->getInstanceName() << ":b !!!\n"; + } + if ( !g->addLink( validator, "r", sinkB, "from" ) ) { + std::cout << "Unable to link " << validator->getInstanceName() << ":r and " + << sinkB->getInstanceName() << ":from !!!\n"; + } + + auto input = g->getDataSetter( "s_to" ); + auto output = g->getDataGetter( "r_from" ); + auto outputD = g->getDataGetter( "rd_from" ); + auto outputB = g->getDataGetter( "test_from" ); + auto inputR = g->getDataSetter( "m_f" ); + if ( inputR == nullptr ) { std::cout << "Failed to get the graph function input !!\n"; } + + //! [Inspect the graph interface : inputs and outputs port] + auto inputs = g->getAllDataSetters(); + std::cout << "Input ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\"" << portName << "\" accepting type " << portType << "\n"; + } + auto outputs = g->getAllDataGetters(); + std::cout << "Output ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; + } + //! [Inspect the graph interface : inputs and outputs port] + + if ( !g->compile() ) { std::cout << "Compilation error !!"; } + + // Set input/ouput data + VectorType test; + input->setData( &test ); + + test.reserve( 10 ); + std::mt19937 gen( 0 ); + std::uniform_real_distribution<> dis( 0.0, 1.0 ); + // Fill the vector with random numbers between 0 and 1 + for ( size_t n = 0; n < test.capacity(); ++n ) { + test.push_back( dis( gen ) ); + } + +#if 0 + std::cout << "Input values : \n\t"; + for ( auto ord : test ) { + std::cout << ord << ' '; + } + std::cout << '\n'; +#endif + // No need to do this as mean operator source has a copy of a functor + ReduceOperator::function_type m1 = MeanOperator(); + inputR->setData( &m1 ); + + g->execute(); + + auto& result = output->getData(); + auto& resultD = outputD->getData(); + auto& resultB = outputB->getData(); + + std::cout << "Computed mean ( ref ): " << result << "\n"; + std::cout << "Computed mean ( tra ): " << resultD << "\n"; + std::cout << std::boolalpha; + std::cout << "Ratio ( expected 2 ): " << resultD / result << " -- validator --> " + << resultB << "\n"; + + std::cout << '\n'; + + REQUIRE( resultD / result == 2_ra ); + REQUIRE( resultB ); + + g->saveToJson( "Transform-reduce.json" ); + g->destroy(); + delete g; + } } diff --git a/tests/unittest/Dataflow/serialization.cpp b/tests/unittest/Dataflow/serialization.cpp index 43802439288..a6f0ba004e8 100644 --- a/tests/unittest/Dataflow/serialization.cpp +++ b/tests/unittest/Dataflow/serialization.cpp @@ -41,6 +41,7 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { a->setData( &x ); DataType y { 2_ra }; b->setData( &y ); + // Execute initial graph"; g.execute(); auto z = r->getData(); REQUIRE( z == x + y ); @@ -66,12 +67,14 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { auto typedAddition = dynamic_cast( addition ); REQUIRE( typedAddition != nullptr ); if ( typedAddition != nullptr ) { typedAddition->setOperator( add ); } - // execute loaded graph + + // Execute loaded graph + // Data delivered by the source nodes are the one saved by the original graph g1.execute(); auto r_loaded = g1.getDataGetter( "r_from" ); auto& z_loaded = r_loaded->getData(); - // Data loaded for source nodes are the one saved by the original graph REQUIRE( z_loaded == z ); + auto a_loaded = g1.getDataSetter( "a_to" ); auto b_loaded = g1.getDataSetter( "b_to" ); DataType xp { 2_ra }; @@ -80,6 +83,21 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { b_loaded->setData( &yp ); g1.execute(); REQUIRE( z_loaded == 5 ); + + // Reset sources to use the loaded data loaded + g1.releaseDataSetter( "a_to" ); + g1.releaseDataSetter( "b_to" ); + g1.execute(); + REQUIRE( z_loaded == z ); + + // reactivate the dataSetter and change the data delivered by a (copy data into a) + g1.activateDataSetter( "b_to" ); + auto loadedSource_a = + dynamic_cast*>( g1.getNode( "a" ) ); + Scalar newX = 3_ra; + loadedSource_a->setData( &newX ); + g1.execute(); + REQUIRE( z_loaded == 6 ); std::filesystem::remove_all( tmpdir ); } } From 668b3aa6aa3b1245dc3b2dd83ca9a6e9a534a322 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 20:59:24 +0200 Subject: [PATCH 050/239] [dataflow-core] improve nodes --- src/Dataflow/Core/DataflowGraph.cpp | 4 +- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 39 +++++++--- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 10 +-- .../Core/Nodes/Functionals/BinaryOpNode.hpp | 77 +++++++++++++++++-- .../Nodes/Functionals/CoreDataFunctionals.hpp | 8 +- .../Core/Nodes/Functionals/ReduceNode.hpp | 3 + .../Core/Nodes/Sources/CoreDataSources.hpp | 14 +++- .../Core/Nodes/Sources/FunctionSource.hpp | 2 - .../Nodes/Sources/SingleDataSourceNode.hpp | 2 + .../Nodes/Sources/SingleDataSourceNode.inl | 22 +++--- 10 files changed, 137 insertions(+), 44 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 516a791d4d1..d61a9bb4da6 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -658,8 +658,8 @@ bool DataflowGraph::addSetter( PortBase* in ) { addInput( in ); // TODO check if this verification is needed // Danger : Doing this, two different nodes (not the same type) but with the same name and port - // name could not be added to the graph Danger : If not doing this,there will be some collisions - // on the data structure + // name could not be added to the graph. + // Danger : If not doing this,there will be some collisions on the data structure if ( m_dataSetters.find( in->getName() ) == m_dataSetters.end() ) { auto portOut = std::shared_ptr( in->reflect( this, in->getName() ) ); m_dataSetters.emplace( std::make_pair( diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index a61467efcee..ed11d6b4337 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -1,5 +1,9 @@ #include +#include +#include +#include + #include #include @@ -9,6 +13,7 @@ namespace Dataflow { namespace Core { namespace NodeFactoriesManager { + /** TODO : replace this by factory autoregistration at compile time */ #define ADD_FUNCTIONALS_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ FACTORY->registerNodeCreator( \ @@ -20,13 +25,29 @@ namespace NodeFactoriesManager { FACTORY->registerNodeCreator( \ NAMESPACE::BinaryOp##SUFFIX::getTypename() + "_", #NAMESPACE ); \ FACTORY->registerNodeCreator( \ - NAMESPACE::BinaryOp##SUFFIX##Array::getTypename() + "_", #NAMESPACE ) + NAMESPACE::BinaryOp##SUFFIX##Array::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryPredicate##SUFFIX::getTypename() + "_", #NAMESPACE ) -#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##Source::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##ArraySource::getTypename() + "_", #NAMESPACE ) +/* + * not yet supported + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryPredicate##SUFFIX##Array::getTypename() + "_", #NAMESPACE ) +*/ + +#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##Source::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##ArraySource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##UnaryFunctionSource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##BinaryFunctionSource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##UnaryPredicateSource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##BinaryPredicateSource::getTypename() + "_", #NAMESPACE ) #define ADD_SINKS_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ FACTORY->registerNodeCreator( \ @@ -120,12 +141,6 @@ void registerStandardFactories() { ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4i ); ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4ui ); - /* --- Functions --- */ - coreFactory->registerNodeCreator( - Sources::ScalarBinaryPredicateSource::getTypename() + "_", "Functions" ); - coreFactory->registerNodeCreator( - Sources::ScalarUnaryPredicateSource::getTypename() + "_", "Functions" ); - /* --- Graphs --- */ coreFactory->registerNodeCreator( DataflowGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp index d652532b949..54cb823cbc7 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -1,9 +1,11 @@ #pragma once +/* + * Commented out to speedup compilationUser have to include only the desired + */ #include #include #include -#include namespace Ra { namespace Dataflow { @@ -14,9 +16,3 @@ void registerStandardFactories(); } // namespace Core } // namespace Dataflow } // namespace Ra - -// These nodes will not be added in the BuiltIns factory as they are templated. -// Only fully specialized template can be added to the factory. -// #include -// #include -// #include diff --git a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp index e8508c98fcb..3ccb7f1c23f 100644 --- a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp @@ -8,23 +8,50 @@ namespace Dataflow { namespace Core { namespace Functionals { +/** + * \brief namespace containing template utilities for management of BinaryOpNode content + */ namespace internal { + +/** + * \brief Type traits giving access to value_type and const ref type + * \tparam A + * \tparam is_container + */ template struct ArgTypeHelperInternal { using value_type = A; using const_value_ref = const A&; }; +/** + * \brief Partial specialization for container type + * \tparam A + */ template struct ArgTypeHelperInternal { using value_type = typename A::value_type; using const_value_ref = const typename A::value_type&; }; +/** + * \brief CRTP + * \tparam A + */ template struct ArgTypeHelper : public ArgTypeHelperInternal::value> {}; // Find a way to evaluate the copy/move semantic of t_out +/** + * \brief Manage the call to y = f(a, b) according to inputs aand ouput types of the node + * \tparam t_a Type of the source data for argument a of the function + * \tparam t_b Type of the source data for argument b of the function + * \tparam t_out Type of the ouput data sent by the node + * \tparam funcType Profile of the operator f + * \tparam it_a true if t_a is a container + * \tparam it_b true if t_b is a container + * \tparam it_out true if t_out is a container + */ template struct ExecutorHelper { static t_out executeInternal( t_a& a, t_b& b, funcType f ) { @@ -53,6 +83,9 @@ struct ExecutorHelper { } }; +/** + * \brief Call of an operator to transform a container and a scalar into a container. + */ template struct ExecutorHelper { static t_out executeInternal( t_a& a, t_b& b, funcType f ) { @@ -66,6 +99,9 @@ struct ExecutorHelper { } }; +/** + * \brief Call of an operator to transform a scalar and a container into a container. + */ template struct ExecutorHelper { static t_out executeInternal( t_a& a, t_b& b, funcType f ) { @@ -79,13 +115,39 @@ struct ExecutorHelper { } }; +/** + * \brief Call of an operator to transform two scalars into a scalar. + */ template struct ExecutorHelper { static t_out executeInternal( t_a& a, t_b& b, funcType f ) { return f( a, b ); } }; } // namespace internal -/** \brief Apply a binary operation on its input - * \tparam v_t type of the element to apply the operator on + +/** \brief Apply a binary operation on its input. + * \tparam t_a type of the first argument + * \tparam t_b type of the second argument + * \tparam t_out type of the result + * + * This node apply an operator f on its input such that : + * - if t_a, t_b and t_out are collections, r[i] = f(a[i], b[i]) for all elements i in the + * collections. + * - if t_a and t_out are collections, t_b an object, r[i] = f(a[i], b) for all elements i in the + * collections. + * - if t_b and t_out are collections, t_a an object, r[i] = f(a, b[i]) for all elements i in the + * collections. + * - if t_a, t_b and t_out are objects, r = f(a, b). + * All other configurations of t_a, t_b and t_out are illegal. + * + * This node has three inputs : + * - a : port accepting the input data of type t_a. Must be linked. + * - b : port accepting the input data of type t_b. Must be linked. + * - f : port accepting an operator with profile std::function. + * Link to this port is not mandatory, the operator might be set once for the node. + * If this port is linked, the operator will be taken from the port. + * + * This node has one output : + * - out : port giving a t_out such that out = std::transform(a, b, f) */ template class BinaryOpNode : public Node @@ -100,7 +162,7 @@ class BinaryOpNode : public Node using BinaryOperator = std::function; /** - * \brief Construct an additioner + * \brief Construct an null operator * \param instanceName */ explicit BinaryOpNode( const std::string& instanceName ) : @@ -111,7 +173,7 @@ class BinaryOpNode : public Node /** * \brief Construct a BinaryOpNode with the given operator * \param instanceName - * \param filterFunction + * \param op */ BinaryOpNode( const std::string& instanceName, BinaryOperator op ) : BinaryOpNode( instanceName, getTypename(), op ) {} @@ -120,6 +182,7 @@ class BinaryOpNode : public Node Node::init(); m_result = t_out {}; } + void execute() override { auto f = m_operator; auto p_f = static_cast*>( m_inputs[2].get() ); @@ -155,12 +218,12 @@ class BinaryOpNode : public Node void toJsonInternal( nlohmann::json& data ) const override { data["comment"] = std::string { "Binary operator could not be serialized for " } + getTypeName(); - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + LOG( Ra::Core::Utils::logDEBUG ) << "Unable to save data when serializing a " << getTypeName() << "."; } void fromJsonInternal( const nlohmann::json& ) override { - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + LOG( Ra::Core::Utils::logDEBUG ) << "Unable to read data when un-serializing a " << getTypeName() << "."; } @@ -178,7 +241,7 @@ class BinaryOpNode : public Node } }; -// implementation of the methods +// implementation of the methods are inlined // see issue .inl coding style #1011 } // namespace Functionals diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp index 568284d68e2..994cd506996 100644 --- a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -22,7 +22,13 @@ using namespace Ra::Core; using ArrayTransformer##SUFFIX = TransformNode>; \ using ArrayReducer##SUFFIX = ReduceNode>; \ using BinaryOp##SUFFIX = BinaryOpNode; \ - using BinaryOp##SUFFIX##Array = BinaryOpNode> + using BinaryOp##SUFFIX##Array = BinaryOpNode>; \ + using BinaryPredicate##SUFFIX = BinaryOpNode + +/* Not yet supported + using BinaryPredicate##SUFFIX##Array = BinaryOpNode, + Ra::Core::VectorArray, bool> +*/ DECLARE_FUNCTIONALS( Float, float ); DECLARE_FUNCTIONALS( Double, double ); diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp index f5180c4340b..a7db270245e 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp @@ -26,6 +26,9 @@ namespace Functionals { * - out : port giving a v_t such that out = std::reduce(in, f, init) (std::accumulate if less * than C++17) */ +// TODO, allow to specify the type of the reduced information. This will allow, e.g, given a +// collection of X, to compute a tuple containing mean and standard deviation of the collection +// as reduction (using online Welford algo). template class ReduceNode : public Node { diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index fc3543b80be..73564e49971 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -14,10 +15,15 @@ namespace Core { namespace Sources { // TODO unify editable and non editable data sources -// This macro does not end with semicolon. To be added when callin it -#define DECLARE_COREDATA_SOURCES( PREFIX, TYPE ) \ - using PREFIX##Source = SingleDataSourceNode; \ - using PREFIX##ArraySource = SingleDataSourceNode> +// declare synonyms for convenient sources +// This macro does not end with semicolon. To be added when calling it +#define DECLARE_COREDATA_SOURCES( PREFIX, TYPE ) \ + using PREFIX##Source = SingleDataSourceNode; \ + using PREFIX##ArraySource = SingleDataSourceNode>; \ + using PREFIX##UnaryFunctionSource = FunctionSourceNode; \ + using PREFIX##BinaryFunctionSource = FunctionSourceNode; \ + using PREFIX##UnaryPredicateSource = FunctionSourceNode; \ + using PREFIX##BinaryPredicateSource = FunctionSourceNode using namespace Ra::Core; diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index 0662e60beef..9d07f962dd7 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -51,8 +51,6 @@ class FunctionSourceNode : public Node static const std::string& getTypename(); }; -using ScalarBinaryPredicateSource = FunctionSourceNode; -using ScalarUnaryPredicateSource = FunctionSourceNode; } // namespace Sources } // namespace Core } // namespace Dataflow diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index 58fc42b663c..ea4c7215ff1 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -31,6 +31,8 @@ class SingleDataSourceNode : public Node /** \brief Set the data to be delivered by the node. * @param data + * \warning This will copy the given data into the node. + * To prevent copy prefer using the corresponding dataSetter on the owning graph. */ void setData( T* data ); diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index b279a4aa31d..c60c23cfaee 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -19,8 +19,12 @@ SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, template void SingleDataSourceNode::execute() { auto interface = static_cast*>( m_interface[0] ); - if ( interface->isLinked() ) { m_data = &( interface->getData() ); } + if ( interface->isLinked() ) { + // use exernal storage to deliver data + m_data = &( interface->getData() ); + } else { + // use local storage to deliver data m_data = &m_localData; } m_portOut->setData( m_data ); @@ -32,21 +36,21 @@ void SingleDataSourceNode::execute() { template void SingleDataSourceNode::setData( T* data ) { - if ( m_data == &m_localData ) { - // copy data into local storage - m_localData = *data; - } - else { - m_data = data; - } + /// \warning this will copy data into local storage + m_localData = *data; +#if 0 + m_data = &m_localData; m_portOut->setData( m_data ); +#endif } template void SingleDataSourceNode::setData( T& data ) { - // TODO : assert m_data == &m_localData ??? m_localData = data; +#if 0 + m_data = &m_localData; m_portOut->setData( m_data ); +#endif } template From 5a82404988b7f4c67f9d95606e88c1cda34449ce Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 11 Oct 2022 21:00:40 +0200 Subject: [PATCH 051/239] [example] include what is needed --- examples/DataflowExamples/HelloGraph/main.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index 2b924ca3866..11adb6329e5 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -1,8 +1,6 @@ #include +#include #include -#include -#include -#include using namespace Ra::Dataflow::Core; From bf66157af9f3829027381fcc7e8766ad8f6f03f3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 12 Oct 2022 00:37:35 +0200 Subject: [PATCH 052/239] [unittests] add unit tests on sources and sinks --- tests/unittest/Dataflow/nodes.cpp | 1 - tests/unittest/Dataflow/sourcesandsinks.cpp | 129 ++++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/unittest/Dataflow/sourcesandsinks.cpp diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 4b91140da17..89fff76310d 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -7,7 +7,6 @@ #include #include -#include using namespace Ra::Dataflow::Core; template diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp new file mode 100644 index 00000000000..e1aa4c88902 --- /dev/null +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include + +#include + +#include +#include + +using namespace Ra::Dataflow::Core; + +//#define USE_SOURCE_DATA + +template +void testGraph( const std::string& name, T in, T& out ) { + auto g = new DataflowGraph { name }; + auto source = new Sources::SingleDataSourceNode( "in" ); + +#ifdef USE_SOURCE_DATA + std::cout << "Setting " << simplifiedDemangledType() << " data on source node ... "; + source->setData( &in ); +#endif + + auto sink = new Sinks::SinkNode( "out" ); + g->addNode( source ); + g->addNode( sink ); + auto linked = g->addLink( source, "to", sink, "from" ); + if ( !linked ) { std::cerr << "Error linking soure and sink nodes.\n"; } + REQUIRE( linked ); + + auto compiled = g->compile(); + if ( !compiled ) { std::cerr << "Error compiledcompiled graph.\n"; } + REQUIRE( compiled ); + +#ifndef USE_SOURCE_DATA + std::cout << "Setting " << simplifiedDemangledType() << " data on interface port ... "; + auto input = g->getDataSetter( "in_to" ); + REQUIRE( input != nullptr ); + input->setData( &in ); +#endif + + g->execute(); + + auto output = g->getDataGetter( "out_from" ); + REQUIRE( output != nullptr ); + + T r = output->getData(); + out = r; + + nlohmann::json graphData; + g->toJson( graphData ); + g->destroy(); + delete g; +} + +TEST_CASE( "Dataflow/Core/Sources and Sinks", "[Dataflow][Core][Sources and Sinks]" ) { + SECTION( "Operations on base type : Scalar" ) { + using DataType = Scalar; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + + DataType x { 3.141592_ra }; + DataType y { 0_ra }; + testGraph( "Test on Scalar", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : float" ) { + using DataType = float; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { 3.141592 }; + DataType y { 0 }; + testGraph( "Test on float", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : double" ) { + using DataType = double; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { 3.141592 }; + DataType y { 0 }; + testGraph( "Test on float", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : int" ) { + using DataType = int; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { -3 }; + DataType y { 0 }; + testGraph( "Test on int", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : unsigned int" ) { + using DataType = unsigned int; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { 3 }; + DataType y { 0 }; + testGraph( "Test on unsigned int", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : bool" ) { + using DataType = bool; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { true }; + DataType y { false }; + testGraph( "Test on bool", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : Vector3" ) { + using DataType = Ra::Core::Vector3; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { 1_ra, 2_ra, 3_ra }; + DataType y; + testGraph( "Test on bool", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } +} From 51e9b1d68740e4e730631fb5aba612449e73ceee Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 12 Oct 2022 10:08:24 +0200 Subject: [PATCH 053/239] [unittests] update unit tests on sources and sinks --- tests/unittest/Dataflow/sourcesandsinks.cpp | 22 +++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp index e1aa4c88902..ff3bf7fd095 100644 --- a/tests/unittest/Dataflow/sourcesandsinks.cpp +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -29,22 +29,22 @@ void testGraph( const std::string& name, T in, T& out ) { if ( !linked ) { std::cerr << "Error linking soure and sink nodes.\n"; } REQUIRE( linked ); + auto input = g->getDataSetter( "in_to" ); + REQUIRE( input != nullptr ); + auto output = g->getDataGetter( "out_from" ); + REQUIRE( output != nullptr ); + auto compiled = g->compile(); if ( !compiled ) { std::cerr << "Error compiledcompiled graph.\n"; } REQUIRE( compiled ); #ifndef USE_SOURCE_DATA std::cout << "Setting " << simplifiedDemangledType() << " data on interface port ... "; - auto input = g->getDataSetter( "in_to" ); - REQUIRE( input != nullptr ); input->setData( &in ); #endif g->execute(); - auto output = g->getDataGetter( "out_from" ); - REQUIRE( output != nullptr ); - T r = output->getData(); out = r; @@ -121,7 +121,17 @@ TEST_CASE( "Dataflow/Core/Sources and Sinks", "[Dataflow][Core][Sources and Sink std::cout << "Test on " << simplifiedDemangledType() << " ... "; DataType x { 1_ra, 2_ra, 3_ra }; DataType y; - testGraph( "Test on bool", x, y ); + testGraph( "Test on Vector3", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } + SECTION( "Operations on base type : Color" ) { + using DataType = Ra::Core::Utils::Color; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x = Ra::Core::Utils::Color::Skin(); + DataType y; + testGraph( "Test on Color", x, y ); REQUIRE( x == y ); std::cout << " ... DONE!\n"; From d0a515016644596f1bc5aa536537114cf52b009b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 12 Oct 2022 10:08:58 +0200 Subject: [PATCH 054/239] [unittests] add unit tests on custom nodes --- tests/unittest/CMakeLists.txt | 2 + tests/unittest/Dataflow/customnodes.cpp | 291 ++++++++++++++++++++++++ 2 files changed, 293 insertions(+) create mode 100644 tests/unittest/Dataflow/customnodes.cpp diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index 3b402c9486d..42b10224fcd 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -31,8 +31,10 @@ set(test_src Core/topomesh.cpp Core/variableset.cpp Core/vectorarray.cpp + Dataflow/customnodes.cpp Dataflow/nodes.cpp Dataflow/serialization.cpp + Dataflow/sourcesandsinks.cpp Engine/environmentmap.cpp Engine/renderparameters.cpp Engine/signalmanager.cpp diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp new file mode 100644 index 00000000000..7f9c2650e2a --- /dev/null +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -0,0 +1,291 @@ +/** + * Demonstrate how to define custom nodes anduse factory to serialize graphs with custom nodes + */ +#include + +#include +#include + +#include + +#include +#include + +using namespace Ra::Dataflow::Core; + +namespace Customs { +using CustomStringSource = Sources::SingleDataSourceNode; +using CustomStringSink = Sinks::SinkNode; + +/** + * \brief generate a predicate that compare a value wrt a threshold. + * The name of the operator is fetched from input port "name" or the internal data set using + * setFunctionName. Available operator are "=", ">", ">=", "<", "<=", "!=", "true", "false". + * + * The threshold is fetched from input port "threshold" or the internal data set using setThreshold. + * + * The operator is sent on the output port "f". + * + * \tparam T the type of the parameter to evaluate + */ +template +class FilterSelector final : public Node +{ + public: + using function_type = std::function; + + explicit FilterSelector( const std::string& name ) : FilterSelector( name, getTypename() ) {} + + void execute() override { + // get operator parameters + if ( m_portName->isLinked() ) { m_operatorName = m_portName->getData(); } + if ( m_portThreshold->isLinked() ) { m_threshold = m_portThreshold->getData(); } + // compute the result associated to the output port + m_currentFunction = m_functions[m_operatorName]; + } + + /** \brief Set the function name used to select the function to deliver. + * this name will be used if the input port "name" is not linked + * @param name + */ + void setOperatorName( const std::string& name ) { m_operatorName = name; } + /** + * \brief Get the delivered data + * @return The non owning pointer (alias) to the delivered data. + */ + function_type* getOperator() const { return m_functions[m_operatorName]; } + + /** \brief Set the threshold - will copy the value into the node + * @param name + */ + void setThreshold( const T& t ) { m_threshold = t; } + + /** \brief Get the threshold + */ + T getThreshold() const { return m_threshold; } + + protected: + void fromJsonInternal( const nlohmann::json& jsonData ) override { + if ( jsonData.contains( "operator" ) ) { m_operatorName = jsonData["operator"]; } + else { + m_operatorName = "true"; + } + if ( jsonData.contains( "threshold" ) ) { m_threshold = jsonData["threshold"]; } + else { + m_threshold = T {}; + } + } + + void toJsonInternal( nlohmann::json& jsonData ) const override { + jsonData["operator"] = m_operatorName; + jsonData["threshold"] = m_threshold; + } + + public: + static const std::string& getTypename() { + static std::string demangledTypeName = std::string { "FilterSelector<" } + + Ra::Dataflow::Core::simplifiedDemangledType() + + ">"; + return demangledTypeName; + } + + private: + FilterSelector( const std::string& instanceName, const std::string& typeName ) : + Node( instanceName, typeName ) { + // Adding ports to node + addInput( m_portName ); + addInput( m_portThreshold ); + addOutput( m_operatourOut, &m_currentFunction ); + addOutput( m_nameOut, &m_operatorName ); + } + + /// Alias to the output port + PortOut* m_operatourOut { new PortOut( "f", this ) }; + PortOut* m_nameOut { new PortOut( "name", this ) }; + /// Alias for the input ports + PortIn* m_portName { new PortIn( "name", this ) }; + PortIn* m_portThreshold { new PortIn( "threshold", this ) }; + + /// The data provided by the node + std::map m_functions { + { "true", []( const T& ) { return true; } }, + { "false", []( const T& ) { return false; } }, + { "<", [this]( const T& v ) { return v < this->m_threshold; } } }; + + std::string m_operatorName { "true" }; + function_type m_currentFunction = m_functions[m_operatorName]; + T m_threshold {}; +}; + +} // namespace Customs + +// Reusable function to create a graph +template +DataflowGraph* buildgraph( const std::string& name ) { + auto ds = new Sources::SingleDataSourceNode>( "ds" ); + auto rs = new Sinks::SinkNode>( "rs" ); + auto ts = new Sources::SingleDataSourceNode( "ts" ); + auto ss = new Customs::CustomStringSource( "ss" ); + auto nm = new Customs::CustomStringSink( "nm" ); + auto fs = new Customs::FilterSelector( "fs" ); + auto fl = new Functionals::FilterNode>( "fl" ); + + auto g = new DataflowGraph( name ); + g->addNode( ds ); + g->addNode( rs ); + g->addNode( ts ); + g->addNode( ss ); + g->addNode( nm ); + g->addNode( fs ); + g->addNode( fl ); + + bool ok; + ok = g->addLink( ds, "to", fl, "in" ); + REQUIRE( ok ); + ok = g->addLink( fl, "out", rs, "from" ); + REQUIRE( ok ); + ok = g->addLink( ss, "to", fs, "name" ); + REQUIRE( ok ); + ok = g->addLink( ts, "to", fs, "threshold" ); + REQUIRE( ok ); + ok = g->addLink( fs, "f", fl, "f" ); + REQUIRE( ok ); + ok = g->addLink( fs, "name", nm, "from" ); + REQUIRE( ok ); + return g; +} + +// test sections +TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { + SECTION( "Build graph with custom nodes" ) { + // build a graph + auto g = buildgraph( "testCustomNodes" ); + + //! [Inspect the graph interface : inputs and outputs port] + auto inputs = g->getAllDataSetters(); + std::cout << "Input ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\"" << portName << "\" accepting type " << portType << "\n"; + } + auto outputs = g->getAllDataGetters(); + std::cout << "Output ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; + } + //! [Inspect the graph interface : inputs and outputs port] + + // get input and ouput of the graph + auto inputCollection = g->getDataSetter( "ds_to" ); + REQUIRE( inputCollection != nullptr ); + auto inputOpName = g->getDataSetter( "ss_to" ); + REQUIRE( inputOpName != nullptr ); + auto inputThreshold = g->getDataSetter( "ts_to" ); + REQUIRE( inputThreshold != nullptr ); + + auto filteredCollection = g->getDataGetter( "rs_from" ); + auto generatedOperator = g->getDataGetter( "nm_from" ); + + auto r = g->compile(); + REQUIRE( r ); + // parameterise the graph + using CollectionType = Ra::Core::VectorArray; + CollectionType testVector; + testVector.reserve( 10 ); + std::mt19937 gen( 0 ); + std::uniform_real_distribution<> dis( 0.0, 1.0 ); + // Fill the vector with random numbers between 0 and 1 + for ( size_t n = 0; n < testVector.capacity(); ++n ) { + testVector.push_back( dis( gen ) ); + } + inputCollection->setData( &testVector ); + + Scalar threshold { 0.5_ra }; + inputThreshold->setData( &threshold ); + + std::string op { "true" }; + inputOpName->setData( &op ); + + std::cout << "Data sent to graph : \n\toperator " << op << " : \n\t"; + for ( auto ord : testVector ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + + // execute the graph that filter out nothing + // execute + g->execute(); + // Get results as references (ne need to get them again later) + auto& vres = filteredCollection->getData(); + auto& vop = generatedOperator->getData(); + + REQUIRE( vres.size() == testVector.size() ); + std::cout << "Result after applying operator " << vop << " and threshold " << threshold + << ": \n\t"; + for ( auto ord : vres ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + + // change operator to filter out everything + op = "false"; + g->execute(); + REQUIRE( vres.size() == 0 ); + + std::cout << "Result after applying operator " << vop << " and threshold " << threshold + << ": \n\t"; + for ( auto ord : vres ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + + // Change operator to keep element less than threshold + op = "<"; + g->execute(); + + std::cout << "Result after applying operator " << vop << " and threshold " << threshold + << ": \n\t"; + for ( auto ord : vres ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + REQUIRE( *( std::max_element( vres.begin(), vres.end() ) ) < threshold ); + } + SECTION( "Serialization of a custom graph" ) { + // Create and fill the factory for the custom nodes + NodeFactorySet::mapped_type customFactory { + new NodeFactorySet::mapped_type::element_type( "CustomNodesUnitTests" ) }; + + // add node creators to the factory + customFactory->registerNodeCreator( + Customs::CustomStringSource::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator( + Customs::CustomStringSink::getTypename() + "_", "Custom" ); + customFactory->registerNodeCreator>( + Customs::FilterSelector::getTypename() + "_", "Custom" ); + // register the factory into the system to enable loading any graph that use these nodes + NodeFactoriesManager::registerFactory( customFactory ); + + // build a graph + auto g = buildgraph( "testCustomNodes" ); + g->addFactory( customFactory->getName(), customFactory ); + + std::string tmpdir { "customGraphExport/" }; + std::filesystem::create_directories( tmpdir ); + + // save the graph without factory + // save the graph with factory + g->saveToJson( tmpdir + "customGraph.json" ); + + g->destroy(); + delete g; + g = new DataflowGraph( "" ); + + g->loadFromJson( tmpdir + "customGraph.json" ); + + // The graph can't be loaded correctly if there is no factory for the custom nodes + // todo, modify loadFromJson to detect loading error and test below + // REQUIRE( !g->compile() ); + + std::filesystem::remove_all( tmpdir ); + } +} From 173b833b9bb63e8ee7d256817ae276cdac5f9b37 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 12 Oct 2022 23:06:08 +0200 Subject: [PATCH 055/239] [dataflow] fix some inconsistencies in the code while improving coverage. --- src/Dataflow/Core/DataflowGraph.cpp | 35 ++++++--------- src/Dataflow/Core/DataflowGraph.hpp | 18 ++++---- src/Dataflow/Core/DataflowGraph.inl | 2 +- src/Dataflow/Core/Node.cpp | 6 --- src/Dataflow/Core/Node.hpp | 17 -------- src/Dataflow/Core/Node.inl | 17 -------- src/Dataflow/Core/NodeFactory.cpp | 8 ++-- src/Dataflow/Core/NodeFactory.hpp | 43 ++++++++++++++----- src/Dataflow/Core/NodeFactory.inl | 14 ++---- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 5 +++ src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 20 +++++---- .../Nodes/Sources/SingleDataSourceNode.inl | 3 +- .../QtGui/GraphEditor/NodeAdapterModel.hpp | 2 +- tests/unittest/Dataflow/customnodes.cpp | 30 ++++++++++--- 14 files changed, 104 insertions(+), 116 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index d61a9bb4da6..6fec67ec402 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -59,7 +59,6 @@ void DataflowGraph::destroy() { m_nodes.clear(); m_factories.reset(); m_dataSetters.clear(); - m_dataGetters.clear(); Node::destroy(); m_ready = false; } @@ -123,13 +122,12 @@ void DataflowGraph::toJsonInternal( nlohmann::json& data ) const { } bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": loadFromJson: " << jsonFilePath << std::endl; + m_loadStatus = true; std::ifstream file( jsonFilePath ); nlohmann::json j; file >> j; fromJson( j ); - return true; + return m_loadStatus; } void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { @@ -150,6 +148,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { else { std::cerr << "DataflowGraph::loadFromJson : Unable to find a factory with name " << factoryName << std::endl; + m_loadStatus = false; return; } } @@ -169,6 +168,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { if ( newNode ) { nodeById.emplace( id, newNode ); } else { std::cerr << "Unable to create the node " << name << std::endl; + m_loadStatus = false; } } @@ -187,6 +187,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { fromOutput = nodeFrom->getOutputs()[fromIndex]->getName(); } else { + m_loadStatus = false; std::cerr << "Error when reading JSON file \"" << "\": Output index " << fromIndex << " for node \"" << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() @@ -195,6 +196,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } } else { + m_loadStatus = false; std::cerr << "Error when reading JSON file \"" << "\": Could not find a node associated with id " << l["out_id"] << ". Link not added." << std::endl; @@ -208,6 +210,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { toInput = nodeTo->getInputs()[toIndex]->getName(); } else { + m_loadStatus = false; std::cerr << "Error when reading JSON file \"" << "\": Input index " << toIndex << " for node \"" << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() @@ -216,6 +219,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } } else { + m_loadStatus = false; std::cerr << "Error when reading JSON file \"" << "\": Could not find a node associated with id " << l["in_id"] << ". Link not added." << std::endl; @@ -225,6 +229,7 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { addLink( nodeFrom, fromOutput, nodeTo, toInput ); } else { + m_loadStatus = false; std::cerr << "Error when reading JSON file \"" << "\": Could not add a link (missing or wrong information, please refer to " @@ -577,7 +582,7 @@ bool DataflowGraph::compile() { #endif m_recompile = false; m_ready = true; - this->init(); + init(); return m_ready; } @@ -589,19 +594,7 @@ void DataflowGraph::clearNodes() { } m_nodesByLevel.clear(); m_nodesByLevel.shrink_to_fit(); - -#if 0 - --> specific to rendergraph ??? - // Remove only non permanent nodes - // disconnect sink - getDisplayNode()->disconnectInputs(); - // remove node - m_nodes.erase( m_nodes.begin() + 4, m_nodes.end() ); -#endif - m_nodes.erase( std::remove_if( m_nodes.begin(), - m_nodes.end(), - []( const auto& n ) { return n->isDeletable(); } ), - m_nodes.end() ); + m_nodes.erase( m_nodes.begin(), m_nodes.end() ); m_nodes.shrink_to_fit(); } @@ -656,10 +649,6 @@ int DataflowGraph::goThroughGraph( bool DataflowGraph::addSetter( PortBase* in ) { addInput( in ); - // TODO check if this verification is needed - // Danger : Doing this, two different nodes (not the same type) but with the same name and port - // name could not be added to the graph. - // Danger : If not doing this,there will be some collisions on the data structure if ( m_dataSetters.find( in->getName() ) == m_dataSetters.end() ) { auto portOut = std::shared_ptr( in->reflect( this, in->getName() ) ); m_dataSetters.emplace( std::make_pair( @@ -673,6 +662,8 @@ bool DataflowGraph::addSetter( PortBase* in ) { inline bool DataflowGraph::addGetter( PortBase* out ) { if ( out->is_input() ) { return false; } + // This is very similar than addOutput, except the data can't be set, they will be in the init + // of any Sink bool found = false; // TODO check if this verification is needed ? for ( auto& output : m_outputs ) { diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 897ed693497..bf5f624fcdc 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -84,7 +84,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node bool removeLink( Node* node, const std::string& nodeInputName ); /// Gets the nodes - const std::vector>* getNodes() const; + const std::vector>* getNodes() const; /// Gets a specific node according to its instance name as a parameter. /// @param instanceNameNode The instance name of the node. @@ -157,7 +157,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// The node factory to use for loading std::shared_ptr m_factories; /// The unordered list of nodes. - std::vector> m_nodes; + std::vector> m_nodes; // Internal node levels representation /// The list of nodes ordered by levels. /// Two nodes at the same level have no dependency between them. @@ -181,18 +181,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// @param name The name of the node to find. int findNode( const Node* node ); - public: - static const std::string& getTypename(); - - private: /// Data setters management : used to pass parameter to the graph when the graph is not embeded /// into another graph (inputs are here for this case). - using DataSetter = std::pair; - std::map m_dataSetters; - std::map m_dataGetters; - bool addSetter( PortBase* in ); /// Adds an out port for a GRAPH. This port is also an interface port whose reference is stored @@ -200,6 +192,12 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// the same name already associated with the graph. /// \param out The port to add. bool addGetter( PortBase* out ); + + /// Loding status. Set tu true when starting loading, set to false if some nodes can't be laoded + bool m_loadStatus { true }; + + public: + static const std::string& getTypename(); }; } // namespace Core diff --git a/src/Dataflow/Core/DataflowGraph.inl b/src/Dataflow/Core/DataflowGraph.inl index 1c4d0716305..274bec42f41 100644 --- a/src/Dataflow/Core/DataflowGraph.inl +++ b/src/Dataflow/Core/DataflowGraph.inl @@ -22,7 +22,7 @@ inline void DataflowGraph::addFactory( std::shared_ptr f ) { m_factories->addFactory( f->getName(), f ); } -inline const std::vector>* DataflowGraph::getNodes() const { +inline const std::vector>* DataflowGraph::getNodes() const { return &m_nodes; } inline const std::vector>* DataflowGraph::getNodesByLevel() const { diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index 5f2c0bb6ab2..62d4665abbb 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -62,13 +62,7 @@ void Node::fromJson( const nlohmann::json& data ) { generateUuid(); } if ( data.contains( "model" ) ) { - std::string readTypeName = data["model"]["name"]; - if ( readTypeName != m_typeName ) { - LOG( logERROR ) << "Node::fromJson : incoherent type names : json data : " - << readTypeName << " -- expected : " << m_typeName; - } if ( data["model"].contains( "instance" ) ) { m_instanceName = data["model"]["instance"]; } - // get the specific concrete node informations const auto& datamodel = data["model"]; fromJsonInternal( datamodel ); diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index efbdd0ab9dc..64411338611 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -132,15 +132,6 @@ class RA_DATAFLOW_API Node const nlohmann::json& getJsonMetaData(); /// @} - /// \brief Tests if the node is deletable (deprecated) - /// \todo remove the deletable status management of the node through the use of proper - /// ownership. This will require modifying RadiumNodeEditor external - /// \return the deletable status of the node - [[deprecated]] bool isDeletable(); - - /// \brief Set the deletable status of the node - [[deprecated]] void setDeletableStatus( bool deletable = true ); - /// \brief Flag that checks if the node is already initialized bool m_initialized { false }; @@ -179,14 +170,6 @@ class RA_DATAFLOW_API Node template void addOutput( PortOut* out, T* data ); - /// Adds an interface port to the node. - /// This function checks if there is no interface port with the same name already associated - /// with this node. - /// \param port The interface port to add. - /// \deprecated, use buildInterfaces instead - - [[deprecated]] bool addInterface( PortBase* port ); - /// \brief Adds an editable parameter to the node if it does not already exist. /// \note the node will take ownership of the editable object. /// \param editableParameter The editable parameter to add. diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index 8216ff76f5b..b4da91f6298 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -42,14 +42,6 @@ inline void Node::setInstanceName( const std::string& newName ) { m_instanceName = newName; } -inline bool Node::isDeletable() { - return m_isDeletable; -} - -inline void Node::setDeletableStatus( bool deletable ) { - m_isDeletable = deletable; -} - inline const std::vector>& Node::getInputs() { return m_inputs; } @@ -107,15 +99,6 @@ void Node::addOutput( PortOut* out, T* data ) { } } -inline bool Node::addInterface( PortBase* ports ) { - bool found = false; - for ( auto& port : m_interface ) { - if ( port->getName() == ports->getName() ) { found = true; } - } - if ( !found ) { m_interface.emplace_back( ports ); } - return !found; -} - inline bool Node::addEditableParameter( EditableParameterBase* editableParameter ) { bool found = false; for ( auto& edit : m_editableParameters ) { diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index 42916b3b3a3..5be1f667b26 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -39,16 +39,18 @@ Node* NodeFactory::createNode( std::string& nodeType, return nullptr; } -void NodeFactory::registerNodeCreator( std::string nodeType, +bool NodeFactory::registerNodeCreator( std::string nodeType, NodeCreatorFunctor nodeCreator, const std::string& nodeCategory ) { auto it = m_nodesCreators.find( nodeType ); if ( it == m_nodesCreators.end() ) { m_nodesCreators[nodeType] = { std::move( nodeCreator ), nodeCategory }; + return true; } else { std::cerr << "NodeFactory: trying to add an already existing node creator for type " << nodeType << "." << std::endl; + return false; } } @@ -97,10 +99,6 @@ bool registerFactory( NodeFactorySet::mapped_type factory ) { return s_factoryManager.addFactory( std::move( factoryName ), std::move( factory ) ); } -bool removeFactory( const NodeFactorySet::key_type& factoryName ) { - return s_factoryManager.removeFactory( factoryName ); -} - NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ) { auto factory = s_factoryManager.find( factoryName ); if ( factory == s_factoryManager.end() ) { return nullptr; } diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index f3f314b59e1..d401a01c9c8 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -45,10 +45,12 @@ class RA_DATAFLOW_API NodeFactory * Associate, for a given concrete node type, a custom NodeCreatorFunctor * @tparam T Concrete node type identifier * \param nodeCreator Functor to create an node of the corresponding concrete node type. - * \param nodeCategory Category of the node + * \param nodeCategory Category of the node. + * \return true if the node creator is successfully added, false if not (e.g. due to a name + * collision). */ template - void registerNodeCreator( NodeCreatorFunctor nodeCreator, + bool registerNodeCreator( NodeCreatorFunctor nodeCreator, const std::string& nodeCategory = "RadiumNodes" ); /** @@ -56,18 +58,22 @@ class RA_DATAFLOW_API NodeFactory * @tparam T Concrete node type identifier * \param instanceNamePrefix prefix of the node instance name (will be called "prefix_i" with i * a unique number. - * \param nodeCategory Category of the node + * \param nodeCategory Category of the node. + * \return true if the node creator is successfully added, false if not (e.g. due to a name + * collision). */ template - void registerNodeCreator( const std::string& instanceNamePrefix, + bool registerNodeCreator( const std::string& instanceNamePrefix, const std::string& nodeCategory = "RadiumNodes" ); /** * Associate, for a given concrete node type name, a NodeCreatorFunctor * \param nodeType the name of the concrete type * (the same as what is obtained by T::getTypename() on a node of type T) * \param nodeCreator Functor to create an node of the corresponding concrete node type. + * \return true if the node creator is successfully added, false if not (e.g. due to a name + * collision). */ - void registerNodeCreator( std::string nodeType, + bool registerNodeCreator( std::string nodeType, NodeCreatorFunctor nodeCreator, const std::string& nodeCategory = "RadiumNodes" ); @@ -206,16 +212,33 @@ RA_DATAFLOW_API NodeFactorySet getFactoryManager(); /** Register a factory into the manager. * The key will be fetched from the factory (its name) */ +/** + * \brief Register a factory into the manager. + * The key will be fetched from the factory (its name) + * \param factory + * \return true if the factory was registered, false if not (e.g. due to name collision). + */ RA_DATAFLOW_API bool registerFactory( NodeFactorySet::mapped_type factory ); -/** Remove a factory from the manager*/ -RA_DATAFLOW_API bool removeFactory( const NodeFactorySet::key_type& factoryName ); - +/** + * \brief Gets the given factory from the manager + * \param factoryName + * \return a shared_ptr to the requested factory, nullptr if the factory does not exist. + */ RA_DATAFLOW_API NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ); -RA_DATAFLOW_API NodeFactorySet::mapped_type getDataFlowBuiltInsFactory(); - +/** + * \brief Unregister the factory from the manager + * \param factoryName + * \return true if the factory was unregistered, false if not (e.g. for names not being managed). + */ RA_DATAFLOW_API bool unregisterFactory( NodeFactorySet::key_type factoryName ); + +/** + * \brief Gets the factory for nodes exported by the Core dataflow library. + * \return + */ +RA_DATAFLOW_API NodeFactorySet::mapped_type getDataFlowBuiltInsFactory(); } // namespace NodeFactoriesManager } // namespace Core diff --git a/src/Dataflow/Core/NodeFactory.inl b/src/Dataflow/Core/NodeFactory.inl index 54c71db1e81..6547969fc1f 100644 --- a/src/Dataflow/Core/NodeFactory.inl +++ b/src/Dataflow/Core/NodeFactory.inl @@ -6,15 +6,15 @@ namespace Dataflow { namespace Core { template -void NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, +bool NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, const std::string& nodeCategory ) { - registerNodeCreator( T::getTypename(), std::move( nodeCreator ), nodeCategory ); + return registerNodeCreator( T::getTypename(), std::move( nodeCreator ), nodeCategory ); } template -void NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, +bool NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, const std::string& nodeCategory ) { - registerNodeCreator( + return registerNodeCreator( T::getTypename(), [this, instanceNamePrefix]( const nlohmann::json& data ) { auto node = new T( instanceNamePrefix + std::to_string( this->nextNodeId() ) ); @@ -31,12 +31,6 @@ inline const NodeFactory::ContainerType& NodeFactory::getFactoryMap() const { inline bool NodeFactorySet::addFactory( NodeFactorySet::key_type factoryname, NodeFactorySet::mapped_type factory ) { const auto [loc, inserted] = insert( { std::move( factoryname ), std::move( factory ) } ); -#ifdef DEBUG - if ( !inserted ) { - std::cerr << "NodeFactorySet: Factory " << loc->first - << " already in the set. Not inserted again." << std::endl; - } -#endif return inserted; } diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index 46a687ca621..8b5d3ae0ea2 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -15,7 +15,12 @@ class SinkNode : public Node public: explicit SinkNode( const std::string& name ) : SinkNode( name, SinkNode::getTypename() ) {} + /** + * \brief initialize the interface port data pointer + */ + void init() override; void execute() override; + /** * Get the delivered data * @return a copy of the delivered data. diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index 7a478fb3b13..78d295a4ee5 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -17,16 +17,18 @@ SinkNode::SinkNode( const std::string& instanceName, const std::string& typeN } template -void SinkNode::execute() { - auto input = static_cast*>( m_inputs[0].get() ); +void SinkNode::init() { + std::cout << "Init sink node\n"; + // this should be done only once (or when the address of local data changes) auto interface = static_cast*>( m_interface[0] ); - if ( input->isLinked() ) { - // If interface is linked, do not copy the data, just transmit the pointer - interface->setData( &( input->getData() ) ); - } - else { - m_data = input->getData(); - } + interface->setData( &m_data ); + Node::init(); +} + +template +void SinkNode::execute() { + auto input = static_cast*>( m_inputs[0].get() ); + m_data = input->getData(); #ifdef GRAPH_CALL_TRACE std::cout << "\e[33m\e[1m" << getTypename() << "\e[0m \"" << getInstanceName() << "\": execute." << std::endl; diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index c60c23cfaee..4cb3c543b36 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -18,9 +18,10 @@ SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, template void SingleDataSourceNode::execute() { + // interfaces ports are at the same index as output ports auto interface = static_cast*>( m_interface[0] ); if ( interface->isLinked() ) { - // use exernal storage to deliver data + // use external storage to deliver data m_data = &( interface->getData() ); } else { diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp index ecacf28128b..f538752b067 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp @@ -36,7 +36,7 @@ class RA_DATAFLOW_API NodeAdapterModel : public QtNodes::NodeDataModel QString uuid() const override { return m_node->getUuid().c_str(); } - bool isDeletable() override { return m_node->isDeletable(); } + bool isDeletable() override { return true; } // Assume all nodes belong to the graph void updateState() override; diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 7f9c2650e2a..e3ac8764bc3 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -256,12 +256,20 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { new NodeFactorySet::mapped_type::element_type( "CustomNodesUnitTests" ) }; // add node creators to the factory - customFactory->registerNodeCreator( + bool registered; + registered = customFactory->registerNodeCreator( Customs::CustomStringSource::getTypename() + "_", "Custom" ); - customFactory->registerNodeCreator( + REQUIRE( registered == true ); + registered = customFactory->registerNodeCreator( Customs::CustomStringSink::getTypename() + "_", "Custom" ); - customFactory->registerNodeCreator>( + REQUIRE( registered == true ); + registered = customFactory->registerNodeCreator>( Customs::FilterSelector::getTypename() + "_", "Custom" ); + REQUIRE( registered == true ); + // The same node can't be register twice in the same factory + registered = customFactory->registerNodeCreator>( + Customs::FilterSelector::getTypename() + "_", "Custom" ); + REQUIRE( registered == false ); // register the factory into the system to enable loading any graph that use these nodes NodeFactoriesManager::registerFactory( customFactory ); @@ -280,12 +288,20 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { delete g; g = new DataflowGraph( "" ); - g->loadFromJson( tmpdir + "customGraph.json" ); + bool loaded = g->loadFromJson( tmpdir + "customGraph.json" ); + + REQUIRE( loaded == true ); + g->destroy(); + delete g; + + /// try to load the graph without custom factory + NodeFactoriesManager::unregisterFactory( customFactory->getName() ); - // The graph can't be loaded correctly if there is no factory for the custom nodes - // todo, modify loadFromJson to detect loading error and test below - // REQUIRE( !g->compile() ); + g = new DataflowGraph( "" ); + loaded = g->loadFromJson( tmpdir + "customGraph.json" ); + REQUIRE( loaded == false ); + delete g; std::filesystem::remove_all( tmpdir ); } } From a3c056a57112b066a49872eaedd420ffbb6a7881 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 13 Oct 2022 13:42:31 +0200 Subject: [PATCH 056/239] [dataflow] improve source code readability. --- src/Dataflow/Core/Enumerator.hpp | 6 ++- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 4 -- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 26 +++++++++--- .../Core/Nodes/Functionals/BinaryOpNode.hpp | 41 ++++++++++--------- .../Nodes/Functionals/CoreDataFunctionals.hpp | 2 +- .../Core/Nodes/Functionals/FilterNode.hpp | 20 ++++++++- .../Core/Nodes/Functionals/FilterNode.inl | 22 ++++------ .../Core/Nodes/Functionals/ReduceNode.hpp | 8 ++++ .../Core/Nodes/Functionals/ReduceNode.inl | 32 +++++---------- .../Core/Nodes/Functionals/TransformNode.hpp | 6 +++ .../Core/Nodes/Functionals/TransformNode.inl | 23 ++++------- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 4 ++ src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 10 ++--- .../Core/Nodes/Sources/FunctionSource.hpp | 14 +++++-- .../Core/Nodes/Sources/FunctionSource.inl | 4 -- .../Nodes/Sources/SingleDataSourceNode.hpp | 9 ++-- .../Nodes/Sources/SingleDataSourceNode.inl | 2 - src/Dataflow/Core/Port.hpp | 17 ++++++-- src/Dataflow/Core/TypeDemangler.hpp | 2 +- tests/unittest/Dataflow/customnodes.cpp | 25 +++-------- 20 files changed, 153 insertions(+), 124 deletions(-) diff --git a/src/Dataflow/Core/Enumerator.hpp b/src/Dataflow/Core/Enumerator.hpp index 6e54e8f036d..2bc55939ad4 100644 --- a/src/Dataflow/Core/Enumerator.hpp +++ b/src/Dataflow/Core/Enumerator.hpp @@ -10,7 +10,11 @@ namespace Dataflow { namespace Core { /** - * \brief Allow to manage + * \brief This class might be removed in a future and replaced by an instantiation of + * Ra::Core::Utils::BijectiveAssociation. + * + * This class allows to associate Values of type T to an int. + * Used right now to build the Node edition UI. * \tparam T */ template diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index ed11d6b4337..8dda78a2997 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -1,9 +1,5 @@ #include -#include -#include -#include - #include #include diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp index 54cb823cbc7..3bb6d864ba4 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -1,8 +1,4 @@ #pragma once - -/* - * Commented out to speedup compilationUser have to include only the desired - */ #include #include #include @@ -11,8 +7,28 @@ namespace Ra { namespace Dataflow { namespace Core { namespace NodeFactoriesManager { + +/** + * \brief Create the node system default factory. + * + * The default factory of the node system contains instantiation of the nodes below for the + * following type + * - Scalar, float, double int, unsigned int + * - Color, VectorDf, VectorDd, VectorDi, VectorDi (where D in {2, 3, 4} + * + * List of instanced nodes for any TYPE above + * - SingleDataSourceNode and SingleDataSourceNode> + * - FunctionSourceNode, FunctionSourceNode + * - FunctionSourceNode, FunctionSourceNode + * - SinkNode, SinkNode + * - FilterNode> + * - TransformNode>, ReduceNode> + * - BinaryOpNode, BinaryOpNode>, BinaryOpNode + * + * All these node might be serialized/unserialized without any additional nor custom factory. + */ void registerStandardFactories(); -} +} // namespace NodeFactoriesManager } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp index 3ccb7f1c23f..4d15563d8a1 100644 --- a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp @@ -179,23 +179,21 @@ class BinaryOpNode : public Node BinaryOpNode( instanceName, getTypename(), op ) {} void init() override { - Node::init(); m_result = t_out {}; + Node::init(); } void execute() override { - auto f = m_operator; - auto p_f = static_cast*>( m_inputs[2].get() ); - if ( p_f->isLinked() ) { f = p_f->getData(); } - auto p_a = static_cast*>( m_inputs[0].get() ); - auto p_b = static_cast*>( m_inputs[1].get() ); - if ( p_a->isLinked() && p_b->isLinked() ) { + auto f = m_operator; + if ( m_portF->isLinked() ) { f = m_portF->getData(); } + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portA->isLinked() && m_portB->isLinked() ) { m_result = internal::ExecutorHelper::executeInternal( - p_a->getData(), p_b->getData(), f ); + m_portA->getData(), m_portB->getData(), f ); } } - /// Sets the operator on the node + /// \brief Sets the operator to be evaluated by the node. void setOperator( BinaryOperator op ) { m_operator = op; } protected: @@ -203,16 +201,12 @@ class BinaryOpNode : public Node const std::string& typeName, BinaryOperator op ) : Node( instanceName, typeName ), m_operator( op ) { - auto in_a = new PortIn( "a", this ); - addInput( in_a ); - in_a->mustBeLinked(); - auto in_b = new PortIn( "b", this ); - addInput( in_b ); - in_b->mustBeLinked(); - auto f = new PortIn( "f", this ); - addInput( f ); - auto out = new PortOut( "r", this ); - addOutput( out, &m_result ); + addInput( m_portA ); + m_portA->mustBeLinked(); + addInput( m_portB ); + m_portB->mustBeLinked(); + addInput( m_portF ); + addOutput( m_portR, &m_result ); } void toJsonInternal( nlohmann::json& data ) const override { @@ -228,9 +222,18 @@ class BinaryOpNode : public Node } private: + /// \brief the used operator BinaryOperator m_operator = []( Arg1_type, Arg2_type ) -> Res_type { return Res_type {}; }; t_out m_result; + /// @{ + /// \brief Alias for the ports (allow simpler access) + PortIn* m_portA { new PortIn( "a", this ) }; + PortIn* m_portB { new PortIn( "b", this ) }; + PortIn* m_portF { new PortIn( "f", this ) }; + PortOut* m_portR { new PortOut( "r", this ) }; + /// @} + public: static const std::string& getTypename() { static std::string demangledName = diff --git a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp index 994cd506996..f93ebceba64 100644 --- a/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/CoreDataFunctionals.hpp @@ -16,7 +16,7 @@ namespace Functionals { using namespace Ra::Core; -// This macro does not end with semicolon. To be added when callin it +// This macro does not end with semicolon. To be added when calling it #define DECLARE_FUNCTIONALS( SUFFIX, TYPE ) \ using ArrayFilter##SUFFIX = FilterNode>; \ using ArrayTransformer##SUFFIX = TransformNode>; \ diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index fc4a13c56cf..c4233b3dc26 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -8,10 +8,22 @@ namespace Dataflow { namespace Core { namespace Functionals { -/** \brief Filter on iterable collection +/** \brief Filter on iterable collection. * \tparam coll_t the collection to filter. Must respect the SequenceContainer requirements * \tparam v_t (optional), type of the element in the collection. Default to coll_t::value_type * \see https://en.cppreference.com/w/cpp/named_req/SequenceContainer + * + * This node apply an operator f on its input such that to keep only elements validated by a + * predicate : + * + * This node has two inputs : + * - in : port accepting the input data of type coll_t. Must be linked. + * - f : port accepting an operator with profile std::function. + * Link to this port is not mandatory, the operator might be set once for the node. + * If this port is linked, the operator will be taken from the port. + * + * This node has one output : + * - out : port giving a coll_t such that out = std::copy_if(a, f) */ template class FilterNode : public Node @@ -53,6 +65,12 @@ class FilterNode : public Node UnaryPredicate m_predicate; coll_t m_elements; + /// @{ + /// \brief Alias for the ports (allow simpler access) + PortIn* m_portIn { new PortIn( "in", this ) }; + PortIn* m_portPredicate { new PortIn( "f", this ) }; + PortOut* m_portOut { new PortOut( "out", this ) }; + /// @} public: static const std::string& getTypename(); }; diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl index b41094ef1aa..8524d2eff85 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl @@ -29,12 +29,10 @@ void FilterNode::init() { template void FilterNode::execute() { - auto predPort = static_cast*>( m_inputs[1].get() ); - auto f = m_predicate; - if ( predPort->isLinked() ) { f = predPort->getData(); } - auto input = static_cast*>( m_inputs[0].get() ); - if ( input->isLinked() ) { - const auto& inData = input->getData(); + auto f = m_portPredicate->isLinked() ? m_portPredicate->getData() : m_predicate; + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portIn->isLinked() ) { + const auto& inData = m_portIn->getData(); m_elements.clear(); // m_elements.reserve( inData.size() ); // --> this is not a requirement of // SequenceContainer @@ -59,15 +57,11 @@ FilterNode::FilterNode( const std::string& instanceName, const std::string& typeName, UnaryPredicate filterFunction ) : Node( instanceName, typeName ), m_predicate( filterFunction ) { - auto portIn = new PortIn( "in", this ); - addInput( portIn ); - portIn->mustBeLinked(); - auto predicate = new PortIn( "f", this ); - addInput( predicate ); - - auto portOut = new PortOut( "out", this ); - addOutput( portOut, &m_elements ); + addInput( m_portIn ); + m_portIn->mustBeLinked(); + addInput( m_portPredicate ); + addOutput( m_portOut, &m_elements ); } template diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp index a7db270245e..1a8d61be862 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp @@ -72,6 +72,14 @@ class ReduceNode : public Node v_t m_result; v_t m_init; + /// @{ + /// \brief Alias for the ports (allow simpler access) + PortIn* m_portIn { new PortIn( "in", this ) }; + PortIn* m_portF { new PortIn( "f", this ) }; + PortIn* m_portInit { new PortIn( "init", this ) }; + PortOut* m_portOut { new PortOut( "out", this ) }; + /// @} + public: static const std::string& getTypename(); }; diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl index 8344d1ba744..2f60ad24f3b 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl @@ -34,16 +34,12 @@ void ReduceNode::init() { template void ReduceNode::execute() { - auto f = m_operator; - auto iv = m_init; - auto ivp = static_cast*>( m_inputs[2].get() ); - if ( ivp->isLinked() ) { iv = ivp->getData(); } - m_result = iv; - auto predPort = static_cast*>( m_inputs[1].get() ); - if ( predPort->isLinked() ) { f = predPort->getData(); } - auto input = static_cast*>( m_inputs[0].get() ); - if ( input->isLinked() ) { - const auto& inData = input->getData(); + auto f = m_portF->isLinked() ? m_portF->getData() : m_operator; + auto iv = m_portInit->isLinked() ? m_portInit->getData() : m_init; + m_result = iv; + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portIn->isLinked() ) { + const auto& inData = m_portIn->getData(); m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); #ifdef GRAPH_CALL_TRACE std::cout << "\e[36m\e[1mMReduceNode \e[0m \"" << m_instanceName << "\": execute, from " @@ -65,18 +61,12 @@ ReduceNode::ReduceNode( const std::string& instanceName, ReduceOperator op, v_t initialValue ) : Node( instanceName, typeName ), m_operator( op ), m_init( initialValue ) { - auto in = new PortIn( "in", this ); - addInput( in ); - in->mustBeLinked(); - auto f = new PortIn( "f", this ); - addInput( f ); - - auto iv = new PortIn( "init", this ); - addInput( iv ); - - auto out = new PortOut( "out", this ); - addOutput( out, &m_result ); + addInput( m_portIn ); + m_portIn->mustBeLinked(); + addInput( m_portF ); + addInput( m_portInit ); + addOutput( m_portOut, &m_result ); } template diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp index a157ee7d279..f8926bd071e 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp @@ -62,6 +62,12 @@ class TransformNode : public Node TransformOperator m_operator; coll_t m_elements; + /// @{ + /// \brief Alias for the ports (allow simpler access) + PortIn* m_portIn { new PortIn( "in", this ) }; + PortIn* m_portOperator { new PortIn( "f", this ) }; + PortOut* m_portOut { new PortOut( "out", this ) }; + /// @} public: static const std::string& getTypename(); }; diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl index 2e61229985d..4e81f27b2e6 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl @@ -27,12 +27,10 @@ void TransformNode::init() { template void TransformNode::execute() { - auto predPort = static_cast*>( m_inputs[1].get() ); - auto f = m_operator; - if ( predPort->isLinked() ) { f = predPort->getData(); } - auto input = static_cast*>( m_inputs[0].get() ); - if ( input->isLinked() ) { - const auto& inData = input->getData(); + auto f = m_portOperator->isLinked() ? m_portOperator->getData() : m_operator; + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portIn->isLinked() ) { + const auto& inData = m_portIn->getData(); m_elements.clear(); // m_elements.reserve( inData.size() ); // --> this is not a requirement of // SequenceContainer @@ -57,15 +55,10 @@ TransformNode::TransformNode( const std::string& instanceName, const std::string& typeName, TransformOperator op ) : Node( instanceName, typeName ), m_operator( op ) { - auto in = new PortIn( "in", this ); - addInput( in ); - in->mustBeLinked(); - - auto f = new PortIn( "f", this ); - addInput( f ); - - auto out = new PortOut( "out", this ); - addOutput( out, &m_elements ); + addInput( m_portIn ); + m_portIn->mustBeLinked(); + addInput( m_portOperator ); + addOutput( m_portOut, &m_elements ); } template diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index 8b5d3ae0ea2..f472e2b72ed 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -39,6 +39,10 @@ class SinkNode : public Node private: T m_data; + /// @{ + /// \brief Alias for the ports (allow simpler access) + PortIn* m_portIn { new PortIn( "from", this ) }; + /// @} public: static const std::string& getTypename(); }; diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index 78d295a4ee5..7806f58cd55 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -11,14 +11,13 @@ namespace Sinks { template SinkNode::SinkNode( const std::string& instanceName, const std::string& typeName ) : Node( instanceName, typeName ) { - auto portIn = new PortIn( "from", this ); - portIn->mustBeLinked(); - addInput( portIn ); + + m_portIn->mustBeLinked(); + addInput( m_portIn ); } template void SinkNode::init() { - std::cout << "Init sink node\n"; // this should be done only once (or when the address of local data changes) auto interface = static_cast*>( m_interface[0] ); interface->setData( &m_data ); @@ -27,8 +26,7 @@ void SinkNode::init() { template void SinkNode::execute() { - auto input = static_cast*>( m_inputs[0].get() ); - m_data = input->getData(); + m_data = m_portIn->getData(); #ifdef GRAPH_CALL_TRACE std::cout << "\e[33m\e[1m" << getTypename() << "\e[0m \"" << getInstanceName() << "\": execute." << std::endl; diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index 9d07f962dd7..f23169787e5 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -10,6 +10,11 @@ namespace Dataflow { namespace Core { namespace Sources { +/** + * \brief Node that deliver a std::function + * \tparam R return type of the function + * \tparam Type of the function arguments + */ template class FunctionSourceNode : public Node { @@ -39,13 +44,14 @@ class FunctionSourceNode : public Node void fromJsonInternal( const nlohmann::json& ) override; void toJsonInternal( nlohmann::json& ) const override; + /// @{ /// The data provided by the node - function_type* m_data { nullptr }; - - function_type m_localData; + function_type m_localData { []( Args... ) { return R {}; } }; + function_type* m_data { &m_localData }; + /// @} /// Alias to the output port - PortOut* m_portOut { nullptr }; + PortOut* m_portOut { new PortOut( "f", this ) }; public: static const std::string& getTypename(); diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl index 5dd46cf832e..f7bcc60db84 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl @@ -11,9 +11,6 @@ template FunctionSourceNode::FunctionSourceNode( const std::string& instanceName, const std::string& typeName ) : Node( instanceName, typeName ) { - m_localData = []( Args... ) { return R {}; }; - m_data = &m_localData; - m_portOut = new PortOut( "f", this ); addOutput( m_portOut, m_data ); } @@ -35,7 +32,6 @@ template void FunctionSourceNode::setData( function_type* data ) { m_localData = *data; m_data = &m_localData; - m_portOut->setData( m_data ); } diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index ea4c7215ff1..15bf9afca59 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -62,15 +62,16 @@ class SingleDataSourceNode : public Node void fromJsonInternal( const nlohmann::json& ) override; void toJsonInternal( nlohmann::json& ) const override; + /// @{ /// The data provided by the node - /// Ownership of this pointer is left to the caller - T* m_data { nullptr }; - /// Used to deliver (and edit) data when the interface is not connected. T m_localData; + /// Ownership of this pointer is left to the caller + T* m_data { &m_localData }; + /// @} /// Alias to the output port - PortOut* m_portOut { nullptr }; + PortOut* m_portOut { new PortOut( "to", this ) }; /// used only at deserialization void setData( T& data ); diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 4cb3c543b36..b1c4e838f7a 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -11,8 +11,6 @@ template SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, const std::string& typeName ) : Node( instanceName, typeName ) { - m_data = &m_localData; - m_portOut = new PortOut( "to", this ); addOutput( m_portOut, m_data ); } diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index 89e51bc80d6..afd63beb8af 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -17,7 +17,11 @@ class PortOut; template class PortIn; -/// Base class for nodes' ports +/** + * \brief Base class for nodes' ports + * A port is a strongly typed extremity of connections between nodes. + * + */ class RA_DATAFLOW_API PortBase { private: @@ -98,6 +102,11 @@ class RA_DATAFLOW_API PortBase virtual std::string getTypeName() = 0; }; +/** + * \brief Output port delivering data of Type T. + * Output port stores a non-owning pointer to the data that will be made available on a connection. + * \tparam T The type of the delivered data. + */ template class PortOut : public PortBase { @@ -144,8 +153,10 @@ class PortOut : public PortBase }; /** - * PortIn is an Observable&> that notifies its observers at connect/disconnect event - * @tparam T + * \brief Input port accepting data of type T. + * An input port does not staore the data but is an accessor to the data stored on the connected + * output port. An Input port is observable and notify its observers at each connect/disconnect + * event. \tparam T The accepted data type */ template class PortIn : public PortBase, diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index 01a05c34069..cb7859959b6 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -4,7 +4,7 @@ namespace Ra { namespace Dataflow { namespace Core { -/// Return the human readable version of the type name T with simplified radium types +/// \brief Return the human readable version of the type name T with simplified radium types template const char* simplifiedDemangledType() noexcept; diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index e3ac8764bc3..ee516885c79 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -161,19 +161,6 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { // build a graph auto g = buildgraph( "testCustomNodes" ); - //! [Inspect the graph interface : inputs and outputs port] - auto inputs = g->getAllDataSetters(); - std::cout << "Input ports (" << inputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : inputs ) { - std::cout << "\t\"" << portName << "\" accepting type " << portType << "\n"; - } - auto outputs = g->getAllDataGetters(); - std::cout << "Output ports (" << outputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : outputs ) { - std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; - } - //! [Inspect the graph interface : inputs and outputs port] - // get input and ouput of the graph auto inputCollection = g->getDataSetter( "ds_to" ); REQUIRE( inputCollection != nullptr ); @@ -219,8 +206,8 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { auto& vop = generatedOperator->getData(); REQUIRE( vres.size() == testVector.size() ); - std::cout << "Result after applying operator " << vop << " and threshold " << threshold - << ": \n\t"; + std::cout << "Result after applying operator " << vop << " (from " << op + << " ) and threshold " << threshold << ": \n\t"; for ( auto ord : vres ) { std::cout << ord << ' '; } @@ -231,8 +218,8 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { g->execute(); REQUIRE( vres.size() == 0 ); - std::cout << "Result after applying operator " << vop << " and threshold " << threshold - << ": \n\t"; + std::cout << "Result after applying operator " << vop << " (from " << op + << " ) and threshold " << threshold << ": \n\t"; for ( auto ord : vres ) { std::cout << ord << ' '; } @@ -242,8 +229,8 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { op = "<"; g->execute(); - std::cout << "Result after applying operator " << vop << " and threshold " << threshold - << ": \n\t"; + std::cout << "Result after applying operator " << vop << " (from " << op + << " ) and threshold " << threshold << ": \n\t"; for ( auto ord : vres ) { std::cout << ord << ' '; } From 14c4d8441d4cba849d826a81b1989ddeb680dcd7 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 17 Oct 2022 15:34:22 +0200 Subject: [PATCH 057/239] [tests] add graph inspection unittest --- tests/unittest/CMakeLists.txt | 1 + tests/unittest/Dataflow/graphinspect.cpp | 100 ++++++++ .../unittest/data/Dataflow/ExampleGraph.json | 233 ++++++++++++++++++ 3 files changed, 334 insertions(+) create mode 100644 tests/unittest/Dataflow/graphinspect.cpp create mode 100644 tests/unittest/data/Dataflow/ExampleGraph.json diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index 42b10224fcd..ade0b81db75 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -32,6 +32,7 @@ set(test_src Core/variableset.cpp Core/vectorarray.cpp Dataflow/customnodes.cpp + Dataflow/graphinspect.cpp Dataflow/nodes.cpp Dataflow/serialization.cpp Dataflow/sourcesandsinks.cpp diff --git a/tests/unittest/Dataflow/graphinspect.cpp b/tests/unittest/Dataflow/graphinspect.cpp new file mode 100644 index 00000000000..f333f796e16 --- /dev/null +++ b/tests/unittest/Dataflow/graphinspect.cpp @@ -0,0 +1,100 @@ +#include + +#include + +#include +using namespace Ra::Dataflow::Core; + +TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { + SECTION( "Inspection of a graph" ) { + DataflowGraph g( "" ); + g.loadFromJson( "data/Dataflow/ExampleGraph.json" ); + + // Factories used by the graph + auto factories = g.getNodeFactories(); + REQUIRE( factories != nullptr ); + std::cout << "Used factories by the graph \"" << g.getInstanceName() << "\" with type \"" + << g.getTypeName() << "\" :\n"; + for ( const auto& f : *( factories.get() ) ) { + std::cout << "\t" << f.first << "\n"; + } + + // Nodes of the graph + auto nodes = g.getNodes(); + REQUIRE( nodes != nullptr ); + std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() + << ") :\n"; + REQUIRE( nodes->size() == g.getNodesCount() ); + for ( const auto& n : *( nodes ) ) { + std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() + << "\"\n"; + // Inspect input, output and interfaces of the node + std::cout << "\t\tInput ports :\n"; + for ( const auto& p : n->getInputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tOutput ports :\n"; + for ( const auto& p : n->getOutputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tInterface ports :\n"; + for ( const auto& p : n->getInterfaces() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + } + + // Nodes by level after the compilation + auto c = g.compile(); + REQUIRE( c == true ); + auto cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto n : ( *cn )[i] ) { + std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + } + } + + // describe the graph interface : inputs and outputs port of the whole graph (not of the + // nodes) + std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; + auto inputs = g.getAllDataSetters(); + std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + } + auto outputs = g.getAllDataGetters(); + std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + } + + // removing the boolean sink from the graph + auto n = g.getNode( "validation value" ); + REQUIRE( n->getInstanceName() == "validation value" ); + c = g.removeNode( n ); + REQUIRE( c == true ); + c = g.compile(); + REQUIRE( c == true ); + // Simplified graph after compilation + cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level, after sink deletion :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto nn : ( *cn )[i] ) { + std::cout << "\t\t\"" << nn->getInstanceName() << "\"\n"; + } + } + + // the source "Validator" is no more in level 0 as it is non reachable from a sink in the + // graph. + bool found = false; + for ( const auto nn : ( *cn )[0] ) { + if ( nn->getInstanceName() == "Validator" ) { found = true; } + } + REQUIRE( found == false ); + } +} diff --git a/tests/unittest/data/Dataflow/ExampleGraph.json b/tests/unittest/data/Dataflow/ExampleGraph.json new file mode 100644 index 00000000000..6c9f70b6aae --- /dev/null +++ b/tests/unittest/data/Dataflow/ExampleGraph.json @@ -0,0 +1,233 @@ +{ + "id": "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}", + "model": { + "graph": { + "connections": [ + { + "in_id": "{016829fe-65fc-4b09-aaaf-04b76813e9bd}", + "in_index": 0, + "out_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "out_index": 0 + }, + { + "in_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "in_index": 0, + "out_id": "{564cd37a-5d99-45cd-a642-8743a7b10adc}", + "out_index": 0 + }, + { + "in_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "in_index": 1, + "out_id": "{5bd5b929-acf2-461b-a7da-73e662d61962}", + "out_index": 0 + }, + { + "in_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "in_index": 2, + "out_id": "{7a0c688e-c785-4849-ad34-89abd1473f4c}", + "out_index": 0 + }, + { + "in_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", + "in_index": 0, + "out_id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", + "out_index": 0 + }, + { + "in_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", + "in_index": 1, + "out_id": "{5bd5b929-acf2-461b-a7da-73e662d61962}", + "out_index": 0 + }, + { + "in_id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", + "in_index": 0, + "out_id": "{564cd37a-5d99-45cd-a642-8743a7b10adc}", + "out_index": 0 + }, + { + "in_id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", + "in_index": 1, + "out_id": "{6d442201-7e25-44f4-81cc-4dcea78780b2}", + "out_index": 0 + }, + { + "in_id": "{465d2861-dcbe-4717-a70d-c75e1042a730}", + "in_index": 0, + "out_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", + "out_index": 0 + }, + { + "in_id": "{0b74b010-2bc6-4b56-914d-ddbf30701946}", + "in_index": 0, + "out_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", + "out_index": 0 + }, + { + "in_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", + "in_index": 0, + "out_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "out_index": 0 + }, + { + "in_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", + "in_index": 1, + "out_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", + "out_index": 0 + }, + { + "in_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", + "in_index": 2, + "out_id": "{f920fe05-221d-4549-819a-f24dce92d9db}", + "out_index": 0 + } + ], + "factories": [], + "nodes": [ + { + "id": "{564cd37a-5d99-45cd-a642-8743a7b10adc}", + "model": { + "comment": "Unable to save data when serializing a SingleDataSourceNode>.", + "instance": "Vector", + "name": "Source>" + }, + "position": { + "x": -288.0, + "y": 58.0 + } + }, + { + "id": "{6d442201-7e25-44f4-81cc-4dcea78780b2}", + "model": { + "comment": "Unable to save data when serializing a FunctionSourceNode>.", + "instance": "Doubler", + "name": "Source>" + }, + "position": { + "x": -290.0, + "y": 139.0 + } + }, + { + "id": "{7a0c688e-c785-4849-ad34-89abd1473f4c}", + "model": { + "instance": "Neutral", + "name": "Source", + "number": 0.0 + }, + "position": { + "x": -289.0, + "y": -83.0 + } + }, + { + "id": "{5bd5b929-acf2-461b-a7da-73e662d61962}", + "model": { + "comment": "Unable to save data when serializing a FunctionSourceNode>.", + "instance": "Reducer", + "name": "Source>" + }, + "position": { + "x": -292.0, + "y": 224.0 + } + }, + { + "id": "{016829fe-65fc-4b09-aaaf-04b76813e9bd}", + "model": { + "instance": "reduced vector", + "name": "Sink" + }, + "position": { + "x": 459.0, + "y": -140.0 + } + }, + { + "id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "model": { + "comment": "Reduce operator could not be serialized for Reduce>", + "instance": "Collection reducer - original collection", + "name": "Reduce>" + }, + "position": { + "x": 159.0, + "y": -133.0 + } + }, + { + "id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", + "model": { + "comment": "Reduce operator could not be serialized for Reduce>", + "instance": "Collection reducer - transformed collection", + "name": "Reduce>" + }, + "position": { + "x": 461.0, + "y": 79.0 + } + }, + { + "id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", + "model": { + "comment": "Transform operator could not be serialized for Transform>", + "instance": "Collection transformer", + "name": "Transform>" + }, + "position": { + "x": 159.0, + "y": 53.0 + } + }, + { + "id": "{465d2861-dcbe-4717-a70d-c75e1042a730}", + "model": { + "instance": "transformed/reduced vector", + "name": "Sink" + }, + "position": { + "x": 720.0, + "y": -62.0 + } + }, + { + "id": "{f920fe05-221d-4549-819a-f24dce92d9db}", + "model": { + "comment": "Unable to save data when serializing a FunctionSourceNode>.", + "instance": "Validator", + "name": "Source>" + }, + "position": { + "x": -291.0, + "y": 309.0 + } + }, + { + "id": "{0b74b010-2bc6-4b56-914d-ddbf30701946}", + "model": { + "instance": "validation value", + "name": "Sink" + }, + "position": { + "x": 969.0, + "y": 197.0 + } + }, + { + "id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", + "model": { + "comment": "Binary operator could not be serialized for BinaryOp bool>", + "instance": "Validator : evaluate the validation predicate", + "name": "BinaryOp bool>" + }, + "position": { + "x": 705.0, + "y": 158.0 + } + } + ] + }, + "instance": "Example graph", + "name": "Core DataflowGraph" + } +} From 6fa00f129691148f569f7d46182d83c2461bfc3e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 13 Oct 2022 16:09:11 +0200 Subject: [PATCH 058/239] [dataflow] make it compiles on Windows --- examples/DataflowExamples/HelloGraph/main.cpp | 3 +- src/Dataflow/Core/CMakeLists.txt | 5 +- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 126 +----------------- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp | 12 +- .../Nodes/Private/FunctionalsNodeFactory.cpp | 61 +++++++++ .../Nodes/Private/FunctionalsNodeFactory.hpp | 18 +++ .../Core/Nodes/Private/SinksNodeFactory.cpp | 53 ++++++++ .../Core/Nodes/Private/SinksNodeFactory.hpp | 18 +++ .../Core/Nodes/Private/SourcesNodeFactory.cpp | 62 +++++++++ .../Core/Nodes/Private/SourcesNodeFactory.hpp | 18 +++ .../Core/Nodes/Sinks/CoreDataSinks.hpp | 1 + src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 4 +- .../Core/Nodes/Sources/FunctionSource.inl | 4 +- .../Nodes/Sources/SingleDataSourceNode.inl | 6 +- src/Dataflow/Core/TypeDemangler.cpp | 17 ++- src/Dataflow/Core/TypeDemangler.hpp | 1 + src/Dataflow/Core/TypeDemangler.inl | 4 +- src/Dataflow/Core/filelist.cmake | 6 + tests/unittest/Dataflow/customnodes.cpp | 6 +- tests/unittest/Dataflow/nodes.cpp | 5 +- tests/unittest/Dataflow/serialization.cpp | 6 +- tests/unittest/Dataflow/sourcesandsinks.cpp | 6 +- 22 files changed, 299 insertions(+), 143 deletions(-) create mode 100644 src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.cpp create mode 100644 src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.hpp create mode 100644 src/Dataflow/Core/Nodes/Private/SinksNodeFactory.cpp create mode 100644 src/Dataflow/Core/Nodes/Private/SinksNodeFactory.hpp create mode 100644 src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp create mode 100644 src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.hpp diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index 11adb6329e5..e655bb13001 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -1,6 +1,7 @@ #include -#include #include +#include +#include using namespace Ra::Dataflow::Core; diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt index b4d1063c7c1..032c9a3dc7d 100644 --- a/src/Dataflow/Core/CMakeLists.txt +++ b/src/Dataflow/Core/CMakeLists.txt @@ -7,7 +7,7 @@ include(filelist.cmake) add_library( ${ra_dataflowcore_target} SHARED ${dataflow_core_sources} ${dataflow_core_headers} - ${dataflow_core_inlines} + ${dataflow_core_inlines} ${dataflow_core_private} ) # LocalDependencies from parent scope @@ -18,6 +18,9 @@ find_package(stduuid REQUIRED NO_DEFAULT_PATH) target_compile_definitions(${ra_dataflowcore_target} PRIVATE RA_DATAFLOW_EXPORTS) target_compile_options(${ra_dataflowcore_target} PUBLIC ${RA_DEFAULT_COMPILE_OPTIONS}) +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") + target_compile_options(${ra_dataflowcore_target} PRIVATE /bigobj) +endif() add_dependencies(${ra_dataflowcore_target} Core) target_link_libraries(${ra_dataflowcore_target} PUBLIC Core stduuid) diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 8dda78a2997..4a9ea079e96 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -1,8 +1,10 @@ -#include + #include -#include +#include +#include +#include namespace Ra { namespace Dataflow { @@ -10,132 +12,18 @@ namespace Core { namespace NodeFactoriesManager { -/** TODO : replace this by factory autoregistration at compile time */ -#define ADD_FUNCTIONALS_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ - FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayTransformer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::ArrayReducer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::BinaryOp##SUFFIX::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::BinaryOp##SUFFIX##Array::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::BinaryPredicate##SUFFIX::getTypename() + "_", #NAMESPACE ) - -/* - * not yet supported - FACTORY->registerNodeCreator( \ - NAMESPACE::BinaryPredicate##SUFFIX##Array::getTypename() + "_", #NAMESPACE ) -*/ - -#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##Source::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##ArraySource::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##UnaryFunctionSource::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##BinaryFunctionSource::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##UnaryPredicateSource::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##BinaryPredicateSource::getTypename() + "_", #NAMESPACE ) - -#define ADD_SINKS_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##Sink::getTypename() + "_", #NAMESPACE ); \ - FACTORY->registerNodeCreator( \ - NAMESPACE::PREFIX##ArraySink::getTypename() + "_", #NAMESPACE ) - void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; /* --- Sources --- */ - // bool could not be declared as others, because of the specificity of std::vector that is - // not compatible with Ra::Core::VectorArray implementation see - // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no - // Ra::Core::VectorArray of bool - coreFactory->registerNodeCreator( - Sources::BooleanSource::getTypename() + "_", "Sources" ); - // prevent Scalar type collision with float or double in factory -#ifdef CORE_USE_DOUBLE - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Float ); -#else - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Double ); -#endif - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Scalar ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Int ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, UInt ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Color ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2f ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2d ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3f ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3d ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4f ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4d ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2i ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector2ui ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3i ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector3ui ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4i ); - ADD_SOURCES_TO_FACTORY( coreFactory, Sources, Vector4ui ); + Private::registerSourcesFactories( coreFactory ); /* --- Sinks --- */ - // bool could not be declared as others, because of the specificity of std::vector that is - // not compatible with Ra::Core::VectorArray implementation see - // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no - // Ra::Core::VectorArray of bool - coreFactory->registerNodeCreator( Sinks::BooleanSink::getTypename() + "_", - "Sinks" ); -#ifdef CORE_USE_DOUBLE - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Float ); -#else - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Double ); -#endif - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Scalar ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Int ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, UInt ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Color ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2f ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2d ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3f ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3d ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4f ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4d ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2i ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector2ui ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3i ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector3ui ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4i ); - ADD_SINKS_TO_FACTORY( coreFactory, Sinks, Vector4ui ); + Private::registerSinksFactories( coreFactory ); /* --- Functionals */ -#ifdef CORE_USE_DOUBLE - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Float ); -#else - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Double ); -#endif - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Scalar ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Int ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, UInt ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Color ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2f ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2d ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3f ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3d ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4f ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4d ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2i ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector2ui ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3i ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector3ui ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4i ); - ADD_FUNCTIONALS_TO_FACTORY( coreFactory, Functionals, Vector4ui ); + Private::registerFunctionalsFactories( coreFactory ); /* --- Graphs --- */ coreFactory->registerNodeCreator( DataflowGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp index 3bb6d864ba4..c8fd82d811c 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.hpp @@ -1,8 +1,7 @@ #pragma once -#include -#include -#include +#include +#include namespace Ra { namespace Dataflow { namespace Core { @@ -26,8 +25,13 @@ namespace NodeFactoriesManager { * - BinaryOpNode, BinaryOpNode>, BinaryOpNode * * All these node might be serialized/unserialized without any additional nor custom factory. + * + * If needed, the definition of all these type aliases can be included using one of the headers + * - #include + * - #include + * - #include */ -void registerStandardFactories(); +RA_DATAFLOW_API void registerStandardFactories(); } // namespace NodeFactoriesManager } // namespace Core } // namespace Dataflow diff --git a/src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.cpp b/src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.cpp new file mode 100644 index 00000000000..f2b5bae2733 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.cpp @@ -0,0 +1,61 @@ +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +namespace NodeFactoriesManager { + +namespace Private { + +/** TODO : replace this by factory autoregistration at compile time */ +#define ADD_FUNCTIONALS_TO_FACTORY( FACTORY, NAMESPACE, SUFFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayFilter##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayTransformer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::ArrayReducer##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryOp##SUFFIX::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryOp##SUFFIX##Array::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryPredicate##SUFFIX::getTypename() + "_", #NAMESPACE ) + +/* + * not yet supported + FACTORY->registerNodeCreator( \ + NAMESPACE::BinaryPredicate##SUFFIX##Array::getTypename() + "_", #NAMESPACE ) +*/ + +void registerFunctionalsFactories( NodeFactorySet::mapped_type factory ) { + /* --- Functionals */ +#ifdef CORE_USE_DOUBLE + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Float ); +#else + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Double ); +#endif + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Scalar ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Int ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, UInt ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Color ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector2f ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector2d ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector3f ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector3d ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector4f ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector4d ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector2i ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector2ui ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector3i ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector3ui ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector4i ); + ADD_FUNCTIONALS_TO_FACTORY( factory, Functionals, Vector4ui ); +} +} // namespace Private +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.hpp b/src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.hpp new file mode 100644 index 00000000000..0d02ecdb766 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Private/FunctionalsNodeFactory.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace NodeFactoriesManager { +namespace Private { + +void registerFunctionalsFactories( NodeFactorySet::mapped_type factory ); + +} // namespace Private +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Private/SinksNodeFactory.cpp b/src/Dataflow/Core/Nodes/Private/SinksNodeFactory.cpp new file mode 100644 index 00000000000..e1fa6713761 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Private/SinksNodeFactory.cpp @@ -0,0 +1,53 @@ +#include +#include +namespace Ra { +namespace Dataflow { +namespace Core { + +namespace NodeFactoriesManager { + +namespace Private { + +/** TODO : replace this by factory autoregistration at compile time */ + +#define ADD_SINKS_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##Sink::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##ArraySink::getTypename() + "_", #NAMESPACE ) + +void registerSinksFactories( NodeFactorySet::mapped_type factory ) { + /* --- Sinks --- */ + // bool could not be declared as others, because of the specificity of std::vector that is + // not compatible with Ra::Core::VectorArray implementation see + // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no + // Ra::Core::VectorArray of bool + factory->registerNodeCreator( Sinks::BooleanSink::getTypename() + "_", + "Sinks" ); +#ifdef CORE_USE_DOUBLE + ADD_SINKS_TO_FACTORY( factory, Sinks, Float ); +#else + ADD_SINKS_TO_FACTORY( factory, Sinks, Double ); +#endif + ADD_SINKS_TO_FACTORY( factory, Sinks, Scalar ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Int ); + ADD_SINKS_TO_FACTORY( factory, Sinks, UInt ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Color ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector2f ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector2d ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector3f ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector3d ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector4f ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector4d ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector2i ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector2ui ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector3i ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector3ui ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector4i ); + ADD_SINKS_TO_FACTORY( factory, Sinks, Vector4ui ); +} +} // namespace Private +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Private/SinksNodeFactory.hpp b/src/Dataflow/Core/Nodes/Private/SinksNodeFactory.hpp new file mode 100644 index 00000000000..a048cb71cff --- /dev/null +++ b/src/Dataflow/Core/Nodes/Private/SinksNodeFactory.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace NodeFactoriesManager { +namespace Private { + +void registerSinksFactories( NodeFactorySet::mapped_type factory ); + +} // namespace Private +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp b/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp new file mode 100644 index 00000000000..a0b904d0568 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp @@ -0,0 +1,62 @@ +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Core { + +namespace NodeFactoriesManager { + +namespace Private { + +/** TODO : replace this by factory autoregistration at compile time */ +#define ADD_SOURCES_TO_FACTORY( FACTORY, NAMESPACE, PREFIX ) \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##Source::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##ArraySource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##UnaryFunctionSource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##BinaryFunctionSource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##UnaryPredicateSource::getTypename() + "_", #NAMESPACE ); \ + FACTORY->registerNodeCreator( \ + NAMESPACE::PREFIX##BinaryPredicateSource::getTypename() + "_", #NAMESPACE ) + +void registerSourcesFactories( NodeFactorySet::mapped_type factory ) { + /* --- Sources --- */ + // bool could not be declared as others, because of the specificity of std::vector that is + // not compatible with Ra::Core::VectorArray implementation see + // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no + // Ra::Core::VectorArray of bool + factory->registerNodeCreator( + Sources::BooleanSource::getTypename() + "_", "Sources" ); + // prevent Scalar type collision with float or double in factory +#ifdef CORE_USE_DOUBLE + ADD_SOURCES_TO_FACTORY( factory, Sources, Float ); +#else + ADD_SOURCES_TO_FACTORY( factory, Sources, Double ); +#endif + ADD_SOURCES_TO_FACTORY( factory, Sources, Scalar ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Int ); + ADD_SOURCES_TO_FACTORY( factory, Sources, UInt ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Color ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector2f ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector2d ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector3f ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector3d ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector4f ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector4d ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector2i ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector2ui ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector3i ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector3ui ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector4i ); + ADD_SOURCES_TO_FACTORY( factory, Sources, Vector4ui ); +} +} // namespace Private +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.hpp b/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.hpp new file mode 100644 index 00000000000..f40f2c50151 --- /dev/null +++ b/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.hpp @@ -0,0 +1,18 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Core { +namespace NodeFactoriesManager { +namespace Private { + +void registerSourcesFactories( NodeFactorySet::mapped_type factory ); + +} // namespace Private +} // namespace NodeFactoriesManager +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp index cb252c70038..854bc12e2f4 100644 --- a/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/CoreDataSinks.hpp @@ -2,6 +2,7 @@ #include #include +#include #include namespace Ra { diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index 7806f58cd55..aeffc73b69e 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -19,8 +19,8 @@ SinkNode::SinkNode( const std::string& instanceName, const std::string& typeN template void SinkNode::init() { // this should be done only once (or when the address of local data changes) - auto interface = static_cast*>( m_interface[0] ); - interface->setData( &m_data ); + auto interfacePort = static_cast*>( m_interface[0] ); + interfacePort->setData( &m_data ); Node::init(); } diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl index f7bcc60db84..558fde3313e 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl @@ -16,8 +16,8 @@ FunctionSourceNode::FunctionSourceNode( const std::string& instanceN template void FunctionSourceNode::execute() { - auto interface = static_cast*>( m_interface[0] ); - if ( interface->isLinked() ) { m_data = &( interface->getData() ); } + auto interfacePort = static_cast*>( m_interface[0] ); + if ( interfacePort->isLinked() ) { m_data = &( interfacePort->getData() ); } else { m_data = &m_localData; } diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index b1c4e838f7a..5e956e0c556 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -17,10 +17,10 @@ SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, template void SingleDataSourceNode::execute() { // interfaces ports are at the same index as output ports - auto interface = static_cast*>( m_interface[0] ); - if ( interface->isLinked() ) { + auto interfacePort = static_cast*>( m_interface[0] ); + if ( interfacePort->isLinked() ) { // use external storage to deliver data - m_data = &( interface->getData() ); + m_data = &( interfacePort->getData() ); } else { // use local storage to deliver data diff --git a/src/Dataflow/Core/TypeDemangler.cpp b/src/Dataflow/Core/TypeDemangler.cpp index 707d128b15d..b3f439f16b6 100644 --- a/src/Dataflow/Core/TypeDemangler.cpp +++ b/src/Dataflow/Core/TypeDemangler.cpp @@ -1,13 +1,20 @@ -#include +#include + #include +#include + namespace Ra { namespace Dataflow { namespace Core { namespace TypeInternal { -std::string makeTypeReadable( std::string fullType ) { - static Ra::Core::Utils::BijectiveAssociation knownTypes { + +/** \todo verify windows specific type demangling needs. + * + */ +RA_DATAFLOW_API std::string makeTypeReadable( std::string fullType ) { + static std::map knownTypes { { "std::", "" }, { ", std::allocator", "" }, { ", std::allocator", "" }, @@ -27,7 +34,9 @@ std::string makeTypeReadable( std::string fullType ) { { "Eigen::Matrix", "Vector4" }, { "Eigen::Matrix", "Vector4d" }, { "Eigen::Matrix", "Vector4i" }, - { "Eigen::Matrix", "Vector4ui" } }; + { "Eigen::Matrix", "Vector4ui" }, + // Windows (visual studio 2022) specific name fix + { " __ptr64", "" } }; for ( const auto& [key, value] : knownTypes ) { Ra::Core::Utils::replaceAllInString( fullType, key, value ); diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index cb7859959b6..a76aa4b4c85 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -1,4 +1,5 @@ #pragma once +#include namespace Ra { namespace Dataflow { diff --git a/src/Dataflow/Core/TypeDemangler.inl b/src/Dataflow/Core/TypeDemangler.inl index 29a1e325515..982556316bc 100644 --- a/src/Dataflow/Core/TypeDemangler.inl +++ b/src/Dataflow/Core/TypeDemangler.inl @@ -7,9 +7,9 @@ namespace Dataflow { namespace Core { namespace TypeInternal { -std::string makeTypeReadable( std::string ); - +RA_DATAFLOW_API std::string makeTypeReadable( std::string ); } + /// Return the human readable version of the type name T with simplified radium types template const char* simplifiedDemangledType() noexcept { diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index a15e7bab858..c90f42accc7 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -44,3 +44,9 @@ set(dataflow_core_inlines Port.inl TypeDemangler.inl ) + +set(dataflow_core_private + Nodes/Private/FunctionalsNodeFactory.hpp Nodes/Private/FunctionalsNodeFactory.cpp + Nodes/Private/SinksNodeFactory.hpp Nodes/Private/SinksNodeFactory.cpp + Nodes/Private/SourcesNodeFactory.hpp Nodes/Private/SourcesNodeFactory.cpp +) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index ee516885c79..7220b7e0325 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -8,8 +8,12 @@ #include +#include + #include -#include +#include +#include +#include using namespace Ra::Dataflow::Core; diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 89fff76310d..2d2d07bfa39 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -6,7 +6,10 @@ #include #include -#include + +#include +#include +#include using namespace Ra::Dataflow::Core; template diff --git a/tests/unittest/Dataflow/serialization.cpp b/tests/unittest/Dataflow/serialization.cpp index a6f0ba004e8..2293f6ba5dd 100644 --- a/tests/unittest/Dataflow/serialization.cpp +++ b/tests/unittest/Dataflow/serialization.cpp @@ -5,8 +5,12 @@ #include +#include + #include -#include +#include +#include +#include TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { SECTION( "Serialization of a graph" ) { diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp index ff3bf7fd095..1eb4ef9848c 100644 --- a/tests/unittest/Dataflow/sourcesandsinks.cpp +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -6,11 +6,13 @@ #include #include -#include +#include +#include +#include using namespace Ra::Dataflow::Core; -//#define USE_SOURCE_DATA +// #define USE_SOURCE_DATA template void testGraph( const std::string& name, T in, T& out ) { From 5ad5dce91568ada44f7b6f5e71a39d20af0e0291 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 14 Oct 2022 14:34:06 +0200 Subject: [PATCH 059/239] [tests] simplify unittests on predefined nodes --- tests/unittest/Dataflow/nodes.cpp | 87 ++++++++++--------------------- 1 file changed, 27 insertions(+), 60 deletions(-) diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 2d2d07bfa39..7f0429613d3 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -326,63 +326,37 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { g->addNode( nodeT ); g->addNode( nodeRD ); - if ( !g->addLink( nodeS, "to", meanCalculator, "in" ) ) { - std::cout << "Unable to link " << nodeS->getInstanceName() << ":to and " - << meanCalculator->getInstanceName() << ":in !!!\n"; - } - if ( !g->addLink( nodeM, "f", meanCalculator, "f" ) ) { - std::cout << "Unable to link " << nodeM->getInstanceName() << ":f and " - << meanCalculator->getInstanceName() << ":f !!!\n"; - } - if ( !g->addLink( nodeN, "to", meanCalculator, "init" ) ) { - std::cout << "Unable to link " << nodeN->getInstanceName() << ":to and " - << meanCalculator->getInstanceName() << ":init !!!\n"; - } - if ( !g->addLink( meanCalculator, "out", nodeR, "from" ) ) { - std::cout << "Unable to link " << meanCalculator->getInstanceName() << ":out and " - << nodeR->getInstanceName() << ":from !!!\n"; - } - - if ( !g->addLink( nodeS, "to", nodeT, "in" ) ) { - std::cout << "Unable to link " << nodeS->getInstanceName() << ":to and " - << nodeT->getInstanceName() << ":in !!!\n"; - } - if ( !g->addLink( nodeD, "f", nodeT, "f" ) ) { - std::cout << "Unable to link " << nodeD->getInstanceName() << ":f and " - << nodeT->getInstanceName() << ":f !!!\n"; - } - if ( !g->addLink( nodeT, "out", doubleMeanCalculator, "in" ) ) { - std::cout << "Unable to link " << nodeT->getInstanceName() << ":out and " - << doubleMeanCalculator->getInstanceName() << ":in !!!\n"; - } - if ( !g->addLink( doubleMeanCalculator, "out", nodeRD, "from" ) ) { - std::cout << "Unable to link " << doubleMeanCalculator->getInstanceName() << ":out and " - << nodeRD->getInstanceName() << ":from !!!\n"; - } - if ( !g->addLink( nodeM, "f", doubleMeanCalculator, "f" ) ) { - std::cout << "Unable to link " << nodeM->getInstanceName() << ":f and " - << doubleMeanCalculator->getInstanceName() << ":f !!!\n"; - } + bool linkAdded; + linkAdded = g->addLink( nodeS, "to", meanCalculator, "in" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodeM, "f", meanCalculator, "f" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodeN, "to", meanCalculator, "init" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( meanCalculator, "out", nodeR, "from" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodeS, "to", nodeT, "in" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodeD, "f", nodeT, "f" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodeT, "out", doubleMeanCalculator, "in" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( doubleMeanCalculator, "out", nodeRD, "from" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodeM, "f", doubleMeanCalculator, "f" ); + REQUIRE( linkAdded == true ); g->addNode( nodePred ); g->addNode( sinkB ); g->addNode( validator ); - if ( !g->addLink( meanCalculator, "out", validator, "a" ) ) { - std::cout << "Unable to link " << meanCalculator->getInstanceName() << ":out and " - << validator->getInstanceName() << ":a !!!\n"; - } - if ( !g->addLink( doubleMeanCalculator, "out", validator, "b" ) ) { - std::cout << "Unable to link " << doubleMeanCalculator->getInstanceName() << ":out and " - << validator->getInstanceName() << ":b !!!\n"; - } - if ( !g->addLink( nodePred, "f", validator, "f" ) ) { - std::cout << "Unable to link " << nodePred->getInstanceName() << ":f and " - << validator->getInstanceName() << ":b !!!\n"; - } - if ( !g->addLink( validator, "r", sinkB, "from" ) ) { - std::cout << "Unable to link " << validator->getInstanceName() << ":r and " - << sinkB->getInstanceName() << ":from !!!\n"; - } + linkAdded = g->addLink( meanCalculator, "out", validator, "a" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( doubleMeanCalculator, "out", validator, "b" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( nodePred, "f", validator, "f" ); + REQUIRE( linkAdded == true ); + linkAdded = g->addLink( validator, "r", sinkB, "from" ); + REQUIRE( linkAdded == true ); auto input = g->getDataSetter( "s_to" ); auto output = g->getDataGetter( "r_from" ); @@ -418,13 +392,6 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { test.push_back( dis( gen ) ); } -#if 0 - std::cout << "Input values : \n\t"; - for ( auto ord : test ) { - std::cout << ord << ' '; - } - std::cout << '\n'; -#endif // No need to do this as mean operator source has a copy of a functor ReduceOperator::function_type m1 = MeanOperator(); inputR->setData( &m1 ); From ccfbffe89ea557f2fa9317069766332f18c0554e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 14 Oct 2022 16:11:06 +0200 Subject: [PATCH 060/239] [dataflow] improve logging and error management --- src/Dataflow/Core/DataflowGraph.cpp | 252 ++++-------------- src/Dataflow/Core/DataflowGraph.hpp | 5 +- src/Dataflow/Core/Node.cpp | 42 ++- src/Dataflow/Core/Node.hpp | 8 +- src/Dataflow/Core/Node.inl | 8 - src/Dataflow/Core/NodeFactory.cpp | 25 +- .../Core/Nodes/Functionals/BinaryOpNode.hpp | 3 +- .../Core/Nodes/Functionals/FilterNode.hpp | 2 +- .../Core/Nodes/Functionals/FilterNode.inl | 8 +- .../Core/Nodes/Functionals/ReduceNode.hpp | 2 +- .../Core/Nodes/Functionals/ReduceNode.inl | 7 +- .../Core/Nodes/Functionals/TransformNode.hpp | 2 +- .../Core/Nodes/Functionals/TransformNode.inl | 8 +- .../Core/Nodes/Private/SourcesNodeFactory.cpp | 2 +- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 4 +- src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 10 +- .../Core/Nodes/Sources/CoreDataSources.hpp | 6 +- .../Core/Nodes/Sources/FunctionSource.hpp | 4 +- .../Core/Nodes/Sources/FunctionSource.inl | 7 +- .../Nodes/Sources/SingleDataSourceNode.hpp | 4 +- .../Nodes/Sources/SingleDataSourceNode.inl | 15 +- src/Dataflow/Core/Port.inl | 24 +- 22 files changed, 109 insertions(+), 339 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 6fec67ec402..928d3270032 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -4,10 +4,14 @@ #include #include +#include + namespace Ra { namespace Dataflow { namespace Core { +using namespace Ra::Core::Utils; + DataflowGraph::DataflowGraph( const std::string& name ) : DataflowGraph( name, getTypename() ) {} DataflowGraph::DataflowGraph( const std::string& instanceName, const std::string& typeName ) : @@ -31,19 +35,7 @@ void DataflowGraph::init() { void DataflowGraph::execute() { if ( !m_ready ) { -#ifdef GRAPH_CALL_TRACE - std::cout << std::endl - << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": not ready to execute, recompile the graph." << std::endl; -#endif - if ( !compile() ) { -#ifdef GRAPH_CALL_TRACE - std::cout << std::endl - << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": unable tocompile the graph." << std::endl; -#endif - return; - } + if ( !compile() ) { return; } } std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { std::for_each( level.begin(), level.end(), []( auto node ) { node->execute(); } ); @@ -122,16 +114,13 @@ void DataflowGraph::toJsonInternal( nlohmann::json& data ) const { } bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { - m_loadStatus = true; std::ifstream file( jsonFilePath ); nlohmann::json j; file >> j; - fromJson( j ); - return m_loadStatus; + return fromJson( j ); } -void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { - +bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "graph" ) ) { // indicate that the graph must be recompiled after loading m_recompile = true; @@ -146,17 +135,13 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { auto factory = NodeFactoriesManager::getFactory( factoryName ); if ( factory ) { addFactory( factory ); } else { - std::cerr << "DataflowGraph::loadFromJson : Unable to find a factory with name " - << factoryName << std::endl; - m_loadStatus = false; - return; + LOG( logERROR ) + << "DataflowGraph::loadFromJson : Unable to find a factory with name " + << factoryName; + return false; } } } - if ( !m_factories ) { - std::cerr << "DataflowGraph::loadFromJson : no node factories available !"; - return; - } std::unordered_map nodeById; auto nodes = data["graph"]["nodes"]; @@ -167,8 +152,8 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { auto newNode = m_factories->createNode( name, n, this ); if ( newNode ) { nodeById.emplace( id, newNode ); } else { - std::cerr << "Unable to create the node " << name << std::endl; - m_loadStatus = false; + LOG( logERROR ) << "Unable to create the node " << name; + return false; } } @@ -187,19 +172,19 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { fromOutput = nodeFrom->getOutputs()[fromIndex]->getName(); } else { - m_loadStatus = false; - std::cerr << "Error when reading JSON file \"" - << "\": Output index " << fromIndex << " for node \"" - << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() - << ")\" must be between 0 and " << nodeFrom->getOutputs().size() - 1 - << ". Link not added." << std::endl; + LOG( logERROR ) << "Error when reading JSON file \"" + << "\": Output index " << fromIndex << " for node \"" + << nodeFrom->getInstanceName() << " (" + << nodeFrom->getTypeName() << ")\" must be between 0 and " + << nodeFrom->getOutputs().size() - 1 << ". Link not added."; + return false; } } else { - m_loadStatus = false; - std::cerr << "Error when reading JSON file \"" - << "\": Could not find a node associated with id " << l["out_id"] - << ". Link not added." << std::endl; + LOG( logERROR ) << "Error when reading JSON file \"" + << "\": Could not find a node associated with id " << l["out_id"] + << ". Link not added."; + return false; } if ( nodeById.find( l["in_id"] ) != nodeById.end() ) { @@ -210,42 +195,38 @@ void DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { toInput = nodeTo->getInputs()[toIndex]->getName(); } else { - m_loadStatus = false; - std::cerr << "Error when reading JSON file \"" - << "\": Input index " << toIndex << " for node \"" - << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() - << ")\" must be between 0 and " << nodeTo->getInputs().size() - 1 - << ". Link not added." << std::endl; + LOG( logERROR ) << "Error when reading JSON file \"" + << "\": Input index " << toIndex << " for node \"" + << nodeFrom->getInstanceName() << " (" + << nodeFrom->getTypeName() << ")\" must be between 0 and " + << nodeTo->getInputs().size() - 1 << ". Link not added."; + return false; } } else { - m_loadStatus = false; - std::cerr << "Error when reading JSON file \"" - << "\": Could not find a node associated with id " << l["in_id"] - << ". Link not added." << std::endl; + LOG( logERROR ) << "Error when reading JSON file \"" + << "\": Could not find a node associated with id " << l["in_id"] + << ". Link not added."; + return false; } if ( nodeFrom && ( fromOutput != "" ) && nodeTo && ( toInput != "" ) ) { addLink( nodeFrom, fromOutput, nodeTo, toInput ); } else { - m_loadStatus = false; - std::cerr + LOG( logERROR ) << "Error when reading JSON file \"" << "\": Could not add a link (missing or wrong information, please refer to " - "the previous error messages). Link not added." - << std::endl; + "the previous error messages). Link not added."; + return false; } } } + return true; } bool DataflowGraph::addNode( Node* newNode ) { std::map m_mapInputs; -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": trying to add node \"" - << newNode->getInstanceName() << "\"..." << std::endl; -#endif // Check if the new node already exists (= same name and type) if ( findNode( newNode ) == -1 ) { if ( newNode->getInputs().empty() ) { @@ -263,45 +244,19 @@ bool DataflowGraph::addNode( Node* newNode ) { } } m_nodes.emplace_back( newNode ); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": success adding node \"" << newNode->getInstanceName() << "\"!" - << std::endl; -#endif m_ready = false; return true; } else { -#ifdef GRAPH_CALL_TRACE - std::cerr << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": could not add node \"" << newNode->getInstanceName() - << "\" (node already exists)." << std::endl; -#endif return false; } } bool DataflowGraph::removeNode( Node* node ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": trying to remove node \"" << node->getInstanceName() << "\"..." << std::endl; -#endif // Check if the new node already exists (= same name) int index = -1; - if ( ( index = findNode( node ) ) == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": could not remove node \"" << node->getInstanceName() - << "\" (node does not exist)." << std::endl; -#endif - return false; - } + if ( ( index = findNode( node ) ) == -1 ) { return false; } else { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": success removing node \"" << node->getInstanceName() << "\"!" - << std::endl; -#endif if ( node->getInputs().empty() ) { // Check if it is a source node for ( auto& port : node->getInterfaces() ) { // Erase input ports of the graph associated @@ -337,29 +292,15 @@ bool DataflowGraph::addLink( Node* nodeFrom, const std::string& nodeFromOutputName, Node* nodeTo, const std::string& nodeToInputName ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": ADD LINK : try to connect output \"" + nodeFromOutputName + "\" of node \"" + - nodeFrom->getInstanceName() + "\" to input \"" + nodeToInputName + - "\" of node \"" + nodeTo->getInstanceName() + "\"." - << std::endl; -#endif // Check node "from" existence in the graph if ( findNode( nodeFrom ) == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : node \"from\" \"" + nodeFrom->getInstanceName() + - "\" does not exist." - << std::endl; -#endif + LOG( logERROR ) << "DataflowGraph::addLink Unable to find initial node."; return false; } // Check node "to" existence in the graph if ( findNode( nodeTo ) == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : node \"to\" \"" + nodeTo->getInstanceName() + "\" does not exist." - << std::endl; -#endif + LOG( logERROR ) << "DataflowGraph::addLink Unable to find destination node."; return false; } @@ -374,11 +315,9 @@ bool DataflowGraph::addLink( Node* nodeFrom, index++; } if ( foundFrom == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : output \"" + nodeFromOutputName + "\" for node \"from\" \"" + - nodeFrom->getInstanceName() + "\" does not exist." - << std::endl; -#endif + LOG( logERROR ) << "DataflowGraph::addLink Unable to find output port " + << nodeFromOutputName << " from initial node " + << nodeFrom->getInstanceName(); return false; } @@ -393,60 +332,24 @@ bool DataflowGraph::addLink( Node* nodeFrom, index++; } if ( foundTo == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : input \"" + nodeToInputName + "\" for target node \"" + - nodeTo->getInstanceName() + "\" does not exist." - << std::endl; -#endif + LOG( logERROR ) << "DataflowGraph::addLink Unable to find input port " << nodeFromOutputName + << " from destination node " << nodeTo->getInstanceName(); return false; } // Compare types // TODO fix the variable naming ... if ( nodeTo->getInputs()[foundTo]->getType() != nodeFrom->getOutputs()[foundFrom]->getType() ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + - nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + - "\" for node \"" + nodeFrom->getInstanceName() + - "\" : type mismatch : \n\t" - "Type to : " - << nodeTo->getInputs()[foundTo]->getTypeName() - << "\n\t" - "Type from : " - << nodeFrom->getOutputs()[foundFrom]->getTypeName() << "\n\t" << std::endl; -#endif return false; } // Check if input is connected - if ( nodeTo->getInputs()[foundTo]->isLinked() ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + - nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + - "\" for node \"" + nodeFrom->getInstanceName() + - "\" : input already connected." - << std::endl; -#endif - return false; - } + if ( nodeTo->getInputs()[foundTo]->isLinked() ) { return false; } // Try to connect ports if ( !nodeTo->getInputs()[foundTo]->connect( nodeFrom->getOutputs()[foundFrom].get() ) ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "ADD LINK : cannot connect output \"" + nodeFromOutputName + "\" for node \"" + - nodeTo->getInstanceName() + "\" and input \"" + nodeToInputName + - "\" for node \"" + nodeFrom->getInstanceName() + "\"." - << std::endl; -#endif return false; } -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": ADD LINK : success connecting output \"" + nodeFromOutputName + - "\" of node \"" + nodeFrom->getInstanceName() + "\" to input \"" + - nodeToInputName + "\" of node \"" + nodeTo->getInstanceName() + "\"." - << std::endl; -#endif // The state of the graph changes, set it to not ready m_ready = false; return true; @@ -454,13 +357,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { // Check node's existence in the graph - if ( findNode( node ) == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "REMOVE LINK : node \"" + node->getInstanceName() + "\" does not exist." - << std::endl; -#endif - return false; - } + if ( findNode( node ) == -1 ) { return false; } // Check if node's input exists int found = -1; @@ -472,22 +369,9 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { } index++; } - if ( found == -1 ) { -#ifdef GRAPH_CALL_TRACE - std::cerr << "REMOVE LINK : input \"" + nodeInputName + "\" for target node \"" + - node->getInstanceName() + "\" does not exist." - << std::endl; -#endif - return false; - } + if ( found == -1 ) { return false; } node->getInputs()[found]->disconnect(); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": REMOVE LINK : success disconnecting input \"" + nodeInputName + - "\" of node \"" + node->getInstanceName() + "\"." - << std::endl; -#endif return true; } @@ -500,11 +384,6 @@ int DataflowGraph::findNode( const Node* node ) { } bool DataflowGraph::compile() { -#ifdef GRAPH_CALL_TRACE - std::cout << std::endl - << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": begin compilation." - << std::endl; -#endif // Find useful nodes (directly or indirectly connected to a Sink) std::unordered_map>> infoNodes; for ( auto const& n : m_nodes ) { @@ -522,11 +401,6 @@ bool DataflowGraph::compile() { } } } -#ifdef GRAPH_CALL_TRACE - std::cout << std::endl - << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": useful nodes found." - << std::endl; -#endif // Compute the level (rank of execution) of useful nodes int maxLevel = 0; for ( auto& infNode : infoNodes ) { @@ -544,11 +418,6 @@ bool DataflowGraph::compile() { } } } -#ifdef GRAPH_CALL_TRACE - std::cout << std::endl - << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": nodes level computed." - << std::endl; -#endif m_nodesByLevel.clear(); m_nodesByLevel.resize( infoNodes.size() != 0 ? maxLevel + 1 : 0 ); for ( auto& infNode : infoNodes ) { @@ -564,22 +433,11 @@ bool DataflowGraph::compile() { for ( size_t k = 0; k < lvl[j]->getInputs().size(); k++ ) { if ( lvl[j]->getInputs()[k]->isLinkMandatory() && !lvl[j]->getInputs()[k]->isLinked() ) { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName - << "\": compilation failed, node " << lvl[j]->getInstanceName() - << " has mandatory port " << lvl[j]->getInputs()[k]->getName() - << " not linked." << std::endl; -#endif return m_ready = false; } } } } -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[32m\e[1mDataflowGraph\e[0m \"" << m_instanceName << "\": end compilation." - << std::endl - << std::endl; -#endif m_recompile = false; m_ready = true; init(); @@ -697,11 +555,6 @@ std::shared_ptr DataflowGraph::getDataSetter( std::string portName ) { p->connect( in ); return p; } -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[36m\e[1mDataflowGraph::graphGetInput \e[0m \"" - << "Error, can't generate the interface node for graph input " << portName - << std::endl; -#endif return nullptr; } @@ -721,11 +574,6 @@ PortBase* DataflowGraph::getDataGetter( std::string portName ) { for ( auto& portOut : m_outputs ) { if ( portOut->getName() == portName ) { return portOut.get(); } } -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[36m\e[1mDataflowGraph::graphGetOutput \e[0m \"" - << "Error, can't generate the interface node for graph output " << portName - << std::endl; -#endif return nullptr; } @@ -742,8 +590,8 @@ Node* DataflowGraph::getNode( const std::string& instanceNameNode ) { for ( const auto& node : m_nodes ) { if ( node->getInstanceName() == instanceNameNode ) { return node.get(); } } - std::cerr << getTypename() + ": The node with the instance name " + instanceNameNode + - " has not been found"; + LOG( logERROR ) << "DataflowGraph::getNode : The node with the instance name " + << instanceNameNode << " has not been found"; return nullptr; } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index bf5f624fcdc..8c545ff63d0 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -150,7 +150,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node */ DataflowGraph( const std::string& instanceName, const std::string& typeName ); - void fromJsonInternal( const nlohmann::json& ) override; + bool fromJsonInternal( const nlohmann::json& data ) override; void toJsonInternal( nlohmann::json& ) const override; private: @@ -193,9 +193,6 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \param out The port to add. bool addGetter( PortBase* out ); - /// Loding status. Set tu true when starting loading, set to false if some nodes can't be laoded - bool m_loadStatus { true }; - public: static const std::string& getTypename(); }; diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index 62d4665abbb..2905fc1ed76 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -28,23 +28,7 @@ void Node::generateUuid() { } /// Gets the UUID of the node as a string std::string Node::getUuid() const { - if ( m_uuid.is_nil() ) { - // generates the uuid (need to remove const attribute) ... - const_cast( this )->generateUuid(); - } - std::string struuid = "{" + uuids::to_string( m_uuid ) + "}"; - return struuid; -} - -bool Node::setUuid( const std::string& uid ) { - if ( m_uuid.is_nil() ) { - auto id = uuids::uuid::from_string( uid ); - if ( id ) { - m_uuid = id.value(); - return true; - } - } - return false; + return std::string { "{" } + uuids::to_string( m_uuid ) + "}"; } Node::Node( const std::string& instanceName, const std::string& typeName ) : @@ -52,25 +36,33 @@ Node::Node( const std::string& instanceName, const std::string& typeName ) : generateUuid(); } -void Node::fromJson( const nlohmann::json& data ) { +bool Node::fromJson( const nlohmann::json& data ) { + if ( data.contains( "model" ) ) { + if ( data["model"].contains( "instance" ) ) { m_instanceName = data["model"]["instance"]; } + } + else { + LOG( logERROR ) << "Missing required model when loading a Dataflow::Node"; + return false; + } // get the common content of the Node from the json data if ( data.contains( "id" ) ) { std::string struuid = data["id"]; m_uuid = uuids::uuid::from_string( struuid ).value(); } else { - generateUuid(); - } - if ( data.contains( "model" ) ) { - if ( data["model"].contains( "instance" ) ) { m_instanceName = data["model"]["instance"]; } - // get the specific concrete node informations - const auto& datamodel = data["model"]; - fromJsonInternal( datamodel ); + LOG( logERROR ) << "Missing required uuid when loading node " << m_instanceName; + return false; } + + // get the specific concrete node information + const auto& datamodel = data["model"]; + auto loaded = fromJsonInternal( datamodel ); + // get the supplemental informations related to application/gui/... for ( auto& [key, value] : data.items() ) { if ( key != "id" && key != "model" ) { m_extraJsonData.emplace( key, value ); } } + return loaded; } void Node::toJson( nlohmann::json& data ) const { diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 64411338611..36e1073b00c 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -106,10 +106,6 @@ class RA_DATAFLOW_API Node /// \brief Gets the UUID of the node as a string std::string getUuid() const; - /// \brief Sets the UUID of the node from a valid uuid string - /// \return true if the uuid is set, false if the node already have a valid uid or the string is - /// invalid - bool setUuid( const std::string& uid ); /// @} /// \name Serialization of a node @@ -122,7 +118,7 @@ class RA_DATAFLOW_API Node /// \brief unserialized the content of the node. /// Fill the node from its json representation - void fromJson( const nlohmann::json& data ); + bool fromJson( const nlohmann::json& data ); /// \brief Add a metadata to the node to store application specific information. /// used, e.g. by the node editor gui to save node position in the graphical canvas. @@ -148,7 +144,7 @@ class RA_DATAFLOW_API Node /// Must be implemented by inheriting classes. /// Be careful with template specialization and function member overriding when implementing /// this method. - virtual void fromJsonInternal( const nlohmann::json& ) = 0; + virtual bool fromJsonInternal( const nlohmann::json& ) = 0; /// internal json representation of the Node. /// Must be implemented by inheriting classes. diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index b4da91f6298..87a8cd5dd31 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -8,18 +8,10 @@ namespace Dataflow { namespace Core { inline void Node::init() { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName - << "\": initialization." << std::endl; -#endif m_initialized = true; } inline void Node::destroy() { -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[34m\e[1m" << getTypeName() << "\e[0m \"" << m_instanceName << "\": destroy." - << std::endl; -#endif m_interface.clear(); m_inputs.clear(); m_outputs.clear(); diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index 5be1f667b26..f8c3cc6444f 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -27,15 +27,6 @@ Node* NodeFactory::createNode( std::string& nodeType, if ( owningGraph != nullptr ) { owningGraph->addNode( node ); } return node; } -#if 0 - else { - std::cerr << "NodeFactory: no defined node for type " << nodeType << "." << std::endl; - std::cerr << "Available nodes are : " << std::endl; - for ( const auto& e : m_nodesCreators ) { - std::cerr << "\t" << e.first << std::endl; - } - } -#endif return nullptr; } @@ -47,11 +38,10 @@ bool NodeFactory::registerNodeCreator( std::string nodeType, m_nodesCreators[nodeType] = { std::move( nodeCreator ), nodeCategory }; return true; } - else { - std::cerr << "NodeFactory: trying to add an already existing node creator for type " - << nodeType << "." << std::endl; - return false; - } + LOG( Ra::Core::Utils::logWARNING ) + << "NodeFactory: trying to add an already existing node creator for type " << nodeType + << "."; + return false; } size_t NodeFactory::nextNodeId() { @@ -70,11 +60,8 @@ Node* NodeFactorySet::createNode( std::string& nodeType, auto node = it.second->createNode( nodeType, data, owningGraph ); if ( node ) { return node; } } - std::cerr << "NodeFactorySet: unable to find constructor for " << nodeType - << " in any of the following factories :" << std::endl; - for ( const auto& it : m_factories ) { - std::cerr << "\t" << it.first << std::endl; - } + LOG( Ra::Core::Utils::logERROR ) << "NodeFactorySet: unable to find constructor for " + << nodeType << " in any managed factory."; return nullptr; } diff --git a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp index 4d15563d8a1..373e06c9421 100644 --- a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp @@ -216,9 +216,10 @@ class BinaryOpNode : public Node << "Unable to save data when serializing a " << getTypeName() << "."; } - void fromJsonInternal( const nlohmann::json& ) override { + bool fromJsonInternal( const nlohmann::json& ) override { LOG( Ra::Core::Utils::logDEBUG ) << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; } private: diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index c4233b3dc26..89cece758fd 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -59,7 +59,7 @@ class FilterNode : public Node UnaryPredicate predicate ); void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; + bool fromJsonInternal( const nlohmann::json& ) override; private: UnaryPredicate m_predicate; diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl index 8524d2eff85..3799b5150ff 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl @@ -37,11 +37,6 @@ void FilterNode::execute() { // m_elements.reserve( inData.size() ); // --> this is not a requirement of // SequenceContainer std::copy_if( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[36m\e[1mFilterNode \e[0m \"" << m_instanceName << "\": execute, from " - << input->getData().size() << " to " << m_elements.size() << " " - << typeid( T ).name() << "." << std::endl; -#endif } } @@ -73,9 +68,10 @@ void FilterNode::toJsonInternal( nlohmann::json& data ) const { } template -void FilterNode::fromJsonInternal( const nlohmann::json& ) { +bool FilterNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; } } // namespace Functionals diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp index 1a8d61be862..d881133dacd 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp @@ -65,7 +65,7 @@ class ReduceNode : public Node v_t initialValue ); void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; + bool fromJsonInternal( const nlohmann::json& ) override; private: ReduceOperator m_operator; diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl index 2f60ad24f3b..e37e15aa51b 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl @@ -41,10 +41,6 @@ void ReduceNode::execute() { if ( m_portIn->isLinked() ) { const auto& inData = m_portIn->getData(); m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[36m\e[1mMReduceNode \e[0m \"" << m_instanceName << "\": execute, from " - << input->getData().size() << " " << getTypename() << "." << std::endl; -#endif } } @@ -78,9 +74,10 @@ void ReduceNode::toJsonInternal( nlohmann::json& data ) const { } template -void ReduceNode::fromJsonInternal( const nlohmann::json& ) { +bool ReduceNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; } } // namespace Functionals diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp index f8926bd071e..094ed2bbf06 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp @@ -56,7 +56,7 @@ class TransformNode : public Node TransformOperator op ); void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; + bool fromJsonInternal( const nlohmann::json& ) override; private: TransformOperator m_operator; diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl index 4e81f27b2e6..2a75f07b843 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl @@ -35,11 +35,6 @@ void TransformNode::execute() { // m_elements.reserve( inData.size() ); // --> this is not a requirement of // SequenceContainer std::transform( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[36m\e[1mMapNode \e[0m \"" << m_instanceName << "\": execute, from " - << input->getData().size() << " to " << m_elements.size() << " " - << typeid( T ).name() << "." << std::endl; -#endif } } @@ -70,9 +65,10 @@ void TransformNode::toJsonInternal( nlohmann::json& data ) const { } template -void TransformNode::fromJsonInternal( const nlohmann::json& ) { +bool TransformNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; } } // namespace Functionals diff --git a/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp b/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp index a0b904d0568..ab9e2d50611 100644 --- a/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp +++ b/src/Dataflow/Core/Nodes/Private/SourcesNodeFactory.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace Ra { diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index f472e2b72ed..2d70bc2013e 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -34,9 +34,11 @@ class SinkNode : public Node protected: void toJsonInternal( nlohmann::json& data ) const override; - void fromJsonInternal( const nlohmann::json& data ) override; + bool fromJsonInternal( const nlohmann::json& data ) override; private: + /// \todo : allow user to specify where to store the data ? (i.e. make this a shared_ptr ?). + // If yes, add a method setDataStorage(std::shared_ptr v) T m_data; /// @{ diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index aeffc73b69e..51a765264af 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -27,10 +27,6 @@ void SinkNode::init() { template void SinkNode::execute() { m_data = m_portIn->getData(); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[33m\e[1m" << getTypename() << "\e[0m \"" << getInstanceName() << "\": execute." - << std::endl; -#endif } template @@ -51,10 +47,12 @@ const std::string& SinkNode::getTypename() { } template -void SinkNode::toJsonInternal( nlohmann::json& /*data*/ ) const {} +void SinkNode::toJsonInternal( nlohmann::json& ) const {} template -void SinkNode::fromJsonInternal( const nlohmann::json& /*data*/ ) {} +bool SinkNode::fromJsonInternal( const nlohmann::json& ) { + return true; +} } // namespace Sinks } // namespace Core diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 73564e49971..4e345b83d83 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -67,11 +67,12 @@ DECLARE_COREDATA_SOURCES( Vector4ui, Vector4ui ); } \ \ template <> \ - inline void SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { \ + inline bool SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { \ if ( data.contains( #NAME ) ) { \ TYPE v = data[#NAME]; \ setData( v ); \ } \ + return true; \ } SPECIALIZE_EDITABLE_SOURCE( bool, boolean ); @@ -95,7 +96,7 @@ SingleDataSourceNode::toJsonInternal( nlohmann::json& da } template <> -inline void +inline bool SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "color" ) ) { std::array c = data["color"]; @@ -103,6 +104,7 @@ SingleDataSourceNode::fromJsonInternal( const nlohmann:: Ra::Core::Utils::Color::sRGBToLinearRGB( Ra::Core::Utils::Color( c[0], c[1], c[2] ) ); setData( v ); } + return true; } #undef SPECIALIZE_EDITABLE_SOURCE diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index f23169787e5..c7cfc9b63ba 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -41,8 +41,8 @@ class FunctionSourceNode : public Node protected: FunctionSourceNode( const std::string& instanceName, const std::string& typeName ); - void fromJsonInternal( const nlohmann::json& ) override; - void toJsonInternal( nlohmann::json& ) const override; + bool fromJsonInternal( const nlohmann::json& ) override; + void toJsonInternal( nlohmann::json& data ) const override; /// @{ /// The data provided by the node diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl index 558fde3313e..05791dfd494 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl @@ -22,10 +22,6 @@ void FunctionSourceNode::execute() { m_data = &m_localData; } m_portOut->setData( m_data ); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[34m\e[1mFunctionSourceNode\e[0m \"" << m_instanceName << "\": execute." - << std::endl; -#endif } template @@ -58,9 +54,10 @@ void FunctionSourceNode::toJsonInternal( nlohmann::json& data ) cons } template -void FunctionSourceNode::fromJsonInternal( const nlohmann::json& ) { +bool FunctionSourceNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logDEBUG ) << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; } } // namespace Sources diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index 15bf9afca59..2356e82258d 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -59,8 +59,8 @@ class SingleDataSourceNode : public Node void removeEditable( const std::string& name = "Data" ); protected: - void fromJsonInternal( const nlohmann::json& ) override; - void toJsonInternal( nlohmann::json& ) const override; + bool fromJsonInternal( const nlohmann::json& ) override; + void toJsonInternal( nlohmann::json& data ) const override; /// @{ /// The data provided by the node diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index 5e956e0c556..e97a97945ee 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -27,29 +27,17 @@ void SingleDataSourceNode::execute() { m_data = &m_localData; } m_portOut->setData( m_data ); -#ifdef GRAPH_CALL_TRACE - std::cout << "\e[34m\e[1mSingleDataSourceNode\e[0m \"" << m_instanceName << "\": execute." - << std::endl; -#endif } template void SingleDataSourceNode::setData( T* data ) { /// \warning this will copy data into local storage m_localData = *data; -#if 0 - m_data = &m_localData; - m_portOut->setData( m_data ); -#endif } template void SingleDataSourceNode::setData( T& data ) { m_localData = data; -#if 0 - m_data = &m_localData; - m_portOut->setData( m_data ); -#endif } template @@ -84,9 +72,10 @@ void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { } template -void SingleDataSourceNode::fromJsonInternal( const nlohmann::json& ) { +bool SingleDataSourceNode::fromJsonInternal( const nlohmann::json& ) { LOG( Ra::Core::Utils::logDEBUG ) << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; } } // namespace Sources diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl index 2e1fe3f4254..4460cbd5650 100644 --- a/src/Dataflow/Core/Port.inl +++ b/src/Dataflow/Core/Port.inl @@ -97,16 +97,6 @@ void PortBase::setData( T* data ) { return; } } -#ifdef GRAPH_CALL_TRACE - if ( is_input() ) { - std::cout << "\e[41m\e[1mError, can't set data on the input port " << this->getName() - << "\e[0m" << std::endl; - } - else { - std::cout << "\e[41m\e[1mError, can't set data on the port of incompatible data type." - << this->getName() << "\e[0m" << std::endl; - } -#endif } template @@ -115,17 +105,6 @@ void PortBase::getData( T& t ) { auto thisOut = dynamic_cast*>( this ); if ( thisOut ) { t = thisOut->getData(); } } -#ifdef GRAPH_CALL_TRACE - if ( is_input() ) { - std::cout << "\e[41m\e[1mError, can't get data on the input port " << this->getName() - << "\e[0m" << std::endl; - } - else { - std::cout << "\e[41m\e[1mError, can't get data on the port of incompatible data type " - << this->getName() << " (" << this->getTypeName() << " vs " - << RadiumAddons::Core::Utils::demang() << ")\e[0m" << std::endl; - } -#endif } template @@ -134,7 +113,8 @@ T& PortBase::getData() { auto thisOut = dynamic_cast*>( this ); if ( thisOut ) { return thisOut->getData(); } } - std::cerr << "Could not call T& PortBase::getData() on an input port !!!\n"; + LOG( Ra::Core::Utils::logERROR ) + << "Could not call T& PortBase::getData() on an input port !!!\n"; std::abort(); } From 348cf8aceddce6ba9315f2468b01d29240ec2d03 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 14 Oct 2022 16:11:34 +0200 Subject: [PATCH 061/239] [tests] improve logging and error management --- tests/unittest/Dataflow/customnodes.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 7220b7e0325..35598eabaef 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -69,20 +69,21 @@ class FilterSelector final : public Node T getThreshold() const { return m_threshold; } protected: - void fromJsonInternal( const nlohmann::json& jsonData ) override { - if ( jsonData.contains( "operator" ) ) { m_operatorName = jsonData["operator"]; } + bool fromJsonInternal( const nlohmann::json& data ) override { + if ( data.contains( "operator" ) ) { m_operatorName = data["operator"]; } else { m_operatorName = "true"; } - if ( jsonData.contains( "threshold" ) ) { m_threshold = jsonData["threshold"]; } + if ( data.contains( "threshold" ) ) { m_threshold = data["threshold"]; } else { m_threshold = T {}; } + return true; } - void toJsonInternal( nlohmann::json& jsonData ) const override { - jsonData["operator"] = m_operatorName; - jsonData["threshold"] = m_threshold; + void toJsonInternal( nlohmann::json& data ) const override { + data["operator"] = m_operatorName; + data["threshold"] = m_threshold; } public: From 9c1f45e8fa3f639a21f470f886dad632aa550064 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 14 Oct 2022 17:14:23 +0200 Subject: [PATCH 062/239] [examples] fix snippet comments in HelloGraph --- examples/DataflowExamples/HelloGraph/main.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index e655bb13001..cca1e1e9137 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -89,7 +89,9 @@ int main( int argc, char* argv[] ) { //! [Execute the graph] g.execute(); - // The reference to the result is now available + // The reference to the result is now available. Results can be accessed through a reference + // (sort of RVO from the graph https://en.cppreference.com/w/cpp/language/copy_elision) + // or copied in an application variable. auto& result = output->getData>(); //! [Execute the graph] @@ -116,7 +118,7 @@ int main( int argc, char* argv[] ) { g.releaseDataSetter( "Source_to" ); g.execute(); std::cout << "Output values after third execution: " << result.size() << "\n"; - //! [Disconnect data setter and rerun the graph] + //! [Disconnect data setter and rerun the graph - result is now empty] //! [As interface is disconnected, we can set data direclty on the source node] sourceNode->setData( &test ); @@ -126,7 +128,7 @@ int main( int argc, char* argv[] ) { std::cout << ord << ' '; } std::cout << '\n'; - //! [Disconnect data setter and rerun the graph] + //! [As interface is disconnected, we can set data direclty on the source node] //! [Reconnect data setter and rerun the graph - result is the same than second execution] g.activateDataSetter( "Source_to" ); @@ -136,6 +138,6 @@ int main( int argc, char* argv[] ) { std::cout << ord << ' '; } std::cout << '\n'; - //! [Disconnect data setter and rerun the graph] + //! [Reconnect data setter and rerun the graph - result is the same than second execution] return 0; } From e24fe4fe315e2fed13220896c29c54b97cbe1b60 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 20 Oct 2022 16:58:34 +0200 Subject: [PATCH 063/239] [dataflow-core] improve class and namespace documentation --- src/Dataflow/Core/DataflowGraph.hpp | 33 ++++++++++++++-------- src/Dataflow/Core/Node.hpp | 16 ++++++----- src/Dataflow/Core/Node.inl | 3 +- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 4 +++ src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 2 ++ 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 8c545ff63d0..de02186f25b 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -112,22 +112,23 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Flag set after rendergraph compilation checking if its state is right bool m_ready { false }; - /// Creates an output port connected to the named input port of the graph. - /// Return the connected output port if success, transferring the ownership to the caller. + /// \brief Creates an output port connected to the named input port of the graph. + /// Return the connected output port if success, sharing the ownership with the caller. /// Allows to set data to the graph from the caller . - /// \note As ownership is transferred to the caller, the graph must survive the returned - /// pointer (but tested more robust than that). + /// \note As ownership is shared with the caller, the graph must survive the returned + /// pointer to be able to use the dataSetter.. /// @params portName The name of the input port of the graph - /// TODO : Thereis a bug ???? When listing the data setters, they are connected ... - /// TODO : setters (and getters) Should be created once and activated/deactivated ??? std::shared_ptr getDataSetter( std::string portName ); + /// \brief disconnect the data setting port from its inputs. bool releaseDataSetter( std::string portName ); + /// \brief connect the data setting port from its inputs. bool activateDataSetter( std::string portName ); /// Returns an alias to the named output port of the graph. /// Allows to get the data stored at this port after the execution of the graph. - /// \note ownership is left to the graph. + /// \note ownership is left to the graph. The graph must survive the returned + /// pointer to be able to use the dataGetter.. /// @params portName the name of the output port PortBase* getDataGetter( std::string portName ); @@ -138,7 +139,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// A tuple is composed of an output port connected to an input port of the graph, its name its /// type. \note If called multiple times for the same port, only the last returned result is /// usable. - /// TODO : Thereis a bug ???? When listing the data setters, they are connected ... + /// TODO : Verify why, when listing the data setters, they are connected ... std::vector getAllDataSetters(); /// Creates a vector that stores all the DataGetters (\see getDataGetter) of the graph. @@ -181,15 +182,23 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// @param name The name of the node to find. int findNode( const Node* node ); - /// Data setters management : used to pass parameter to the graph when the graph is not embeded + /// Data setters management : used to pass parameter to the graph when the graph is not embedded /// into another graph (inputs are here for this case). + /// A dataSetter is an outputPort, associated to an input port of the graph. + /// The connection between these ports can be activated/deactivated using + /// activateDataSetter/releaseDataSetter using DataSetter = std::pair; std::map m_dataSetters; + /// \brief Adds an input port to the graph and associate it with a dataSetter. + /// This port is aliased as an interface port in a source node of the graph. + /// This function checks if there is no input port with the same name already + /// associated with the graph. bool addSetter( PortBase* in ); - /// Adds an out port for a GRAPH. This port is also an interface port whose reference is stored - /// in the source and sink nodes of the graph. This function checks if there is no out port with - /// the same name already associated with the graph. + /// \brief Adds an out port for a Graph and register it as a dataGetter. + /// This port is aliased as an interface port in a sink node of the graph. + /// This function checks if there is no out port with the same name already + /// associated with the graph. /// \param out The port to add. bool addGetter( PortBase* out ); diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 36e1073b00c..0fcac2b8859 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -76,16 +76,18 @@ class RA_DATAFLOW_API Node /// \name Control the interfaces of the nodes (inputs, outputs, internal data, ...) /// @{ /// \brief Gets the in ports of the node. + /// Input ports are own to the node. const std::vector>& getInputs(); /// \brief Gets the out ports of the node. + /// Output ports are own to the node. const std::vector>& getOutputs(); /// \brief Build the interface ports of the node const std::vector& buildInterfaces( Node* parent ); /// \brief Get the interface ports of the node - const std::vector& getInterfaces(); + const std::vector& getInterfaces() const; /// \brief Gets the editable parameters of the node. /// used only by the node editor gui to build the editon widget @@ -183,19 +185,19 @@ class RA_DATAFLOW_API Node template EditableParameter* getEditableParameter( const std::string& name ); - /// The deletable status of the node - bool m_isDeletable { true }; - /// The type name of the node. Initialized once at construction std::string m_typeName; /// The instance name of the node std::string m_instanceName; - /// The in ports of the node + /// The in ports of the node (own by the node) std::vector> m_inputs; - /// The out ports of the node + /// The out ports of the node (own by the node) std::vector> m_outputs; - /// The reflected ports of the node if it is only a source or sink node + /// The reflected ports of the node if it is only a source or sink node. + /// This stores only aliases as interface ports will belong to the parent + /// node (i.e. the graph this node belongs to) std::vector m_interface; + /// The editable parameters of the node std::vector> m_editableParameters; diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl index 87a8cd5dd31..a70486f42bf 100644 --- a/src/Dataflow/Core/Node.inl +++ b/src/Dataflow/Core/Node.inl @@ -57,7 +57,8 @@ inline const std::vector& Node::buildInterfaces( Node* parent ) { } return m_interface; } -inline const std::vector& Node::getInterfaces() { + +inline const std::vector& Node::getInterfaces() const { return m_interface; } diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index 2d70bc2013e..8eb20cfaf97 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -6,6 +6,10 @@ namespace Dataflow { namespace Core { namespace Sinks { +/** + * \brief Base class for nodes that will store the result of a computation graph. + * @tparam T The type of the data to serve. + */ template class SinkNode : public Node { diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index 51a765264af..34c05577264 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -19,6 +19,8 @@ SinkNode::SinkNode( const std::string& instanceName, const std::string& typeN template void SinkNode::init() { // this should be done only once (or when the address of local data changes) + // What if interfaces were not created before init (e.g. call of init on a node not added to a + // graph) Todo : assert on the existence of the interface auto interfacePort = static_cast*>( m_interface[0] ); interfacePort->setData( &m_data ); Node::init(); From af2e855ddfd1659d23eb0c0c237b4808513de549 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 20 Oct 2022 16:59:00 +0200 Subject: [PATCH 064/239] [examples] fix snippet labels --- examples/DataflowExamples/HelloGraph/main.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index cca1e1e9137..834d554ff66 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -10,15 +10,16 @@ using namespace Ra::Dataflow::Core; * \brief Demonstrate how to use a graph to filter a collection */ int main( int argc, char* argv[] ) { + using RaVector = Ra::Core::VectorArray; //! [Creating an empty graph] DataflowGraph g { "helloGraph" }; //! [Creating an empty graph] //! [Creating Nodes] - auto sourceNode = new Sources::SingleDataSourceNode>( "Source" ); + auto sourceNode = new Sources::SingleDataSourceNode( "Source" ); auto predicateNode = new Sources::ScalarUnaryPredicateSource( "Selector" ); - auto filterNode = new Functionals::FilterNode>( "Filter" ); - auto sinkNode = new Sinks::SinkNode>( "Sink" ); + auto filterNode = new Functionals::FilterNode( "Filter" ); + auto sinkNode = new Sinks::SinkNode( "Sink" ); //! [Creating Nodes] //! [Adding Nodes to the graph] @@ -50,16 +51,18 @@ int main( int argc, char* argv[] ) { } //! [Inspect the graph interface : inputs and outputs port] - //! [Verifing the graph can be compiled] + //! [Verifying the graph can be compiled] if ( !g.compile() ) { std::cout << " compilation failed"; return 1; } - //! [Verifing the graph can be compiled] + //! [Verifying the graph can be compiled] - ///! [Configure the interface ports (input and output of the graph) + g.saveToJson( "illustrateDoc.json" ); + + ///! [Configure the interface ports (input and output of the graph)] auto input = g.getDataSetter( "Source_to" ); - std::vector test; + RaVector test; input->setData( &test ); auto selector = g.getDataSetter( "Selector_f" ); @@ -69,7 +72,7 @@ int main( int argc, char* argv[] ) { auto output = g.getDataGetter( "Sink_from" ); // The reference to the result will not be available before the first run // auto& result = output->getData>(); - ///! [Configure the interface ports (input and output of the graph) + ///! [Configure the interface ports (input and output of the graph)] //! [Initializing input variable to test the graph] test.reserve( 10 ); @@ -92,7 +95,7 @@ int main( int argc, char* argv[] ) { // The reference to the result is now available. Results can be accessed through a reference // (sort of RVO from the graph https://en.cppreference.com/w/cpp/language/copy_elision) // or copied in an application variable. - auto& result = output->getData>(); + auto& result = output->getData(); //! [Execute the graph] //! [Print the output result] From bbc0e4a534f27b6a11addf08df56d9f149f8893c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 20 Oct 2022 17:02:05 +0200 Subject: [PATCH 065/239] [doc] add first documentation about node system --- doc/CMakeLists.txt | 4 +- doc/concepts.md | 1 + doc/concepts/dataflow/HelloGraph.json | 76 ++++++++++ doc/concepts/nodesystem.md | 198 ++++++++++++++++++++++++++ doc/images/HelloGraph.png | Bin 0 -> 158928 bytes 5 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 doc/concepts/dataflow/HelloGraph.json create mode 100644 doc/concepts/nodesystem.md create mode 100644 doc/images/HelloGraph.png diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 0a2d6911052..a509e0c97bb 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -169,8 +169,8 @@ if(DOXYGEN_FOUND) doxygen_add_docs( RadiumDoc ${md_pages_order} ${md_files} ${CMAKE_CURRENT_SOURCE_DIR}/../src/ - ${CURRENT_SRC_DIR} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMENT "Generating API documentation with Doxygen" + ${CMAKE_CURRENT_SOURCE_DIR}/../tests/unittest/Dataflow ${CURRENT_SRC_DIR} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Generating API documentation with Doxygen" ) # Where docs will be installed. diff --git a/doc/concepts.md b/doc/concepts.md index 91f1f94b593..be7b462c156 100644 --- a/doc/concepts.md +++ b/doc/concepts.md @@ -5,3 +5,4 @@ * \subpage eventSystem * \subpage pluginSystem * \subpage forwardRenderer +* \subpage nodesystem diff --git a/doc/concepts/dataflow/HelloGraph.json b/doc/concepts/dataflow/HelloGraph.json new file mode 100644 index 00000000000..9290ca65ffd --- /dev/null +++ b/doc/concepts/dataflow/HelloGraph.json @@ -0,0 +1,76 @@ +{ + "id": "{c1c08892-9064-4484-b1f6-1c7da3bb56f8}", + "model": { + "graph": { + "connections": [ + { + "in_id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", + "in_index": 0, + "out_id": "{eef7a427-c725-42d1-adb3-29d5112597d6}", + "out_index": 0 + }, + { + "in_id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", + "in_index": 1, + "out_id": "{ef56ef16-569c-48e7-ae42-91e130088737}", + "out_index": 0 + }, + { + "in_id": "{41d4e5df-fbfe-482f-ab5e-10c7941ffd60}", + "in_index": 0, + "out_id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", + "out_index": 0 + } + ], + "factories": [], + "nodes": [ + { + "id": "{eef7a427-c725-42d1-adb3-29d5112597d6}", + "model": { + "instance": "Source", + "name": "Source>" + }, + "position": { + "x": -23.0, + "y": 88.0 + } + }, + { + "id": "{ef56ef16-569c-48e7-ae42-91e130088737}", + "model": { + "instance": "Selector", + "name": "Source>" + }, + "position": { + "x": -112.0, + "y": 239.0 + } + }, + { + "id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", + "model": { + "instance": "Filter", + "name": "Filter>" + }, + "position": { + "x": 310.0, + "y": 158.0 + } + }, + { + "id": "{41d4e5df-fbfe-482f-ab5e-10c7941ffd60}", + "model": { + "instance": "Sink", + "name": "Sink>" + }, + "position": { + "x": 609.0, + "y": 238.0 + } + } + ] + }, + "instance": "helloGraph", + "name": "Core DataflowGraph" + } +} diff --git a/doc/concepts/nodesystem.md b/doc/concepts/nodesystem.md new file mode 100644 index 00000000000..317ebc49a5f --- /dev/null +++ b/doc/concepts/nodesystem.md @@ -0,0 +1,198 @@ +\page nodesystem Radium node system +[TOC] + +# Radium node system + +Radium-Engine embed a node system allowing to develop computation graph using an adaptation of dataflow programming. +This documentation explain the concepts used in the node system and how to develop computation graph using the Core +node system and how to extend the Core node system to be used in specific Radium-Engine application or library. + +## Structure and usage of the Radium::Dataflow component + +When building the Radium-Engine libraries, the node system is available from the Radium::Dataflow component. +The availability of this component in the set of built/installed libraries is managed using the +`RADIUM_GENERATE_LIB_DATAFLOW` cmake option (set to `ON` by default) of the main Radium-Engine CMakeLists.txt. + +The Radium::Dataflow component is a header-only library with is linked against three sub-components : + +- **DataflowCore** library defining the Core node system and a set Core Nodes allowing to develop several computation + graph. +- **DataflowQtGui** library defining Qt based Gui elements to edit and interact with a computation graph. +- **DataflowRendering** library defining specific nodes to be used by a Graph-based renderer allowing to easily define + custom renderers. + +When defining the CMakeLists.txt configuration file for an application/library that will build upon the node system, +The RadiumDataflow component might be requested in several way : + +- `find_package(Radium REQUIRED COMPONENTS Dataflow)`. This will define the imported target `Radium::Dataflow` that +gives access to all the available sub-components at once but also through imported targets `Radium::DataflowCore`, +`Radium::DataflowQtGui` and `Radium::DataflowRendering`. +- `find_package(Radium REQUIRED COMPONENTS DataflowCore)`. This will define the imported target `Radium::DataflowCore` +only. +- `find_package(Radium REQUIRED COMPONENTS DataflowQtGui)`. This will define the imported target `Radium::DataflowQtGui` +only, with transitive dependencies on `Radium::DataflowCore`. +- `find_package(Radium REQUIRED COMPONENTS DataflowRendering)`. This will define the imported target +`Radium::DataflowRendering` only, with transitive dependencies on `Radium::DataflowCore`. + +The targets that depends on a Dataflow components should then be configured as any target and linked against the +requested dataflow component, by, e.g, adding the line +`target_link_libraries(target_name PUBLIC Radium::Dataflow)` (or the only needed subcomponent). + +## Concepts of the node system + +The node system allow to build computation graph that takes its input from some _data source_ and store their results +in some _data sink_ after applying several _functions_ on the data. + +Computation graphs can be serialized and un-serialized in json format. The serialization process is nevertheless limited +to serializable data stored on the node and it is of the responsability of the application to manage non serializable +data such as, e.g. anonymous functions (lambdas, functors, ...) dynamically defined by the application. + +The Radium node system relies on the following concepts + +- _Node_ (see Ra::Dataflow::Core::Node for reference manual) : a node represent a function that will be executed on +several strongly typed input data, defining the definition domain of the function, to produce some strongly typed +output data, defining the definition co-domain of the function. + + The input and output data are accessed through _ports_ allowing to connect nodes together to form a graph. + + The node profile is implicitly defined by its domain and co-domain. + + A node can be specialized to be a _data source_ node (empty domain) or a _data sink_ node (empty co-domain). + These specific nodes define the input and output of a complex _computation graph_ + +- _Port_ (see Ra::Dataflow::Core::PortBase for reference manual) : a port represent an element of the node +profile and allow to build the computation graph by linking ports together, implicitly defining _links_. + + A port gives access to a strongly typed data and, while implementing the general Ra::Dataflow::Core::PortBase +interface should be specialized to be either an input port (element of the definition domain of a node) through the +instantiation of the template Ra::Dataflow::Core::PortIn or to an output port (element of the definition +co-domain of a node) through the instantiation of the template Ra::Dataflow::Core::PortOut. + + When a node executes its function, it takes its parameter from its input ports and set the result on the output port. + + An output port can be connected to an input port of the same _DataType_ to build the computation graph. +- _Graph_ (see Ra::Dataflow::Core::DataflowGraph for reference manual) : a graph is a set of node connected through +their ports so that they define a direct acyclic graph (DAG) representing a complex function. The DAG represents +connections from some _data source_ nodes to some _data sink_ nodes through + + Once built by adding nodes and links, a graph should be _compiled_ so that the system verify its validity +(DAG, types, connections, ...). + + Once compiled, a graph can be _executed_. + The input data of the computation graph should be set on the available _data sources_ of the graph and the results +fetched from the _data sinks_. + + As a graph can be used as a node (a sub graph) in any other graph. When doing this, all _data sources_ and +_data sinks_ nodes are associated with _interface ports_ and these interface ports are added as _input_ or _output_ +ports on the graph so that links can be defined using these ports. + + _input_ and _output_ ports of a graph can also be accessed directly from the application using _data setters_ and +_data getters_ fetched from the graph. These _data setters_ and _data getters_ allows to use any graph without the +need to know explicitly their _data sources_ and _data sinks_ nor defining ports to be linked with the _input_ and +_output_ ports of the graph. + +- _Factories_ (see Ra::Dataflow::Core::NodeFactoriesManager for reference manual) : the serialization of a graph output +a set of json object describing the graph. If serialization is always possible, care must be taken for the system to +manage un-serilization of any nodes. + + When serializing a graph, the json representing a node contains the type (the name of the concrete C++ class) of the +node and several other properties of the node system. When un-serializing a graph, nodes will be automatically +instanced from their type. +The instantiation of a node is made using services offered by node factories and associated to the node type. So, in +order to be un-serializable, each node must register its type to a factory and each graph must refer to the factories used +to instantiate its node. + +## HelloGraph : your first program that uses the node system + +The example application examples/HelloGraph shows how to define a computation graph to apply filtering on a collection. +In this example, whose code is detailed below, the following graph is built and executed using different input data. + +![HelloGraph computation graph](images/HelloGraph.png) + +This graphs has two inputs, corresponding to the two **Source< ... >** nodes. These input will deliver to the +computation graph : + +- from a Ra::Dataflow::Core::Sources::SingleDataSourceNode, a vector of scalars, whose container type is +Ra::Core::VectorArray (abridged here as a RaVector) and value type is _Scalar_ (float in the default Radium-Engine +configuration), +- from a Ra::Dataflow::Core::Sources::FunctionSourceNode a predicate whose type is _std::function_ +which returns _true_ if its parameter is valid according to some decision process. + +These two _sources_ are linked to the input of a Ra::Dataflow::Core::Functionals::FilterNode, here represented by the +**Filter< ... >** node. This node select from its **in** input only the values validated by the predicate **f** and +built its output **out** with these values. + +The result of this filtering is linked to the graph output, corresponding to the Ra::Dataflow::Core::Sinks::SinkNode +**Sink< ... >**. + +Once the graph is built and compile, the HelloGraph application sent different input to the graph and print the result +of the computation. + +To develop such an application, the following should be done + +### 1. Building and inspecting the graph + +First, an object of type Ra::Dataflow::Core::DataflowGraph is instanced : +\snippet examples/DataflowExamples/HelloGraph/main.cpp Creating an empty graph + +Then, the nodes are instanced +\snippet examples/DataflowExamples/HelloGraph/main.cpp Creating Nodes + +and added to the graph +\snippet examples/DataflowExamples/HelloGraph/main.cpp Adding Nodes to the graph + +Links between ports are added to the graph, and if an error is detected, due to e.g. port type incompatiblitiy, it is +reported +\snippet examples/DataflowExamples/HelloGraph/main.cpp Creating links between Nodes + +Once the graph is built, it can be inspected using dedicated methods. +\snippet examples/DataflowExamples/HelloGraph/main.cpp Inspect the graph interface : inputs and outputs port + +For the graph constructed above, this prints on stdout + +```text +Input ports (2) are : + "Selector_f" accepting type function + "Source_to" accepting type RaVector +Output ports (1) are : + "Sink_from" generating type RaVector +``` + +### 2. Compiling the graph and getting input/output accessors + +In order to use the graph as a function actiing on its input, it should be first compiled by +\snippet examples/DataflowExamples/HelloGraph/main.cpp Verifying the graph can be compiled + +If the compilation success the accessors for the input data and the output result might be fetched from the graph +\snippet examples/DataflowExamples/HelloGraph/main.cpp Configure the interface ports (input and output of the graph) + +Here, the accessor `input` allows to set the pointer on the `RaVector` to be processed while the accessor `selector` +allows to set the predicate to evaluate when filtering the collection. This predicates select values les than `0.5` + +The accessor `output` will allow, once the graph is executed, to get a reference to or to copy the resulting values. + +### 3. Executing the graph + +Once the input data are available (in this example, the `test` vector is filled with 10 random values between 0 and 1), +the graph can be executed and a reference to the resulting vector can be fetched using +\snippet examples/DataflowExamples/HelloGraph/main.cpp Execute the graph + +### 4. Multiple run of the graph on different input + +As accessors use pointers and references on the data sent to / fetched from the graph, the HellowGraph example shows how +to change the input using different available means so that every run of the graph process different values. +See the file examples/DataflowExamples/HelloGraph/main.cpp for all the details. + +## Examples of graphs and of programming custom nodes + +The unittests developed alongside the Radium::Dataflow component, and located in the directory +`tests/unitttest/Dataflow/` of the Radium-Engine source tree, can be used to learn the following : + +- sourcesandsinks.cpp : demonstrate the default supported types for sources and sinks node. +- nodes.cpp : demonstrate the development of a more complex graph implementing transform/reduce +on several collections using different reduction operators. +- graphinspect.cpp : demonstrate the way a graph can be inspected to discover its structure. +- serialization.cpp : demonstrate how to save/load a graph from a json file and use it in an +application. +- customnodes.cpp : demonstrate how it is simple to develop your own node type (in C++) and +use your nodes alongside standard nodes. diff --git a/doc/images/HelloGraph.png b/doc/images/HelloGraph.png new file mode 100644 index 0000000000000000000000000000000000000000..e0df369af27ae5ceff32124d3d06151976b82a1b GIT binary patch literal 158928 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
    HK0a(r8P^GIQc=_tar(om}VTtK6#*pg;y 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 literal 0 HcmV?d00001 From 6f6f78c0625a12544a3b32c75bef49bbf4d35afd Mon Sep 17 00:00:00 2001 From: dlyr Date: Tue, 25 Oct 2022 09:16:51 +0200 Subject: [PATCH 066/239] [doc] node system typos. --- doc/concepts.md | 2 +- doc/concepts/nodesystem.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/concepts.md b/doc/concepts.md index be7b462c156..1dcfefea3b1 100644 --- a/doc/concepts.md +++ b/doc/concepts.md @@ -5,4 +5,4 @@ * \subpage eventSystem * \subpage pluginSystem * \subpage forwardRenderer -* \subpage nodesystem +* \subpage nodeSystem diff --git a/doc/concepts/nodesystem.md b/doc/concepts/nodesystem.md index 317ebc49a5f..246643bc632 100644 --- a/doc/concepts/nodesystem.md +++ b/doc/concepts/nodesystem.md @@ -1,4 +1,4 @@ -\page nodesystem Radium node system +\page nodeSystem Radium node system [TOC] # Radium node system @@ -44,7 +44,7 @@ The node system allow to build computation graph that takes its input from some in some _data sink_ after applying several _functions_ on the data. Computation graphs can be serialized and un-serialized in json format. The serialization process is nevertheless limited -to serializable data stored on the node and it is of the responsability of the application to manage non serializable +to serializable data stored on the node and it is of the responsibility of the application to manage non serializable data such as, e.g. anonymous functions (lambdas, functors, ...) dynamically defined by the application. The Radium node system relies on the following concepts @@ -65,8 +65,8 @@ profile and allow to build the computation graph by linking ports together, impl A port gives access to a strongly typed data and, while implementing the general Ra::Dataflow::Core::PortBase interface should be specialized to be either an input port (element of the definition domain of a node) through the -instantiation of the template Ra::Dataflow::Core::PortIn or to an output port (element of the definition -co-domain of a node) through the instantiation of the template Ra::Dataflow::Core::PortOut. +instancing of the template Ra::Dataflow::Core::PortIn or to an output port (element of the definition +co-domain of a node) through the instancing of the template Ra::Dataflow::Core::PortOut. When a node executes its function, it takes its parameter from its input ports and set the result on the output port. From 9553ce15363650d6822ffbf95ac5a048fe97eca6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 25 Oct 2022 18:24:17 +0200 Subject: [PATCH 067/239] [core] rename constexpr in Random utilities --- src/Core/Random/RandomPointSet.hpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Core/Random/RandomPointSet.hpp b/src/Core/Random/RandomPointSet.hpp index ffed0ae8e4e..c6a1368c1ec 100644 --- a/src/Core/Random/RandomPointSet.hpp +++ b/src/Core/Random/RandomPointSet.hpp @@ -16,12 +16,17 @@ namespace Core { */ namespace Random { -Scalar constexpr sqrtNewtonRaphsonhelper( Scalar x, Scalar curr, Scalar prev ) { - return curr == prev ? curr : sqrtNewtonRaphsonhelper( x, 0.5_ra * ( curr + x / curr ), curr ); +/** + * \brief Defines some helper constexpr functions + */ +namespace internal { +Scalar constexpr sqrtNewtonRaphsonHelper( Scalar x, Scalar curr, Scalar prev ) { + return curr == prev ? curr : sqrtNewtonRaphsonHelper( x, 0.5_ra * ( curr + x / curr ), curr ); } -Scalar constexpr ct_sqrt( Scalar x ) { - return sqrtNewtonRaphsonhelper( x, x, 0_ra ); +Scalar constexpr sqrtConstExpr( Scalar x ) { + return sqrtNewtonRaphsonHelper( x, x, 0_ra ); } +} // namespace internal /** \brief Implements the fibonacci sequence * i --> i/phi @@ -30,7 +35,7 @@ Scalar constexpr ct_sqrt( Scalar x ) { class RA_CORE_API FibonacciSequence { - static constexpr Scalar phi = ( 1_ra + ct_sqrt( 5_ra ) ) / 2_ra; + static constexpr Scalar phi = ( 1_ra + internal::sqrtConstExpr( 5_ra ) ) / 2_ra; size_t n; public: From 0a80517656b4045ec4cf126db28ece1b16023fb4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 25 Oct 2022 18:52:31 +0200 Subject: [PATCH 068/239] [tests] fix some typos on dataflow unittests and add doc snippets --- tests/unittest/Dataflow/customnodes.cpp | 4 ++-- tests/unittest/Dataflow/nodes.cpp | 10 ++++++---- tests/unittest/Dataflow/sourcesandsinks.cpp | 17 ++++------------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 35598eabaef..61a8f808bda 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -21,6 +21,7 @@ namespace Customs { using CustomStringSource = Sources::SingleDataSourceNode; using CustomStringSink = Sinks::SinkNode; +//! [Develop a custom node] /** * \brief generate a predicate that compare a value wrt a threshold. * The name of the operator is fetched from input port "name" or the internal data set using @@ -63,7 +64,6 @@ class FilterSelector final : public Node * @param name */ void setThreshold( const T& t ) { m_threshold = t; } - /** \brief Get the threshold */ T getThreshold() const { return m_threshold; } @@ -121,7 +121,7 @@ class FilterSelector final : public Node function_type m_currentFunction = m_functions[m_operatorName]; T m_threshold {}; }; - +//! [Develop a custom node] } // namespace Customs // Reusable function to create a graph diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 7f0429613d3..de99615f16a 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -11,6 +11,7 @@ #include #include +//! [Create a source to sink graph for type T] using namespace Ra::Dataflow::Core; template std::tuple, std::shared_ptr, PortBase*> @@ -260,6 +261,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { } SECTION( "Transform/reduce/filter/test" ) { + //! [Create a complex transform/reduce graph] auto g = new DataflowGraph( "Complex graph" ); using VectorType = Ra::Core::VectorArray; @@ -365,7 +367,7 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { auto inputR = g->getDataSetter( "m_f" ); if ( inputR == nullptr ) { std::cout << "Failed to get the graph function input !!\n"; } - //! [Inspect the graph interface : inputs and outputs port] + // Inspect the graph interface : inputs and outputs port auto inputs = g->getAllDataSetters(); std::cout << "Input ports (" << inputs.size() << ") are :\n"; for ( auto& [ptrPort, portName, portType] : inputs ) { @@ -376,7 +378,6 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { for ( auto& [ptrPort, portName, portType] : outputs ) { std::cout << "\t\"" << portName << "\" generating type " << portType << "\n"; } - //! [Inspect the graph interface : inputs and outputs port] if ( !g->compile() ) { std::cout << "Compilation error !!"; } @@ -412,9 +413,10 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { REQUIRE( resultD / result == 2_ra ); REQUIRE( resultB ); - - g->saveToJson( "Transform-reduce.json" ); + // uncomment this if you want to edit the generated graph with GraphEditor + // g->saveToJson( "Transform-reduce.json" ); g->destroy(); delete g; + //! [Create a complex transform/reduce graph] } } diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp index 1eb4ef9848c..e628d0d81c1 100644 --- a/tests/unittest/Dataflow/sourcesandsinks.cpp +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -6,29 +6,22 @@ #include #include -#include #include #include using namespace Ra::Dataflow::Core; -// #define USE_SOURCE_DATA - +//! [Create a source to sink graph for type T] template void testGraph( const std::string& name, T in, T& out ) { auto g = new DataflowGraph { name }; auto source = new Sources::SingleDataSourceNode( "in" ); -#ifdef USE_SOURCE_DATA - std::cout << "Setting " << simplifiedDemangledType() << " data on source node ... "; - source->setData( &in ); -#endif - auto sink = new Sinks::SinkNode( "out" ); g->addNode( source ); g->addNode( sink ); auto linked = g->addLink( source, "to", sink, "from" ); - if ( !linked ) { std::cerr << "Error linking soure and sink nodes.\n"; } + if ( !linked ) { std::cerr << "Error linking source and sink nodes.\n"; } REQUIRE( linked ); auto input = g->getDataSetter( "in_to" ); @@ -37,13 +30,11 @@ void testGraph( const std::string& name, T in, T& out ) { REQUIRE( output != nullptr ); auto compiled = g->compile(); - if ( !compiled ) { std::cerr << "Error compiledcompiled graph.\n"; } + if ( !compiled ) { std::cerr << "Error compiling graph.\n"; } REQUIRE( compiled ); -#ifndef USE_SOURCE_DATA std::cout << "Setting " << simplifiedDemangledType() << " data on interface port ... "; input->setData( &in ); -#endif g->execute(); @@ -55,7 +46,7 @@ void testGraph( const std::string& name, T in, T& out ) { g->destroy(); delete g; } - +//! [Create a source to sink graph for type T] TEST_CASE( "Dataflow/Core/Sources and Sinks", "[Dataflow][Core][Sources and Sinks]" ) { SECTION( "Operations on base type : Scalar" ) { using DataType = Scalar; From 167cc4f0b9c4a00f198154394c4d8ab83ec7e64c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 25 Oct 2022 18:53:18 +0200 Subject: [PATCH 069/239] [doc] use snippets from unittests --- doc/CMakeLists.txt | 4 ++-- doc/concepts/nodesystem.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index a509e0c97bb..0a2d6911052 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -169,8 +169,8 @@ if(DOXYGEN_FOUND) doxygen_add_docs( RadiumDoc ${md_pages_order} ${md_files} ${CMAKE_CURRENT_SOURCE_DIR}/../src/ - ${CMAKE_CURRENT_SOURCE_DIR}/../tests/unittest/Dataflow ${CURRENT_SRC_DIR} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMENT "Generating API documentation with Doxygen" + ${CURRENT_SRC_DIR} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Generating API documentation with Doxygen" ) # Where docs will be installed. diff --git a/doc/concepts/nodesystem.md b/doc/concepts/nodesystem.md index 246643bc632..7546d21f233 100644 --- a/doc/concepts/nodesystem.md +++ b/doc/concepts/nodesystem.md @@ -186,7 +186,7 @@ See the file examples/DataflowExamples/HelloGraph/main.cpp for all the details. ## Examples of graphs and of programming custom nodes The unittests developed alongside the Radium::Dataflow component, and located in the directory -`tests/unitttest/Dataflow/` of the Radium-Engine source tree, can be used to learn the following : +`tests/unittest/Dataflow/` of the Radium-Engine source tree, can be used to learn the following : - sourcesandsinks.cpp : demonstrate the default supported types for sources and sinks node. - nodes.cpp : demonstrate the development of a more complex graph implementing transform/reduce @@ -196,3 +196,4 @@ on several collections using different reduction operators. application. - customnodes.cpp : demonstrate how it is simple to develop your own node type (in C++) and use your nodes alongside standard nodes. + \snippet unittest/Dataflow/customnodes.cpp Develop a custom node From 6233c156ad5335d21b47cc42b910d9e19fc9f200 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 1 Nov 2022 18:20:08 +0100 Subject: [PATCH 070/239] [externals] update externals configuration --- external/Dataflow/CMakeLists.txt | 17 +++++++++++------ external/Dataflow/package.cmake | 9 --------- 2 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 external/Dataflow/package.cmake diff --git a/external/Dataflow/CMakeLists.txt b/external/Dataflow/CMakeLists.txt index b1f07f8970f..08d14491398 100644 --- a/external/Dataflow/CMakeLists.txt +++ b/external/Dataflow/CMakeLists.txt @@ -5,19 +5,21 @@ project(radiumdataflow-external VERSION 1.0.0) include(ExternalProject) include(ExternalInclude) +list(APPEND CMAKE_MESSAGE_INDENT "[Dataflow] ") +string(REPLACE ";" "" indent_string "${CMAKE_MESSAGE_INDENT}") +set(indent_string "${indent_string}--") + # force installing by default all the external projects set_property(DIRECTORY PROPERTY EP_STEP_TARGETS install) # Add fPIC for all dependencies set(CMAKE_POSITION_INDEPENDENT_CODE ON) -string(REPLACE ";" "" indent_string "${CMAKE_MESSAGE_INDENT}") -set(indent_string "${indent_string}--") add_custom_target(DataflowExternals ALL) if(NOT DEFINED stduuid_DIR) check_externals_prerequisite() - status_message("[DataflowExternal]" "stduuid" "remote git") + status_message("" "stduuid" "remote git") ExternalProject_Add( stduuid GIT_TAG 3afe7193facd5d674de709fccc44d5055e144d7a @@ -28,8 +30,9 @@ if(NOT DEFINED stduuid_DIR) "${CMAKE_CURRENT_LIST_DIR}/patches/stduuid.patch" INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} -DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON - "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" + -DCMAKE_INSTALL_PREFIX= "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" ) + set_external_dir(stduuid "lib/cmake/stduuid/") add_dependencies(DataflowExternals stduuid) else() status_message("" "stduuid" ${stduuid_DIR}) @@ -37,7 +40,7 @@ endif() if(NOT DEFINED RadiumNodeEditor_DIR) check_externals_prerequisite() - status_message("[DataflowExternal]" "RadiumNodeEditor" "remote git") + status_message("" "RadiumNodeEditor" "remote git") ExternalProject_Add( RadiumNodeEditor GIT_REPOSITORY https://github.com/MathiasPaulin/RadiumQtNodeEditor.git @@ -45,8 +48,10 @@ if(NOT DEFINED RadiumNodeEditor_DIR) GIT_SHALLOW TRUE GIT_PROGRESS TRUE INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" - CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" + CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} -DCMAKE_INSTALL_PREFIX= + "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" ) + set_external_dir(RadiumNodeEditor "lib/cmake/RadiumNodeEditor/") add_dependencies(DataflowExternals RadiumNodeEditor) else() status_message("" "RadiumNodeEditor" ${RadiumNodeEditor_DIR}) diff --git a/external/Dataflow/package.cmake b/external/Dataflow/package.cmake deleted file mode 100644 index 1b47a899a02..00000000000 --- a/external/Dataflow/package.cmake +++ /dev/null @@ -1,9 +0,0 @@ -if(NOT DEFINED stduuid_DIR) - set(stduuid_sub_DIR lib/cmake/stduuid CACHE INTERNAL "") - set(stduuid_DIR ${CMAKE_INSTALL_PREFIX}/${stduuid_sub_DIR}) -endif() - -if(NOT DEFINED RadiumNodeEditor_DIR) - set(RadiumNodeEditor_sub_DIR lib/cmake/RadiumNodeEditor CACHE INTERNAL "") - set(RadiumNodeEditor_DIR ${CMAKE_INSTALL_PREFIX}/${RadiumNodeEditor_sub_DIR}) -endif() From 83a894dc4d49d59585f56d7cce95405a2a48e5c2 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 1 Nov 2022 18:21:50 +0100 Subject: [PATCH 071/239] [dataflow] update configuration files --- doc/basics/dependencies.md | 4 ++-- src/Dataflow/Config.cmake.in | 21 +++++++++++---------- src/Dataflow/Core/CMakeLists.txt | 2 -- src/Dataflow/Core/Config.cmake.in | 8 +------- src/Dataflow/QtGui/CMakeLists.txt | 2 +- src/Dataflow/QtGui/Config.cmake.in | 10 ++++------ 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/doc/basics/dependencies.md b/doc/basics/dependencies.md index 6e8eb1ac1cb..5b52ab7d3d9 100644 --- a/doc/basics/dependencies.md +++ b/doc/basics/dependencies.md @@ -143,9 +143,9 @@ Currently supported (note that these paths must refer to the installation direct Radium is compiled and tested with specific version of dependencies, as given in the external's folder CMakeLists.txt and state here for the record * stduuid: https://github.com/mariusbancila/stduuid, [3afe7193facd5d674de709fccc44d5055e144d7a], - * with options `-DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON` + * with options `-DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON` * RadiumNodeEditor: https://github.com/MathiasPaulin/RadiumQtNodeEditor.git, [main], - * with options `None` + * with options `None` * assimp: https://github.com/assimp/assimp.git, [tags/v5.0.1], * with options `-DASSIMP_BUILD_ASSIMP_TOOLS=False -DASSIMP_BUILD_SAMPLES=False -DASSIMP_BUILD_TESTS=False -DIGNORE_GIT_HASH=True -DASSIMP_NO_EXPORT=True` * tinyply: https://github.com/ddiakopoulos/tinyply.git, [tags/2.3.2], diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in index 008198ee679..562e08464e7 100644 --- a/src/Dataflow/Config.cmake.in +++ b/src/Dataflow/Config.cmake.in @@ -25,16 +25,17 @@ if (Dataflow_FOUND AND NOT TARGET Dataflow) endif() endif() - if(NOT DataflowRendering_FOUND) - if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake") - set(DataflowRendering_FOUND TRUE) - include(${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake) - else() - set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowRendering not found") - set(Configure_Dataflow OFF) - endif() - endif() +# to be uncommented when dataflow rendering subpackage will be available +# if(NOT DataflowRendering_FOUND) +# if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake") +# set(DataflowRendering_FOUND TRUE) +# include(${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake) +# else() +# set(Radium_FOUND False) +# set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowRendering not found") +# set(Configure_Dataflow OFF) +# endif() +# endif() endif() # configure Dataflow component diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt index 032c9a3dc7d..3d3b0c8a0f0 100644 --- a/src/Dataflow/Core/CMakeLists.txt +++ b/src/Dataflow/Core/CMakeLists.txt @@ -10,8 +10,6 @@ add_library( ${dataflow_core_inlines} ${dataflow_core_private} ) -# LocalDependencies from parent scope -populate_local_dependencies(NAME "stduuid_DIR") find_package(stduuid REQUIRED NO_DEFAULT_PATH) # This one should be extracted directly from parent project properties. diff --git a/src/Dataflow/Core/Config.cmake.in b/src/Dataflow/Core/Config.cmake.in index 7cd8b9dacfa..c196aa2280b 100644 --- a/src/Dataflow/Core/Config.cmake.in +++ b/src/Dataflow/Core/Config.cmake.in @@ -18,13 +18,7 @@ endif() if(Configure_DataflowCore) # Theses paths reflect the paths founds in RadiumEngine/external/Dataflow/package - set(_BaseExternalDir_ "${CMAKE_CURRENT_LIST_DIR}/../../../../") - if("@stduuid_DIR@" STREQUAL "") - set(stduuid_DIR "${_BaseExternalDir_}/@stduuid_sub_DIR@") - else() - set(stduuid_DIR "@stduuid_DIR@") - endif() - + set(stduuid_DIR "@stduuid_DIR@") find_dependency(stduuid REQUIRED NO_DEFAULT_PATH) include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/DataflowCoreTargets.cmake") endif() diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt index 044737d9216..f6db1d56c21 100644 --- a/src/Dataflow/QtGui/CMakeLists.txt +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -3,7 +3,7 @@ list(APPEND CMAKE_MESSAGE_INDENT "[${ra_dataflowqtgui_target}] ") project(${ra_dataflowqtgui_target} LANGUAGES CXX VERSION ${Radium_VERSION}) -find_package(RadiumNodeEditor REQUIRED) +find_package(RadiumNodeEditor REQUIRED NO_DEFAULT_PATH) include(filelist.cmake) diff --git a/src/Dataflow/QtGui/Config.cmake.in b/src/Dataflow/QtGui/Config.cmake.in index b630c4a6c28..42e8dadec3b 100644 --- a/src/Dataflow/QtGui/Config.cmake.in +++ b/src/Dataflow/QtGui/Config.cmake.in @@ -34,12 +34,10 @@ if (DataflowQtGui_FOUND AND NOT TARGET DataflowQtGui) endif() if(Configure_DataflowQtGui) - if("@RadiumNodeEditor_DIR@" STREQUAL "") - set(RadiumNodeEditor_DIR "${_BaseExternalDir_}/@RadiumNodeEditor_sub_DIR@") - else() - set(RadiumNodeEditor_DIR "@RadiumNodeEditor_DIR@") + set(RadiumNodeEditor_DIR "@RadiumNodeEditor_DIR@") + find_dependency(RadiumNodeEditor REQUIRED NO_DEFAULT_PATH) + if(MSVC OR MSVC_IDE OR MINGW) + add_imported_dir(FROM RadiumNodeEditor::RadiumNodeEditor TO RadiumExternalDlls_location) endif() - find_dependency(glm REQUIRED NO_DEFAULT_PATH) - find_dependency(RadiumNodeEditor REQUIRED) include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/QtGui/DataflowQtGuiTargets.cmake") endif() From 3e3867081677d4c19e648845cd6686a183b7674b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 2 Nov 2022 12:43:00 +0100 Subject: [PATCH 072/239] [dataflow-core] remove unwanted error messages --- src/Dataflow/Core/Node.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index 2905fc1ed76..28a5bdd2f5d 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -37,6 +37,10 @@ Node::Node( const std::string& instanceName, const std::string& typeName ) : } bool Node::fromJson( const nlohmann::json& data ) { + if ( data.empty() ) { + // This is to avoid wrong error message when creating node from the editor + return true; + } if ( data.contains( "model" ) ) { if ( data["model"].contains( "instance" ) ) { m_instanceName = data["model"]["instance"]; } } From 107a3e7aceeaa42d292d0a39ca3d072f914d2436 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 2 Nov 2022 12:43:43 +0100 Subject: [PATCH 073/239] [dataflow-qtgui] improve contextual menu on node editor --- src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp | 1 + src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index b347d29395f..2b4508e2651 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -92,6 +92,7 @@ void GraphEditorView::buildAdapterRegistry( const NodeFactorySet& factories ) { this->m_dataflowGraph->addFactory( creatorFactory ); return std::make_unique( this->m_dataflowGraph, node ); }, + factoryName.c_str(), creator.second.c_str() ); } } diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp index f98f1c696b8..203cf19bc72 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.hpp @@ -44,7 +44,6 @@ class RA_DATAFLOW_API GraphEditorView : public QWidget private: std::vector connections; - // TODO, put all these as private data QtNodes::FlowScene* scene { nullptr }; QtNodes::FlowView* view { nullptr }; From ea6d934b21fdabc1cac58500742478f341067147 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 2 Nov 2022 14:41:01 +0100 Subject: [PATCH 074/239] [dataflow-qtgui] improve editable parameter gui and add TODO --- src/Dataflow/Core/EditableParameter.hpp | 5 +++ .../GraphEditor/ConnectionStatusData.hpp | 3 ++ .../QtGui/GraphEditor/WidgetFactory.cpp | 45 ++++++------------- .../QtGui/GraphEditor/WidgetFactory.hpp | 2 +- 4 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp index a852fedf100..955693cdf49 100644 --- a/src/Dataflow/Core/EditableParameter.hpp +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -12,6 +12,10 @@ namespace Core { /// \brief Basic introspection for Node internal data edition /// This base class gives key information to associate editing capabilities (and gui) to /// an node internal data. +/// \note This class seems to be very similar in its aim than the Ra::Engine::RenderParameter and +/// their editing capabilities through Ra::Gui::ParameterSetEditor. But, in order to be more +/// general, this class does not depend on Engine. +/// \todo Unify with Ra::Engine::RenderParameter (using Core only parameters set) /// struct RA_DATAFLOW_API EditableParameterBase { /// \name Constructors @@ -54,6 +58,7 @@ struct EditableParameter : public EditableParameterBase { T& m_data; /// Constraints on the edited data + /// \todo, replace by a json desc of the constraints. std::vector additionalData; }; diff --git a/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp b/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp index 7b9ae6444a1..ecedafdabac 100644 --- a/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp +++ b/src/Dataflow/QtGui/GraphEditor/ConnectionStatusData.hpp @@ -13,6 +13,9 @@ namespace QtGui { namespace GraphEditor { using namespace Ra::Dataflow::Core; +/** + * \brief Allow to manage connections in the RadiumNodeEditor external + */ class RA_DATAFLOW_API ConnectionStatusData : public QtNodes::NodeData { public: diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index 23a8ac4af9d..254e126a475 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -189,43 +189,24 @@ void initializeWidgetFactory() { []( EditableParameterBase* editableParameter ) { auto editable = dynamic_cast*>( editableParameter ); - auto c = 255_ra * Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); - auto button = new QPushButton( "Choose color" ); - m_colorDialog = new QColorDialog( QColor( c.x(), c.y(), c.z() ) ); - m_colorDialog->setModal( true ); - m_colorDialog->setObjectName( editable->m_name.c_str() ); -#if !defined( __APPLE__ ) - m_colorDialog->setOption( QColorDialog::DontUseNativeDialog ); -#endif - QColorDialog::connect( - m_colorDialog, &QColorDialog::currentColorChanged, [editable]( QColor value ) { - int newR, newG, newB; - value.getRgb( &newR, &newG, &newB ); - editable->m_data = ( Ra::Core::Utils::Color::sRGBToLinearRGB( - Ra::Core::Utils::Color( float( newR ), float( newG ), float( newB ) ) * - ( 1_ra / 255_ra ) ) ); - } ); - QPushButton::connect( button, &QPushButton::clicked, [editable]() { - Ra::Core::Utils::Color c = - 255_ra * Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); - m_colorDialog->setCurrentColor( QColor( c.x(), c.y(), c.z() ) ); - m_colorDialog->show(); - } ); - return button; + auto controlPanel = new ControlPanel( editable->m_name, false ); + auto clrCbk = [editable]( const Ra::Core::Utils::Color& clr ) { + editable->m_data = clr; + }; + controlPanel->addColorInput( "Choose color", clrCbk, editable->m_data, true ); + controlPanel->setVisible( true ); + return controlPanel; }, - []( QWidget*, EditableParameterBase* editableParameter ) -> bool { + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast*>( editableParameter ); - // auto colorDialog = widget->findChild( editable->m_name.c_str() ); - auto c = 255_ra * Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); - if ( m_colorDialog ) { - m_colorDialog->setCurrentColor( - QColor( int( c.x() ), int( c.y() ), int( c.z() ) ) ); - return true; - } - else { + auto button = widget->findChild( "Choose color" ); + if ( button ) { + // todo, update the color on the button and make the dialog to take its + // initial color from the button. Once dont, return true ... return false; } + return false; } ); /* diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp index 00d6d5a0b15..b06a09552fc 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp @@ -19,6 +19,7 @@ using namespace Ra::Dataflow::Core; /** * Set of functions to manage automatic widget creation and update for editable parameters in the Qt * Node Editor. + * \todo use Ra::Gui::ParameterSetEditor ? */ namespace WidgetFactory { /** Type of the function that creates a widget. @@ -74,7 +75,6 @@ RA_DATAFLOW_API bool updateWidget( QWidget* widget, EditableParameterBase* edita */ RA_DATAFLOW_API void initializeWidgetFactory(); -inline QColorDialog* m_colorDialog; }; // namespace WidgetFactory } // namespace GraphEditor From 20bd83eeb8e6261881db415b8bae072533bab0e4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 09:28:04 +0100 Subject: [PATCH 075/239] [dataflow-core] allow sources and sinks to customize their interface port --- src/Dataflow/Core/Node.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 0fcac2b8859..02b85ab94d9 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -84,7 +84,10 @@ class RA_DATAFLOW_API Node const std::vector>& getOutputs(); /// \brief Build the interface ports of the node - const std::vector& buildInterfaces( Node* parent ); + /// Derived node can override the default implementation that build an interface port for each + /// input or output port (e.g. if several inputs have the same type T, make an interface that is + /// a vector of T*) + virtual const std::vector& buildInterfaces( Node* parent ); /// \brief Get the interface ports of the node const std::vector& getInterfaces() const; From 2e4676325b9c2f499d49b68e00680766cc60b856 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 12:36:58 +0100 Subject: [PATCH 076/239] [dataflow] remove unneeded indicator --- examples/DataflowExamples/GraphEditor/MainWindow.cpp | 2 +- src/Dataflow/Core/DataflowGraph.cpp | 7 ++++--- src/Dataflow/Core/DataflowGraph.hpp | 6 ++---- src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp | 3 +-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/examples/DataflowExamples/GraphEditor/MainWindow.cpp index d037cae584c..c61223b2ea5 100644 --- a/examples/DataflowExamples/GraphEditor/MainWindow.cpp +++ b/examples/DataflowExamples/GraphEditor/MainWindow.cpp @@ -71,7 +71,7 @@ void MainWindow::about() { } void MainWindow::documentWasModified() { - setWindowModified( graph->m_recompile ); + setWindowModified( !graph->m_ready ); } void MainWindow::createActions() { diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 928d3270032..dcfa9728c58 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -123,7 +123,7 @@ bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "graph" ) ) { // indicate that the graph must be recompiled after loading - m_recompile = true; + m_ready = false; // load the graph m_factories.reset( new NodeFactorySet ); addFactory( NodeFactoriesManager::getDataFlowBuiltInsFactory() ); @@ -284,6 +284,7 @@ bool DataflowGraph::removeNode( Node* node ) { } } m_nodes.erase( m_nodes.begin() + index ); + m_ready = false; return true; } } @@ -372,6 +373,7 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { if ( found == -1 ) { return false; } node->getInputs()[found]->disconnect(); + m_ready = false; return true; } @@ -438,8 +440,7 @@ bool DataflowGraph::compile() { } } } - m_recompile = false; - m_ready = true; + m_ready = true; init(); return m_ready; } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index de02186f25b..66e7b6980f1 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -106,10 +106,8 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Deletes all nodes from the render graph. virtual void clearNodes(); - /// Flag used to tell the renderer to recompile the rendergraph - bool m_recompile { false }; - - /// Flag set after rendergraph compilation checking if its state is right + /// Flag set after successful compilation indicating graph is ready to be executed + /// This flag is reset as soon as the graph is modified. bool m_ready { false }; /// \brief Creates an output port connected to the named input port of the graph. diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index 195973556e7..68859012456 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -143,7 +143,6 @@ void NodeAdapterModel::setInData( std::shared_ptr data, int p m_inputsConnected[port] = false; } checkConnections(); - m_dataflowGraph->m_recompile = true; } QtNodes::NodeDataType NodeAdapterModel::IOToDataType( size_t hashType, @@ -167,7 +166,7 @@ void NodeAdapterModel::addMetaData( QJsonObject& json ) { } NodeAdapterModel::~NodeAdapterModel() { - if ( m_dataflowGraph->removeNode( m_node ) ) { m_dataflowGraph->m_recompile = true; } + m_dataflowGraph->removeNode( m_node ); } QJsonObject NodeAdapterModel::save() const { From e396091f93156adeed39c047a03145df08d94847 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 12:41:25 +0100 Subject: [PATCH 077/239] [dataflow-core] improve simplified type demangler --- src/Dataflow/Core/TypeDemangler.hpp | 4 ++++ src/Dataflow/Core/TypeDemangler.inl | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index a76aa4b4c85..7c47277a937 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -9,6 +9,10 @@ namespace Core { template const char* simplifiedDemangledType() noexcept; +/// \brief Return the human readable version of the type name T with simplified radium types +template +const char* simplifiedDemangledType( const T& ) noexcept; + } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/TypeDemangler.inl b/src/Dataflow/Core/TypeDemangler.inl index 982556316bc..e77abe98c87 100644 --- a/src/Dataflow/Core/TypeDemangler.inl +++ b/src/Dataflow/Core/TypeDemangler.inl @@ -10,7 +10,6 @@ namespace TypeInternal { RA_DATAFLOW_API std::string makeTypeReadable( std::string ); } -/// Return the human readable version of the type name T with simplified radium types template const char* simplifiedDemangledType() noexcept { static auto demangled_name = []() { @@ -21,6 +20,11 @@ const char* simplifiedDemangledType() noexcept { return demangled_name.data(); } +template +const char* simplifiedDemangledType( const T& ) noexcept { + return simplifiedDemangledType(); +} + } // namespace Core } // namespace Dataflow } // namespace Ra From dc8c66bb1c1a8eb886c7f67fbcc97ca2e968a00e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 16:08:45 +0100 Subject: [PATCH 078/239] [dataflow-core] fix comment typo --- src/Dataflow/Core/DataflowGraph.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index dcfa9728c58..cac065b870c 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -16,7 +16,7 @@ DataflowGraph::DataflowGraph( const std::string& name ) : DataflowGraph( name, g DataflowGraph::DataflowGraph( const std::string& instanceName, const std::string& typeName ) : Node( instanceName, typeName ) { - // This will alllow to edit subgraph in an independant editor + // This will allow to edit subgraph in an independent editor addEditableParameter( new EditableParameter( instanceName, *this ) ); // A graph always use the builtin nodes factory addFactory( NodeFactoriesManager::getDataFlowBuiltInsFactory() ); From 606eb0e9ed9203f7fa8b1902e4e2a53b771220d3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 16:11:35 +0100 Subject: [PATCH 079/239] [dataflow-core] Fix singleton (static) initialization order to allow automatic initialization of factories --- src/Dataflow/Core/NodeFactory.cpp | 46 ++++++++----------- src/Dataflow/Core/NodeFactory.hpp | 7 +-- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 5 +- src/Dataflow/RaDataflow.hpp | 9 ++++ 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index f8c3cc6444f..ae0b2923afc 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -48,11 +48,6 @@ size_t NodeFactory::nextNodeId() { return ++m_nodesCreated; } -NodeFactorySet::NodeFactorySet() { - // Add the "DataFlowBuiltIns" factory - NodeFactoriesManager::getDataFlowBuiltInsFactory(); -} - Node* NodeFactorySet::createNode( std::string& nodeType, const nlohmann::json& data, DataflowGraph* owningGraph ) { @@ -67,44 +62,41 @@ Node* NodeFactorySet::createNode( std::string& nodeType, namespace NodeFactoriesManager { -/* - * TODO, replace static data by this when implementing an autoregistration mecanism -// to prevent static intialization order -static NodeFactorySet& s_factoryManager() { - static NodeFactorySet real_manager {}; - return real_manager; -}; -*/ -static NodeFactorySet s_factoryManager {}; - -NodeFactorySet getFactoryManager() { +/** + * \brief Allow static intialization without init order problems + * \return The manager singleton + */ +NodeFactorySet& getFactoryManager() { + static NodeFactorySet s_factoryManager {}; return s_factoryManager; } bool registerFactory( NodeFactorySet::mapped_type factory ) { + auto& fctMngr = getFactoryManager(); auto factoryName = factory->getName(); - return s_factoryManager.addFactory( std::move( factoryName ), std::move( factory ) ); + return fctMngr.addFactory( std::move( factoryName ), std::move( factory ) ); } NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ) { - auto factory = s_factoryManager.find( factoryName ); - if ( factory == s_factoryManager.end() ) { return nullptr; } + auto& fctMngr = getFactoryManager(); + auto factory = fctMngr.find( factoryName ); + if ( factory == fctMngr.end() ) { return nullptr; } return factory->second; } bool unregisterFactory( NodeFactorySet::key_type factoryName ) { - return s_factoryManager.removeFactory( factoryName ); + auto& fctMngr = getFactoryManager(); + return fctMngr.removeFactory( factoryName ); } NodeFactorySet::mapped_type getDataFlowBuiltInsFactory() { + auto& fctMngr = getFactoryManager(); + auto i = fctMngr.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); + if ( i != fctMngr.end() ) { return i->second; } - auto i = s_factoryManager.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); - if ( i != s_factoryManager.end() ) { return i->second; } - - // TODO, replace this by an autoregistration mecanism - registerStandardFactories(); - i = s_factoryManager.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); - return i->second; + // Should never be there + std::cerr << "@&$&$@&$@&$ ERROR !!!!!! Core factory not registered\n"; + std::abort(); } } // namespace NodeFactoriesManager diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index d401a01c9c8..ca28c586373 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -131,11 +131,6 @@ class RA_DATAFLOW_API NodeFactorySet using const_iterator = container_type::const_iterator; using iterator = container_type::iterator; - /** - * Default constructor : Will initialize the FactorySet with the Dataflow::Core "default" - * factory - */ - NodeFactorySet(); /** * Add a factory to the set of factories available * \param factoryname the name of the factory @@ -207,7 +202,7 @@ namespace NodeFactoriesManager { /** Names of the system Builtins factories (automatically added to each graph) */ extern const std::string dataFlowBuiltInsFactoryName; -RA_DATAFLOW_API NodeFactorySet getFactoryManager(); +RA_DATAFLOW_API NodeFactorySet& getFactoryManager(); /** Register a factory into the manager. * The key will be fetched from the factory (its name) diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 4a9ea079e96..926da70812e 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -15,7 +15,6 @@ namespace NodeFactoriesManager { void registerStandardFactories() { NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; - /* --- Sources --- */ Private::registerSourcesFactories( coreFactory ); @@ -34,3 +33,7 @@ void registerStandardFactories() { } // namespace Core } // namespace Dataflow } // namespace Ra + +DATAFLOW_LIBRARY_INITIALIZER( CoreNodes ) { + Ra::Dataflow::Core::NodeFactoriesManager::registerStandardFactories(); +} diff --git a/src/Dataflow/RaDataflow.hpp b/src/Dataflow/RaDataflow.hpp index 02bec913fca..4007fd8ccbe 100644 --- a/src/Dataflow/RaDataflow.hpp +++ b/src/Dataflow/RaDataflow.hpp @@ -9,3 +9,12 @@ #else # define RA_DATAFLOW_API DLL_IMPORT #endif + +/// Allow to define initializers for modules that need to be initialized transparently +#define DATAFLOW_LIBRARY_INITIALIZER( f ) \ + static void f##__Initializer(); \ + struct f##__Initializer_t_ { \ + f##__Initializer_t_() { f##__Initializer(); } \ + }; \ + static f##__Initializer_t_ f##__Initializer__; \ + static void f##__Initializer() From e107c45aefb7fc85c09c57e88c0d4bedcb837d89 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 17:23:49 +0100 Subject: [PATCH 080/239] [dataflow-core] allow to load any graph registered in a factory without knowing its type in advance. --- src/Dataflow/Core/DataflowGraph.cpp | 32 +++++++++++++++++++++++++++++ src/Dataflow/Core/DataflowGraph.hpp | 9 ++++++++ 2 files changed, 41 insertions(+) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index cac065b870c..891b1635537 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -596,6 +596,38 @@ Node* DataflowGraph::getNode( const std::string& instanceNameNode ) { return nullptr; } +DataflowGraph* DataflowGraph::loadGraphFromJsonFile( const std::string& filename ) { + std::ifstream jsonFile( filename ); + nlohmann::json j; + jsonFile >> j; + bool valid = false; + if ( j.contains( "model" ) ) { + if ( j["model"].contains( "name" ) && j["model"].contains( "instance" ) ) { valid = true; } + } + if ( !valid ) { + LOG( logERROR ) << "loadGraphFromJsonFile :" << filename + << " does not contain a valid json NodeGraph\n"; + return nullptr; + } + std::string instanceName = j["model"]["instance"]; + std::string graphType = j["model"]["name"]; + LOG( logINFO ) << "Loading the graph " << instanceName << ", with type " << graphType << "\n"; + + auto& fctMngr = Ra::Dataflow::Core::NodeFactoriesManager::getFactoryManager(); + auto ndldd = fctMngr.createNode( graphType, j ); + if ( ndldd == nullptr ) { + LOG( logERROR ) << "Unable to load a graph with type " << graphType << "\n"; + return nullptr; + } + + auto graph = dynamic_cast( ndldd ); + if ( graph != nullptr ) { return graph; } + + LOG( logERROR ) << "Loaded graph not inheriting from DataflowGraph " << graphType << "\n"; + delete ndldd; + return nullptr; +} + } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 66e7b6980f1..1a3d41893c7 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -202,6 +202,15 @@ class RA_DATAFLOW_API DataflowGraph : public Node public: static const std::string& getTypename(); + + /** + * \brief Load a graph from the given file. + * \param filename + * Any type of graph that inherits from DataflowGraph can be loaded by this function as soon as + * the appropriate constructor is registered in the node factory. + * \return The loaded graph, as a DataFlowGraph pointer to be downcast to the correct type + */ + static DataflowGraph* loadGraphFromJsonFile( const std::string& filename ); }; } // namespace Core From 2653c72339313de4475d0445dd72a8918c85d9bb Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 17:24:03 +0100 Subject: [PATCH 081/239] [examples] allow to load any graph registered in a factory without knowing its type in advance. --- .../GraphEditor/MainWindow.cpp | 17 +++++++---- .../DataflowExamples/GraphEditor/main.cpp | 29 +------------------ 2 files changed, 13 insertions(+), 33 deletions(-) diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/examples/DataflowExamples/GraphEditor/MainWindow.cpp index c61223b2ea5..0f795b5bd82 100644 --- a/examples/DataflowExamples/GraphEditor/MainWindow.cpp +++ b/examples/DataflowExamples/GraphEditor/MainWindow.cpp @@ -198,13 +198,20 @@ void MainWindow::loadFile( const QString& fileName ) graphEdit->editGraph( nullptr ); delete graph; - graph = new DataflowGraph( fileName.toStdString() ); - graph->loadFromJson( fileName.toStdString() ); - graphEdit->editGraph( graph ); + graph = DataflowGraph::loadGraphFromJsonFile( fileName.toStdString() ); + if ( graph == nullptr ) { + QMessageBox::warning( + this, + tr( "Application" ), + tr( "Can't load graph from file %1.\n" ).arg( QDir::toNativeSeparators( fileName ) ) ); + } + graphEdit->editGraph( graph ); QGuiApplication::restoreOverrideCursor(); - setCurrentFile( fileName ); - statusBar()->showMessage( tr( "File loaded" ), 2000 ); + if ( graph != nullptr ) { + setCurrentFile( fileName ); + statusBar()->showMessage( tr( "File loaded" ), 2000 ); + } } bool MainWindow::saveFile( const QString& fileName ) { diff --git a/examples/DataflowExamples/GraphEditor/main.cpp b/examples/DataflowExamples/GraphEditor/main.cpp index f7d46ae583d..65ffd555d82 100644 --- a/examples/DataflowExamples/GraphEditor/main.cpp +++ b/examples/DataflowExamples/GraphEditor/main.cpp @@ -4,18 +4,12 @@ #include "MainWindow.hpp" -#include -#include -#include - -using namespace Ra::Dataflow::Core; - int main( int argc, char* argv[] ) { Q_INIT_RESOURCE( GraphEditor ); QApplication app( argc, argv ); QCoreApplication::setOrganizationName( "STORM-IRIT" ); - QCoreApplication::setApplicationName( "Radium flowgraph example" ); + QCoreApplication::setApplicationName( "Radium NodeGraph Editor" ); QCoreApplication::setApplicationVersion( QT_VERSION_STR ); QCommandLineParser parser; parser.setApplicationDescription( QCoreApplication::applicationName() ); @@ -24,27 +18,6 @@ int main( int argc, char* argv[] ) { parser.addPositionalArgument( "file", "The file to open." ); parser.process( app ); - // [Creating the factory for the custom nodes and add it to the nodes system] - using VectorType = std::vector; - // using VectorType = Ra::Core::VectorArray; - // custom node type are either specialization of templated nodes or user-define nodes class - - // create the custom node factory - NodeFactorySet::mapped_type customFactory { - new NodeFactorySet::mapped_type::element_type( "ExampleCustomFactory" ) }; - - // add node creators to the factory - customFactory->registerNodeCreator>( - Sources::SingleDataSourceNode::getTypename() + "_", "Custom" ); - customFactory->registerNodeCreator>( - Functionals::FilterNode::getTypename() + "_", "Custom" ); - customFactory->registerNodeCreator>( - Sinks::SinkNode::getTypename() + "_", "Custom" ); - - // register the factory into the system to enable loading any graph that use these nodes - NodeFactoriesManager::registerFactory( customFactory ); - // [Creating the factory for the custom node types and add it to the node system] - MainWindow mainWin; if ( !parser.positionalArguments().isEmpty() ) mainWin.loadFile( parser.positionalArguments().first() ); From 201b6ed9ea4ab083f325ad059c7f5cb96c196ac9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 17:39:55 +0100 Subject: [PATCH 082/239] [dataflow] fix typo in macro to generate code :( --- src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 4e345b83d83..730d90702b2 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -57,7 +57,7 @@ DECLARE_COREDATA_SOURCES( Vector4ui, Vector4ui ); #define SPECIALIZE_EDITABLE_SOURCE( TYPE, NAME ) \ template <> \ inline SingleDataSourceNode::SingleDataSourceNode( const std::string& name ) : \ - SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { \ + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { \ setEditable( #NAME ); \ } \ \ @@ -85,7 +85,7 @@ SPECIALIZE_EDITABLE_SOURCE( unsigned int, value ); template <> inline SingleDataSourceNode::SingleDataSourceNode( const std::string& name ) : - SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { setEditable( "color" ); } From eb770d264cce10811cb02d38040f4ec68b952596 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 11:03:46 +0100 Subject: [PATCH 083/239] [dataflow-core] add state flag to dataflowgraph --- src/Dataflow/Core/DataflowGraph.cpp | 14 ++++++++++++-- src/Dataflow/Core/DataflowGraph.hpp | 4 ++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 891b1635537..c9efc4b9ae6 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -53,6 +53,7 @@ void DataflowGraph::destroy() { m_dataSetters.clear(); Node::destroy(); m_ready = false; + m_shouldBeSaved = true; } void DataflowGraph::saveToJson( const std::string& jsonFilePath ) { @@ -61,6 +62,7 @@ void DataflowGraph::saveToJson( const std::string& jsonFilePath ) { toJson( data ); std::ofstream file( jsonFilePath ); file << std::setw( 4 ) << data << std::endl; + m_shouldBeSaved = false; } } @@ -117,6 +119,7 @@ bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { std::ifstream file( jsonFilePath ); nlohmann::json j; file >> j; + m_shouldBeSaved = false; return fromJson( j ); } @@ -245,6 +248,7 @@ bool DataflowGraph::addNode( Node* newNode ) { } m_nodes.emplace_back( newNode ); m_ready = false; + m_shouldBeSaved = true; return true; } else { @@ -285,6 +289,7 @@ bool DataflowGraph::removeNode( Node* node ) { } m_nodes.erase( m_nodes.begin() + index ); m_ready = false; + m_shouldBeSaved = true; return true; } } @@ -353,6 +358,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, } // The state of the graph changes, set it to not ready m_ready = false; + m_shouldBeSaved = true; return true; } @@ -374,6 +380,7 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { node->getInputs()[found]->disconnect(); m_ready = false; + m_shouldBeSaved = true; return true; } @@ -446,7 +453,6 @@ bool DataflowGraph::compile() { } void DataflowGraph::clearNodes() { - for ( size_t i = 0; i < m_nodesByLevel.size(); i++ ) { m_nodesByLevel[i].clear(); m_nodesByLevel[i].shrink_to_fit(); @@ -455,6 +461,7 @@ void DataflowGraph::clearNodes() { m_nodesByLevel.shrink_to_fit(); m_nodes.erase( m_nodes.begin(), m_nodes.end() ); m_nodes.shrink_to_fit(); + m_shouldBeSaved = true; } void DataflowGraph::backtrackGraph( @@ -621,7 +628,10 @@ DataflowGraph* DataflowGraph::loadGraphFromJsonFile( const std::string& filename } auto graph = dynamic_cast( ndldd ); - if ( graph != nullptr ) { return graph; } + if ( graph != nullptr ) { + graph->m_shouldBeSaved = false; + return graph; + } LOG( logERROR ) << "Loaded graph not inheriting from DataflowGraph " << graphType << "\n"; delete ndldd; diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 1a3d41893c7..4a07a4467ac 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -110,6 +110,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// This flag is reset as soon as the graph is modified. bool m_ready { false }; + /// Flag that indicates if the graph should be saved to a file + /// This flag is useless outside an load/edit/save scenario + bool m_shouldBeSaved {false}; + /// \brief Creates an output port connected to the named input port of the graph. /// Return the connected output port if success, sharing the ownership with the caller. /// Allows to set data to the graph from the caller . From ffaada026efac83963c50a6c6f1b71936f9f2512 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 11:05:40 +0100 Subject: [PATCH 084/239] [dataflow-qtgui] add graph editor window as an available dataflow::qtgui widget --- src/Dataflow/QtGui/CMakeLists.txt | 2 +- .../QtGui}/GraphEditor/GraphEditor.qrc | 0 .../QtGui/GraphEditor/GraphEditorWindow.cpp | 172 +++++++++++------- .../QtGui/GraphEditor/GraphEditorWindow.hpp | 73 ++++++++ .../QtGui}/GraphEditor/images/copy.png | Bin .../QtGui}/GraphEditor/images/cut.png | Bin .../QtGui}/GraphEditor/images/new.png | Bin .../QtGui}/GraphEditor/images/open.png | Bin .../QtGui}/GraphEditor/images/paste.png | Bin .../QtGui}/GraphEditor/images/save.png | Bin src/Dataflow/QtGui/filelist.cmake | 6 +- 11 files changed, 181 insertions(+), 72 deletions(-) rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/GraphEditor.qrc (100%) rename examples/DataflowExamples/GraphEditor/MainWindow.cpp => src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp (62%) create mode 100644 src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/images/copy.png (100%) rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/images/cut.png (100%) rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/images/new.png (100%) rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/images/open.png (100%) rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/images/paste.png (100%) rename {examples/DataflowExamples => src/Dataflow/QtGui}/GraphEditor/images/save.png (100%) diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt index f6db1d56c21..5a257cb85a5 100644 --- a/src/Dataflow/QtGui/CMakeLists.txt +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -25,7 +25,7 @@ find_package(PowerSlider REQUIRED) add_library( ${ra_dataflowqtgui_target} SHARED ${dataflow_qtgui_sources} ${dataflow_qtgui_headers} - ${dataflow_qtgui_inlines} + ${dataflow_qtgui_inlines} ${dataflow_qtgui_resources} ) # This one should be extracted directly from parent project properties. diff --git a/examples/DataflowExamples/GraphEditor/GraphEditor.qrc b/src/Dataflow/QtGui/GraphEditor/GraphEditor.qrc similarity index 100% rename from examples/DataflowExamples/GraphEditor/GraphEditor.qrc rename to src/Dataflow/QtGui/GraphEditor/GraphEditor.qrc diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp similarity index 62% rename from examples/DataflowExamples/GraphEditor/MainWindow.cpp rename to src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index 0f795b5bd82..a2e1e988aef 100644 --- a/examples/DataflowExamples/GraphEditor/MainWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -1,61 +1,83 @@ -#include "MainWindow.hpp" +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { using namespace Ra::Dataflow::Core; -MainWindow::MainWindow() { - graphEdit = new GraphEditorView( nullptr ); - setCentralWidget( graphEdit ); - graphEdit->setFocusPolicy( Qt::StrongFocus ); + +GraphEditorWindow::GraphEditorWindow( DataflowGraph* graph ) : + m_graphEdit { new GraphEditorView( nullptr ) }, + m_graph { graph }, + m_ownGraph { graph==nullptr } { + + + setCentralWidget( m_graphEdit ); + m_graphEdit->setFocusPolicy( Qt::StrongFocus ); createActions(); createStatusBar(); readSettings(); - connect( graphEdit, &GraphEditorView::needUpdate, this, &MainWindow::documentWasModified ); + connect( + m_graphEdit, &GraphEditorView::needUpdate, this, &GraphEditorWindow::documentWasModified ); setCurrentFile( QString() ); setUnifiedTitleAndToolBarOnMac( true ); - newFile(); - graphEdit->show(); + if (m_ownGraph) { newFile(); } + else { m_graphEdit->editGraph( m_graph ); } + + m_graphEdit->show(); } -void MainWindow::closeEvent( QCloseEvent* event ) { +#if 0 +void GraphEditorWindow::resetGraph( DataflowGraph* graph ) { + m_graphEdit->editGraph( nullptr ); + if ( m_ownGraph ) { delete m_graph; } + m_graph = graph; + m_ownGraph = false; + m_graphEdit->editGraph( m_graph ); + setCurrentFile( "" ); +} +#endif + +void GraphEditorWindow::closeEvent( QCloseEvent* event ) { if ( maybeSave() ) { writeSettings(); event->accept(); } - else { - event->ignore(); - } + else { event->ignore(); } } -void MainWindow::newFile() { +void GraphEditorWindow::newFile() { if ( maybeSave() ) { // Currently edited graph must be deleted only after it is no more used by the editor - graphEdit->editGraph( nullptr ); - delete graph; + m_graphEdit->editGraph( nullptr ); + if ( m_ownGraph ) { + delete m_graph; + m_graph = new DataflowGraph( "untitled.flow" ); + } else { m_graph->destroy(); } setCurrentFile( "" ); - graph = new DataflowGraph( "untitled.flow" ); - graphEdit->editGraph( graph ); + m_graphEdit->editGraph( m_graph ); } } -void MainWindow::open() { +void GraphEditorWindow::open() { if ( maybeSave() ) { QString fileName = QFileDialog::getOpenFileName( this ); if ( !fileName.isEmpty() ) loadFile( fileName ); } } -bool MainWindow::save() { - if ( curFile.isEmpty() ) { return saveAs(); } - else { - return saveFile( curFile ); - } +bool GraphEditorWindow::save() { + if ( m_curFile.isEmpty() ) { return saveAs(); } + else { return saveFile( m_curFile ); } } -bool MainWindow::saveAs() { +bool GraphEditorWindow::saveAs() { QFileDialog dialog( this ); dialog.setWindowModality( Qt::WindowModal ); dialog.setAcceptMode( QFileDialog::AcceptSave ); @@ -63,18 +85,18 @@ bool MainWindow::saveAs() { return saveFile( dialog.selectedFiles().first() ); } -void MainWindow::about() { +void GraphEditorWindow::about() { QMessageBox::about( this, - tr( "About Application" ), - tr( "The Application example demonstrates how to " - "edit a Radium dataflow graph." ) ); + tr( "About Node Editor" ), + tr( "This is NodeGraph Editor widget from Radium::Dataflow::QtGui." ) ); } -void MainWindow::documentWasModified() { - setWindowModified( !graph->m_ready ); +void GraphEditorWindow::documentWasModified() { + setWindowModified( m_graph->m_shouldBeSaved ); + emit needUpdate(); } -void MainWindow::createActions() { +void GraphEditorWindow::createActions() { auto fileMenu = menuBar()->addMenu( tr( "&File" ) ); auto fileToolBar = addToolBar( tr( "File" ) ); @@ -82,7 +104,7 @@ void MainWindow::createActions() { auto newAct = new QAction( newIcon, tr( "&New" ), this ); newAct->setShortcuts( QKeySequence::New ); newAct->setStatusTip( tr( "Create a new file" ) ); - connect( newAct, &QAction::triggered, this, &MainWindow::newFile ); + connect( newAct, &QAction::triggered, this, &GraphEditorWindow::newFile ); fileMenu->addAction( newAct ); fileToolBar->addAction( newAct ); @@ -90,7 +112,7 @@ void MainWindow::createActions() { auto openAct = new QAction( openIcon, tr( "&Open..." ), this ); openAct->setShortcuts( QKeySequence::Open ); openAct->setStatusTip( tr( "Open an existing file" ) ); - connect( openAct, &QAction::triggered, this, &MainWindow::open ); + connect( openAct, &QAction::triggered, this, &GraphEditorWindow::open ); fileMenu->addAction( openAct ); fileToolBar->addAction( openAct ); @@ -98,13 +120,13 @@ void MainWindow::createActions() { auto saveAct = new QAction( saveIcon, tr( "&Save" ), this ); saveAct->setShortcuts( QKeySequence::Save ); saveAct->setStatusTip( tr( "Save the document to disk" ) ); - connect( saveAct, &QAction::triggered, this, &MainWindow::save ); + connect( saveAct, &QAction::triggered, this, &GraphEditorWindow::save ); fileMenu->addAction( saveAct ); fileToolBar->addAction( saveAct ); const auto saveAsIcon = QIcon::fromTheme( "document-save-as" ); auto saveAsAct = - fileMenu->addAction( saveAsIcon, tr( "Save &As..." ), this, &MainWindow::saveAs ); + fileMenu->addAction( saveAsIcon, tr( "Save &As..." ), this, &GraphEditorWindow::saveAs ); saveAsAct->setShortcuts( QKeySequence::SaveAs ); saveAsAct->setStatusTip( tr( "Save the document under a new name" ) ); @@ -122,19 +144,21 @@ void MainWindow::createActions() { #endif auto helpMenu = menuBar()->addMenu( tr( "&Help" ) ); - auto aboutAct = helpMenu->addAction( tr( "&About" ), this, &MainWindow::about ); + auto aboutAct = helpMenu->addAction( tr( "&About" ), this, &GraphEditorWindow::about ); aboutAct->setStatusTip( tr( "Show the application's About box" ) ); auto aboutQtAct = helpMenu->addAction( tr( "About &Qt" ), qApp, &QApplication::aboutQt ); aboutQtAct->setStatusTip( tr( "Show the Qt library's About box" ) ); + if ( !m_ownGraph ) { menuBar()->hide(); } } -void MainWindow::createStatusBar() { +void GraphEditorWindow::createStatusBar() { statusBar()->showMessage( tr( "Ready" ) ); } -void MainWindow::readSettings() { +void GraphEditorWindow::readSettings() { QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() ); + settings.beginGroup( "nodegraph editor" ); const QByteArray geometry = settings.value( "geometry", QByteArray() ).toByteArray(); if ( geometry.isEmpty() ) { const QRect availableGeometry = screen()->availableGeometry(); @@ -142,26 +166,24 @@ void MainWindow::readSettings() { move( ( availableGeometry.width() - width() ) / 2, ( availableGeometry.height() - height() ) / 2 ); } - else { - restoreGeometry( geometry ); - } + else { restoreGeometry( geometry ); } const QByteArray graphGeometry = settings.value( "graph", QByteArray() ).toByteArray(); - if ( graphGeometry.isEmpty() ) { graphEdit->resize( 800, 600 ); } - else { - graphEdit->restoreGeometry( graphGeometry ); - } + if ( graphGeometry.isEmpty() ) { m_graphEdit->resize( 800, 600 ); } + else { m_graphEdit->restoreGeometry( graphGeometry ); } + settings.endGroup(); } -void MainWindow::writeSettings() { +void GraphEditorWindow::writeSettings() { QSettings settings( QCoreApplication::organizationName(), QCoreApplication::applicationName() ); + settings.beginGroup( "nodegraph editor" ); settings.setValue( "geometry", saveGeometry() ); - settings.setValue( "graph", graphEdit->saveGeometry() ); + settings.setValue( "graph", m_graphEdit->saveGeometry() ); + settings.endGroup(); } -bool MainWindow::maybeSave() { -#if 0 - if (!textEdit->document()->isModified()) - return true; +bool GraphEditorWindow::maybeSave() { + if ( m_graph == nullptr ) { return true; } + if (! ( m_ownGraph && m_graph->m_shouldBeSaved ) ) { return true; } const QMessageBox::StandardButton ret = QMessageBox::warning(this, tr("Application"), tr("The document has been modified.\n" @@ -175,12 +197,11 @@ bool MainWindow::maybeSave() { default: break; } -#endif + return true; } -void MainWindow::loadFile( const QString& fileName ) - +void GraphEditorWindow::loadFile( const QString& fileName ) { QGuiApplication::setOverrideCursor( Qt::WaitCursor ); { @@ -195,32 +216,40 @@ void MainWindow::loadFile( const QString& fileName ) } } - graphEdit->editGraph( nullptr ); - delete graph; - - graph = DataflowGraph::loadGraphFromJsonFile( fileName.toStdString() ); - if ( graph == nullptr ) { + bool loaded (true); + if ( m_ownGraph ) { + m_graphEdit->editGraph( nullptr ); + delete m_graph; + m_graph = DataflowGraph::loadGraphFromJsonFile( fileName.toStdString() ); + loaded = ( m_graph != nullptr ); + } else { + m_graph->destroy(); + loaded = m_graph->loadFromJson( fileName.toStdString() ); + } + if ( ! loaded ) { QMessageBox::warning( this, tr( "Application" ), tr( "Can't load graph from file %1.\n" ).arg( QDir::toNativeSeparators( fileName ) ) ); } - graphEdit->editGraph( graph ); QGuiApplication::restoreOverrideCursor(); - if ( graph != nullptr ) { + if ( loaded ) { + m_graphEdit->editGraph( m_graph ); setCurrentFile( fileName ); statusBar()->showMessage( tr( "File loaded" ), 2000 ); + emit needUpdate(); } } -bool MainWindow::saveFile( const QString& fileName ) { +bool GraphEditorWindow::saveFile( const QString& fileName ) { QString errorMessage; QGuiApplication::setOverrideCursor( Qt::WaitCursor ); // TODO, if graph do not compile, tell it to the user ? - graph->compile(); - graph->saveToJson( fileName.toStdString() ); + // m_graph->compile(); + + m_graph->saveToJson( fileName.toStdString() ); #if 0 QSaveFile file(fileName); @@ -248,16 +277,21 @@ bool MainWindow::saveFile( const QString& fileName ) { return true; } -void MainWindow::setCurrentFile( const QString& fileName ) { - curFile = fileName; +void GraphEditorWindow::setCurrentFile( const QString& fileName ) { + m_curFile = fileName; // textEdit->document()->setModified(false); setWindowModified( false ); - QString shownName = curFile; - if ( curFile.isEmpty() ) shownName = "untitled.flow"; + QString shownName = m_curFile; + if ( m_curFile.isEmpty() ) shownName = "untitled.flow"; setWindowFilePath( shownName ); } -QString MainWindow::strippedName( const QString& fullFileName ) { +QString GraphEditorWindow::strippedName( const QString& fullFileName ) { return QFileInfo( fullFileName ).fileName(); } + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp new file mode 100644 index 00000000000..fac29726d6b --- /dev/null +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp @@ -0,0 +1,73 @@ +#pragma once +#include + +#include +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace QtGui { +namespace GraphEditor { + +using namespace Ra::Dataflow::Core; + +/** + * \brief Window widget to edit a Node Graph. + * This class just wrap a Ra::Dataflow::QtGui::GraphEditor::GraphEditorView in a main window with + * several services : + * - The window can be used as a standalone editor if no parameter is given to the constructor. + * - The window can be used to edit an existing graph by giving the graph to the constructor. + * + * connect the client to the needUpdate() signal to be notified of a change in the edited graph. + */ +class GraphEditorWindow : public QMainWindow +{ + Q_OBJECT + public: + explicit GraphEditorWindow( DataflowGraph* graph = nullptr ); + + void loadFile( const QString& fileName ); +#if 0 + // not sure to neeed this + void resetGraph( DataflowGraph* graph = nullptr ); +#endif + + signals: + void needUpdate(); + + protected: + void closeEvent( QCloseEvent* event ) override; + + private slots: + void newFile(); + void open(); + bool save(); + bool saveAs(); + void about(); + void documentWasModified(); + + private: + void createActions(); + void createStatusBar(); + void readSettings(); + void writeSettings(); + bool maybeSave(); + bool saveFile( const QString& fileName ); + void setCurrentFile( const QString& fileName ); + QString strippedName( const QString& fullFileName ); + + GraphEditorView* m_graphEdit{ nullptr }; + QString m_curFile; + + DataflowGraph* m_graph { nullptr }; + bool m_ownGraph { false }; +}; + +} // namespace GraphEditor +} // namespace QtGui +} // namespace Dataflow +} // namespace Ra \ No newline at end of file diff --git a/examples/DataflowExamples/GraphEditor/images/copy.png b/src/Dataflow/QtGui/GraphEditor/images/copy.png similarity index 100% rename from examples/DataflowExamples/GraphEditor/images/copy.png rename to src/Dataflow/QtGui/GraphEditor/images/copy.png diff --git a/examples/DataflowExamples/GraphEditor/images/cut.png b/src/Dataflow/QtGui/GraphEditor/images/cut.png similarity index 100% rename from examples/DataflowExamples/GraphEditor/images/cut.png rename to src/Dataflow/QtGui/GraphEditor/images/cut.png diff --git a/examples/DataflowExamples/GraphEditor/images/new.png b/src/Dataflow/QtGui/GraphEditor/images/new.png similarity index 100% rename from examples/DataflowExamples/GraphEditor/images/new.png rename to src/Dataflow/QtGui/GraphEditor/images/new.png diff --git a/examples/DataflowExamples/GraphEditor/images/open.png b/src/Dataflow/QtGui/GraphEditor/images/open.png similarity index 100% rename from examples/DataflowExamples/GraphEditor/images/open.png rename to src/Dataflow/QtGui/GraphEditor/images/open.png diff --git a/examples/DataflowExamples/GraphEditor/images/paste.png b/src/Dataflow/QtGui/GraphEditor/images/paste.png similarity index 100% rename from examples/DataflowExamples/GraphEditor/images/paste.png rename to src/Dataflow/QtGui/GraphEditor/images/paste.png diff --git a/examples/DataflowExamples/GraphEditor/images/save.png b/src/Dataflow/QtGui/GraphEditor/images/save.png similarity index 100% rename from examples/DataflowExamples/GraphEditor/images/save.png rename to src/Dataflow/QtGui/GraphEditor/images/save.png diff --git a/src/Dataflow/QtGui/filelist.cmake b/src/Dataflow/QtGui/filelist.cmake index d36ae55e95e..f45b26f8880 100644 --- a/src/Dataflow/QtGui/filelist.cmake +++ b/src/Dataflow/QtGui/filelist.cmake @@ -3,11 +3,13 @@ # ---------------------------------------------------- set(dataflow_qtgui_sources GraphEditor/GraphEditorView.cpp GraphEditor/NodeAdapterModel.cpp - GraphEditor/WidgetFactory.cpp + GraphEditor/GraphEditorWindow.cpp GraphEditor/WidgetFactory.cpp ) set(dataflow_qtgui_headers GraphEditor/ConnectionStatusData.hpp GraphEditor/GraphEditorView.hpp - GraphEditor/NodeAdapterModel.hpp GraphEditor/WidgetFactory.hpp + GraphEditor/GraphEditorWindow.hpp GraphEditor/NodeAdapterModel.hpp GraphEditor/WidgetFactory.hpp ) set(dataflow_qtgui_inlines) + +set(dataflow_qtgui_resources GraphEditor/GraphEditor.qrc) From ec88906eee0b00b3f3fb020414754f0a130fe3bc Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 11:06:52 +0100 Subject: [PATCH 085/239] [examples] Simplify graph editor example --- .../GraphEditor/CMakeLists.txt | 7 +-- .../GraphEditor/MainWindow.hpp | 46 ------------------- .../DataflowExamples/GraphEditor/main.cpp | 6 +-- 3 files changed, 4 insertions(+), 55 deletions(-) delete mode 100644 examples/DataflowExamples/GraphEditor/MainWindow.hpp diff --git a/examples/DataflowExamples/GraphEditor/CMakeLists.txt b/examples/DataflowExamples/GraphEditor/CMakeLists.txt index 2cf917f12fb..4c4a7b3fac2 100644 --- a/examples/DataflowExamples/GraphEditor/CMakeLists.txt +++ b/examples/DataflowExamples/GraphEditor/CMakeLists.txt @@ -45,14 +45,11 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) qt_wrap_ui(gui_uis ${gui_uis}) # -------------------------------------------------------------------------------------------------- - -set(app_sources main.cpp MainWindow.cpp) -set(app_resources GraphEditor.qrc) +set(app_sources main.cpp) # set(app_headers) -# delete ${app_headers} -add_executable(${PROJECT_NAME} ${app_sources} ${app_resources}) +add_executable(${PROJECT_NAME} ${app_sources}) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) diff --git a/examples/DataflowExamples/GraphEditor/MainWindow.hpp b/examples/DataflowExamples/GraphEditor/MainWindow.hpp deleted file mode 100644 index 760c41679e0..00000000000 --- a/examples/DataflowExamples/GraphEditor/MainWindow.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once -#include -#include - -#include - -#include - -using namespace Ra::Dataflow::QtGui::GraphEditor; - -/// TODO : transform this example in a grapheWidget allowing to edit graph or subgraphs -class MainWindow : public QMainWindow -{ - Q_OBJECT - - public: - MainWindow(); - - void loadFile( const QString& fileName ); - - protected: - void closeEvent( QCloseEvent* event ) override; - - private slots: - void newFile(); - void open(); - bool save(); - bool saveAs(); - void about(); - void documentWasModified(); - - private: - void createActions(); - void createStatusBar(); - void readSettings(); - void writeSettings(); - bool maybeSave(); - bool saveFile( const QString& fileName ); - void setCurrentFile( const QString& fileName ); - QString strippedName( const QString& fullFileName ); - - GraphEditorView* graphEdit; - QString curFile; - - DataflowGraph* graph { nullptr }; -}; diff --git a/examples/DataflowExamples/GraphEditor/main.cpp b/examples/DataflowExamples/GraphEditor/main.cpp index 65ffd555d82..3a9a31c6583 100644 --- a/examples/DataflowExamples/GraphEditor/main.cpp +++ b/examples/DataflowExamples/GraphEditor/main.cpp @@ -2,11 +2,9 @@ #include #include -#include "MainWindow.hpp" +#include int main( int argc, char* argv[] ) { - Q_INIT_RESOURCE( GraphEditor ); - QApplication app( argc, argv ); QCoreApplication::setOrganizationName( "STORM-IRIT" ); QCoreApplication::setApplicationName( "Radium NodeGraph Editor" ); @@ -18,7 +16,7 @@ int main( int argc, char* argv[] ) { parser.addPositionalArgument( "file", "The file to open." ); parser.process( app ); - MainWindow mainWin; + Ra::Dataflow::QtGui::GraphEditor::GraphEditorWindow mainWin; if ( !parser.positionalArguments().isEmpty() ) mainWin.loadFile( parser.positionalArguments().first() ); mainWin.show(); From 84d68c05c77d9dc5ab2ebbbfe41e1d7aa39bc128 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 11:30:17 +0100 Subject: [PATCH 086/239] forget to install pre-commit -- fix format after installing --- src/Dataflow/Core/DataflowGraph.cpp | 10 ++-- src/Dataflow/Core/DataflowGraph.hpp | 2 +- .../QtGui/GraphEditor/GraphEditorWindow.cpp | 57 ++++++++++++------- .../QtGui/GraphEditor/GraphEditorWindow.hpp | 4 +- src/Dataflow/QtGui/filelist.cmake | 8 ++- 5 files changed, 48 insertions(+), 33 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index c9efc4b9ae6..8ff86ef38f5 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -52,7 +52,7 @@ void DataflowGraph::destroy() { m_factories.reset(); m_dataSetters.clear(); Node::destroy(); - m_ready = false; + m_ready = false; m_shouldBeSaved = true; } @@ -247,7 +247,7 @@ bool DataflowGraph::addNode( Node* newNode ) { } } m_nodes.emplace_back( newNode ); - m_ready = false; + m_ready = false; m_shouldBeSaved = true; return true; } @@ -288,7 +288,7 @@ bool DataflowGraph::removeNode( Node* node ) { } } m_nodes.erase( m_nodes.begin() + index ); - m_ready = false; + m_ready = false; m_shouldBeSaved = true; return true; } @@ -357,7 +357,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, return false; } // The state of the graph changes, set it to not ready - m_ready = false; + m_ready = false; m_shouldBeSaved = true; return true; } @@ -379,7 +379,7 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { if ( found == -1 ) { return false; } node->getInputs()[found]->disconnect(); - m_ready = false; + m_ready = false; m_shouldBeSaved = true; return true; } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 4a07a4467ac..404e2a35dbf 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -112,7 +112,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Flag that indicates if the graph should be saved to a file /// This flag is useless outside an load/edit/save scenario - bool m_shouldBeSaved {false}; + bool m_shouldBeSaved { false }; /// \brief Creates an output port connected to the named input port of the graph. /// Return the connected output port if success, sharing the ownership with the caller. diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index a2e1e988aef..f41441a6839 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -10,8 +10,7 @@ using namespace Ra::Dataflow::Core; GraphEditorWindow::GraphEditorWindow( DataflowGraph* graph ) : m_graphEdit { new GraphEditorView( nullptr ) }, m_graph { graph }, - m_ownGraph { graph==nullptr } { - + m_ownGraph { graph == nullptr } { setCentralWidget( m_graphEdit ); m_graphEdit->setFocusPolicy( Qt::StrongFocus ); @@ -26,8 +25,10 @@ GraphEditorWindow::GraphEditorWindow( DataflowGraph* graph ) : setCurrentFile( QString() ); setUnifiedTitleAndToolBarOnMac( true ); - if (m_ownGraph) { newFile(); } - else { m_graphEdit->editGraph( m_graph ); } + if ( m_ownGraph ) { newFile(); } + else { + m_graphEdit->editGraph( m_graph ); + } m_graphEdit->show(); } @@ -48,7 +49,9 @@ void GraphEditorWindow::closeEvent( QCloseEvent* event ) { writeSettings(); event->accept(); } - else { event->ignore(); } + else { + event->ignore(); + } } void GraphEditorWindow::newFile() { @@ -58,7 +61,10 @@ void GraphEditorWindow::newFile() { if ( m_ownGraph ) { delete m_graph; m_graph = new DataflowGraph( "untitled.flow" ); - } else { m_graph->destroy(); } + } + else { + m_graph->destroy(); + } setCurrentFile( "" ); m_graphEdit->editGraph( m_graph ); @@ -74,7 +80,9 @@ void GraphEditorWindow::open() { bool GraphEditorWindow::save() { if ( m_curFile.isEmpty() ) { return saveAs(); } - else { return saveFile( m_curFile ); } + else { + return saveFile( m_curFile ); + } } bool GraphEditorWindow::saveAs() { @@ -166,10 +174,14 @@ void GraphEditorWindow::readSettings() { move( ( availableGeometry.width() - width() ) / 2, ( availableGeometry.height() - height() ) / 2 ); } - else { restoreGeometry( geometry ); } + else { + restoreGeometry( geometry ); + } const QByteArray graphGeometry = settings.value( "graph", QByteArray() ).toByteArray(); if ( graphGeometry.isEmpty() ) { m_graphEdit->resize( 800, 600 ); } - else { m_graphEdit->restoreGeometry( graphGeometry ); } + else { + m_graphEdit->restoreGeometry( graphGeometry ); + } settings.endGroup(); } @@ -183,13 +195,14 @@ void GraphEditorWindow::writeSettings() { bool GraphEditorWindow::maybeSave() { if ( m_graph == nullptr ) { return true; } - if (! ( m_ownGraph && m_graph->m_shouldBeSaved ) ) { return true; } - const QMessageBox::StandardButton ret - = QMessageBox::warning(this, tr("Application"), - tr("The document has been modified.\n" - "Do you want to save your changes?"), - QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); - switch (ret) { + if ( !( m_ownGraph && m_graph->m_shouldBeSaved ) ) { return true; } + const QMessageBox::StandardButton ret = + QMessageBox::warning( this, + tr( "Application" ), + tr( "The document has been modified.\n" + "Do you want to save your changes?" ), + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel ); + switch ( ret ) { case QMessageBox::Save: return save(); case QMessageBox::Cancel: @@ -201,8 +214,7 @@ bool GraphEditorWindow::maybeSave() { return true; } -void GraphEditorWindow::loadFile( const QString& fileName ) -{ +void GraphEditorWindow::loadFile( const QString& fileName ) { QGuiApplication::setOverrideCursor( Qt::WaitCursor ); { QFile file( fileName ); @@ -216,17 +228,18 @@ void GraphEditorWindow::loadFile( const QString& fileName ) } } - bool loaded (true); + bool loaded( true ); if ( m_ownGraph ) { m_graphEdit->editGraph( nullptr ); delete m_graph; m_graph = DataflowGraph::loadGraphFromJsonFile( fileName.toStdString() ); - loaded = ( m_graph != nullptr ); - } else { + loaded = ( m_graph != nullptr ); + } + else { m_graph->destroy(); loaded = m_graph->loadFromJson( fileName.toStdString() ); } - if ( ! loaded ) { + if ( !loaded ) { QMessageBox::warning( this, tr( "Application" ), diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp index fac29726d6b..4a232e7a0c6 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp @@ -60,7 +60,7 @@ class GraphEditorWindow : public QMainWindow void setCurrentFile( const QString& fileName ); QString strippedName( const QString& fullFileName ); - GraphEditorView* m_graphEdit{ nullptr }; + GraphEditorView* m_graphEdit { nullptr }; QString m_curFile; DataflowGraph* m_graph { nullptr }; @@ -70,4 +70,4 @@ class GraphEditorWindow : public QMainWindow } // namespace GraphEditor } // namespace QtGui } // namespace Dataflow -} // namespace Ra \ No newline at end of file +} // namespace Ra diff --git a/src/Dataflow/QtGui/filelist.cmake b/src/Dataflow/QtGui/filelist.cmake index f45b26f8880..a0185babbaf 100644 --- a/src/Dataflow/QtGui/filelist.cmake +++ b/src/Dataflow/QtGui/filelist.cmake @@ -3,11 +3,13 @@ # ---------------------------------------------------- set(dataflow_qtgui_sources GraphEditor/GraphEditorView.cpp GraphEditor/NodeAdapterModel.cpp - GraphEditor/GraphEditorWindow.cpp GraphEditor/WidgetFactory.cpp + GraphEditor/GraphEditorWindow.cpp GraphEditor/WidgetFactory.cpp ) -set(dataflow_qtgui_headers GraphEditor/ConnectionStatusData.hpp GraphEditor/GraphEditorView.hpp - GraphEditor/GraphEditorWindow.hpp GraphEditor/NodeAdapterModel.hpp GraphEditor/WidgetFactory.hpp +set(dataflow_qtgui_headers + GraphEditor/ConnectionStatusData.hpp GraphEditor/GraphEditorView.hpp + GraphEditor/GraphEditorWindow.hpp GraphEditor/NodeAdapterModel.hpp + GraphEditor/WidgetFactory.hpp ) set(dataflow_qtgui_inlines) From faf55c834f938e13749e177f43a2dd9c91372810 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 15:15:52 +0100 Subject: [PATCH 087/239] [dataflow-core] improve auto initialization of libraries --- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 5 ++--- src/Dataflow/RaDataflow.hpp | 15 ++++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 926da70812e..45ff4a396da 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -1,6 +1,5 @@ - - #include +DATAFLOW_LIBRARY_INITIALIZER_DECL( CoreNodes ); #include #include @@ -34,6 +33,6 @@ void registerStandardFactories() { } // namespace Dataflow } // namespace Ra -DATAFLOW_LIBRARY_INITIALIZER( CoreNodes ) { +DATAFLOW_LIBRARY_INITIALIZER_IMPL( CoreNodes ) { Ra::Dataflow::Core::NodeFactoriesManager::registerStandardFactories(); } diff --git a/src/Dataflow/RaDataflow.hpp b/src/Dataflow/RaDataflow.hpp index 4007fd8ccbe..046321b7ce9 100644 --- a/src/Dataflow/RaDataflow.hpp +++ b/src/Dataflow/RaDataflow.hpp @@ -11,10 +11,11 @@ #endif /// Allow to define initializers for modules that need to be initialized transparently -#define DATAFLOW_LIBRARY_INITIALIZER( f ) \ - static void f##__Initializer(); \ - struct f##__Initializer_t_ { \ - f##__Initializer_t_() { f##__Initializer(); } \ - }; \ - static f##__Initializer_t_ f##__Initializer__; \ - static void f##__Initializer() +#define DATAFLOW_LIBRARY_INITIALIZER_DECL( f ) void f##__Initializer() + +#define DATAFLOW_LIBRARY_INITIALIZER_IMPL( f ) \ + struct f##__Initializer_t_ { \ + f##__Initializer_t_() { ::f##__Initializer(); } \ + }; \ + static f##__Initializer_t_ f##__Initializer__; \ + void f##__Initializer() From 22af2f6cc32f1772ab7e2921cab5bdb8d5fbd935 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 15:16:41 +0100 Subject: [PATCH 088/239] [dataflow-core] Add documentation about some type conflict in port system when constness is of concern --- src/Dataflow/Core/Port.hpp | 4 ++++ src/Dataflow/Core/Port.inl | 25 +++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index afd63beb8af..b5f96dbc20d 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -20,6 +20,10 @@ class PortIn; /** * \brief Base class for nodes' ports * A port is a strongly typed extremity of connections between nodes. + * \warning when comparing and using typed port, beware of the const qualifier + * that is not always exposed by the C++ type system. There are some undefined behavior concerning + * const_casts and const qualifier in the C++ documentation + * (https://en.cppreference.com/w/cpp/language/const_cast). * */ class RA_DATAFLOW_API PortBase diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl index 4460cbd5650..a7b6e599405 100644 --- a/src/Dataflow/Core/Port.inl +++ b/src/Dataflow/Core/Port.inl @@ -96,15 +96,32 @@ void PortBase::setData( T* data ) { thisOut->setData( data ); return; } + LOG( Ra::Core::Utils::logERROR ) + << "Unable to set data with type " << simplifiedDemangledType( *data ) << " on port " + << getName() << " which expect " << getTypeName() << ".\n"; + std::abort(); } + LOG( Ra::Core::Utils::logERROR ) + << "Could not call PortBase::setData(T* data) on the input port " << getName() << ".\n"; + std::abort(); } template void PortBase::getData( T& t ) { if ( !is_input() ) { auto thisOut = dynamic_cast*>( this ); - if ( thisOut ) { t = thisOut->getData(); } + if ( thisOut ) { + t = thisOut->getData(); + return; + } + LOG( Ra::Core::Utils::logERROR ) + << "Unable to get data with type " << simplifiedDemangledType() << " on port " + << getName() << " which expect " << getTypeName() << ".\n"; + std::abort(); } + LOG( Ra::Core::Utils::logERROR ) + << "Could not call PortBase::getData( T& t ) on the input port " << getName() << ".\n"; + std::abort(); } template @@ -112,9 +129,13 @@ T& PortBase::getData() { if ( !is_input() ) { auto thisOut = dynamic_cast*>( this ); if ( thisOut ) { return thisOut->getData(); } + LOG( Ra::Core::Utils::logERROR ) + << "Unable to get data with type " << simplifiedDemangledType() << " on port " + << getName() << " which expect " << getTypeName() << ".\n"; + std::abort(); } LOG( Ra::Core::Utils::logERROR ) - << "Could not call T& PortBase::getData() on an input port !!!\n"; + << "Could not call T& PortBase::getData() on the input port " << getName() << ".\n"; std::abort(); } From 090ae74695377b80fe5343f276e6075bbf70477d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 17:11:25 +0100 Subject: [PATCH 089/239] [dataflow-core] Workaround for the way the Qt Node Editor manage graphs. --- src/Dataflow/Core/DataflowGraph.cpp | 6 ++++++ src/Dataflow/Core/DataflowGraph.hpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 8ff86ef38f5..519b611a14b 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -257,6 +257,9 @@ bool DataflowGraph::addNode( Node* newNode ) { } bool DataflowGraph::removeNode( Node* node ) { + // This is to prevent graph destruction from the graph editor, depending on how it is used + if ( m_nodesAndLinksProtected ) { return false; } + // Check if the new node already exists (= same name) int index = -1; if ( ( index = findNode( node ) ) == -1 ) { return false; } @@ -363,6 +366,9 @@ bool DataflowGraph::addLink( Node* nodeFrom, } bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { + // This is to prevent graph destruction from the graph editor, depending on how it is used + if ( m_nodesAndLinksProtected ) { return false; } + // Check node's existence in the graph if ( findNode( node ) == -1 ) { return false; } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 404e2a35dbf..ce9f6805272 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -215,6 +215,21 @@ class RA_DATAFLOW_API DataflowGraph : public Node * \return The loaded graph, as a DataFlowGraph pointer to be downcast to the correct type */ static DataflowGraph* loadGraphFromJsonFile( const std::string& filename ); + + /** + * \brief protect nodes and links from deletion. + * \param on true to protect, false to unprotect. + */ + void setNodesAndLinksProtection( bool on ) { m_nodesAndLinksProtected = on; } + + /** + * \brief get the protection status protect nodes and links from deletion + * \return the protection status + */ + bool getNodesAndLinksProtection() const { return m_nodesAndLinksProtected; } + + private: + bool m_nodesAndLinksProtected { false }; }; } // namespace Core From 5715441b47f569403dca2e033551ec7ff923d5b4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 17:11:53 +0100 Subject: [PATCH 090/239] [dataflow-qtgui] Workaround for the way the Qt Node Editor manage graphs. --- .../QtGui/GraphEditor/GraphEditorWindow.cpp | 13 +++++++++++++ .../QtGui/GraphEditor/GraphEditorWindow.hpp | 1 + 2 files changed, 14 insertions(+) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index f41441a6839..752414539d8 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -7,6 +7,18 @@ namespace GraphEditor { using namespace Ra::Dataflow::Core; +GraphEditorWindow::~GraphEditorWindow() { + + // Prevent graph destruction if the editor is used to work with active graphs. + auto graphProtection = m_graph->getNodesAndLinksProtection(); + if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( true ); } + delete m_graphEdit; + if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( graphProtection ); } + else { + delete m_graph; + } +} + GraphEditorWindow::GraphEditorWindow( DataflowGraph* graph ) : m_graphEdit { new GraphEditorView( nullptr ) }, m_graph { graph }, @@ -48,6 +60,7 @@ void GraphEditorWindow::closeEvent( QCloseEvent* event ) { if ( maybeSave() ) { writeSettings(); event->accept(); + deleteLater(); } else { event->ignore(); diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp index 4a232e7a0c6..c8cb5390e30 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp @@ -29,6 +29,7 @@ class GraphEditorWindow : public QMainWindow Q_OBJECT public: explicit GraphEditorWindow( DataflowGraph* graph = nullptr ); + ~GraphEditorWindow(); void loadFile( const QString& fileName ); #if 0 From 6a2a507fc9fb4c4c8296399cc0fc593483f85a6d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 17:57:18 +0100 Subject: [PATCH 091/239] [dataflow-qtgui] add missing dllexport --- src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp index c8cb5390e30..152f87b2d10 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.hpp @@ -24,7 +24,7 @@ using namespace Ra::Dataflow::Core; * * connect the client to the needUpdate() signal to be notified of a change in the edited graph. */ -class GraphEditorWindow : public QMainWindow +class RA_DATAFLOW_API GraphEditorWindow : public QMainWindow { Q_OBJECT public: From 4f550910aa74ed4e7c6f387da7684b36d8098f58 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sat, 5 Nov 2022 15:22:54 +0100 Subject: [PATCH 092/239] [dataflow-qtgui] fix crash at app exit --- src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index 752414539d8..dc66418c81d 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -8,7 +8,6 @@ namespace GraphEditor { using namespace Ra::Dataflow::Core; GraphEditorWindow::~GraphEditorWindow() { - // Prevent graph destruction if the editor is used to work with active graphs. auto graphProtection = m_graph->getNodesAndLinksProtection(); if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( true ); } @@ -60,7 +59,7 @@ void GraphEditorWindow::closeEvent( QCloseEvent* event ) { if ( maybeSave() ) { writeSettings(); event->accept(); - deleteLater(); + if ( !m_ownGraph ) { deleteLater(); } } else { event->ignore(); From 8b06e7bc8c3fcd77d3cf22c1f8ac4017b85d2e90 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sat, 5 Nov 2022 19:10:14 +0100 Subject: [PATCH 093/239] [dataflow-core] modify node interface for better reporting. --- src/Dataflow/Core/DataflowGraph.cpp | 24 +++++++++++++++---- src/Dataflow/Core/DataflowGraph.hpp | 2 +- src/Dataflow/Core/Node.hpp | 3 ++- .../Core/Nodes/Functionals/BinaryOpNode.hpp | 3 ++- .../Core/Nodes/Functionals/FilterNode.hpp | 2 +- .../Core/Nodes/Functionals/FilterNode.inl | 3 ++- .../Core/Nodes/Functionals/ReduceNode.hpp | 2 +- .../Core/Nodes/Functionals/ReduceNode.inl | 3 ++- .../Core/Nodes/Functionals/TransformNode.hpp | 2 +- .../Core/Nodes/Functionals/TransformNode.inl | 3 ++- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 2 +- src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 3 ++- .../Core/Nodes/Sources/FunctionSource.hpp | 2 +- .../Core/Nodes/Sources/FunctionSource.inl | 3 ++- .../Nodes/Sources/SingleDataSourceNode.hpp | 2 +- .../Nodes/Sources/SingleDataSourceNode.inl | 3 ++- 16 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 519b611a14b..ffa7e6ac966 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -33,13 +33,17 @@ void DataflowGraph::init() { } } -void DataflowGraph::execute() { +bool DataflowGraph::execute() { if ( !m_ready ) { - if ( !compile() ) { return; } + if ( !compile() ) { return false; } } - std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( const auto& level ) { - std::for_each( level.begin(), level.end(), []( auto node ) { node->execute(); } ); + bool result = true; + std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), [&result]( const auto& level ) { + std::for_each( level.begin(), level.end(), [&result]( auto node ) { + result = result && node->execute(); + } ); } ); + return result; } void DataflowGraph::destroy() { @@ -116,6 +120,12 @@ void DataflowGraph::toJsonInternal( nlohmann::json& data ) const { } bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { + + if ( !nlohmann::json::accept( std::ifstream( jsonFilePath ) ) ) { + LOG( logERROR ) << jsonFilePath << " is not a valid json file !!"; + return false; + } + std::ifstream file( jsonFilePath ); nlohmann::json j; file >> j; @@ -610,9 +620,15 @@ Node* DataflowGraph::getNode( const std::string& instanceNameNode ) { } DataflowGraph* DataflowGraph::loadGraphFromJsonFile( const std::string& filename ) { + if ( !nlohmann::json::accept( std::ifstream( filename ) ) ) { + LOG( logERROR ) << filename << " is not a valid json file !!"; + return nullptr; + } + std::ifstream jsonFile( filename ); nlohmann::json j; jsonFile >> j; + bool valid = false; if ( j.contains( "model" ) ) { if ( j["model"].contains( "name" ) && j["model"].contains( "instance" ) ) { valid = true; } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index ce9f6805272..344a9d159ce 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -33,7 +33,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node virtual ~DataflowGraph() = default; void init() override; - void execute() override; + bool execute() override; void destroy() override; /// Set the factory set to use when loading a graph diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 02b85ab94d9..24e84e8de6d 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -65,7 +65,8 @@ class RA_DATAFLOW_API Node /// \brief Executes the node. /// Execute the node function on the input ports (to be fetched) and write the results to the /// output ports. - virtual void execute() = 0; + /// \return the execution status. + virtual bool execute() = 0; /// \brief delete the node content /// The destroy() function is called once at the end of the lifetime of the node. diff --git a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp index 373e06c9421..ad8156cfe7b 100644 --- a/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/BinaryOpNode.hpp @@ -183,7 +183,7 @@ class BinaryOpNode : public Node Node::init(); } - void execute() override { + bool execute() override { auto f = m_operator; if ( m_portF->isLinked() ) { f = m_portF->getData(); } // The following test will always be true if the node was integrated in a compiled graph @@ -191,6 +191,7 @@ class BinaryOpNode : public Node m_result = internal::ExecutorHelper::executeInternal( m_portA->getData(), m_portB->getData(), f ); } + return true; } /// \brief Sets the operator to be evaluated by the node. diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index 89cece758fd..09c148f71fb 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -48,7 +48,7 @@ class FilterNode : public Node FilterNode( const std::string& instanceName, UnaryPredicate predicate ); void init() override; - void execute() override; + bool execute() override; /// Sets the filtering predicate on the node void setFilterFunction( UnaryPredicate predicate ); diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl index 3799b5150ff..9c189cadb68 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl @@ -28,7 +28,7 @@ void FilterNode::init() { } template -void FilterNode::execute() { +bool FilterNode::execute() { auto f = m_portPredicate->isLinked() ? m_portPredicate->getData() : m_predicate; // The following test will always be true if the node was integrated in a compiled graph if ( m_portIn->isLinked() ) { @@ -38,6 +38,7 @@ void FilterNode::execute() { // SequenceContainer std::copy_if( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); } + return true; } template diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp index d881133dacd..0649ab83dcd 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp @@ -53,7 +53,7 @@ class ReduceNode : public Node ReduceNode( const std::string& instanceName, ReduceOperator op, v_t initialValue = v_t {} ); void init() override; - void execute() override; + bool execute() override; /// Sets the operator on the node void setOperator( ReduceOperator op, v_t initialValue = v_t {} ); diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl index e37e15aa51b..727b025b6df 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl @@ -33,7 +33,7 @@ void ReduceNode::init() { } template -void ReduceNode::execute() { +bool ReduceNode::execute() { auto f = m_portF->isLinked() ? m_portF->getData() : m_operator; auto iv = m_portInit->isLinked() ? m_portInit->getData() : m_init; m_result = iv; @@ -42,6 +42,7 @@ void ReduceNode::execute() { const auto& inData = m_portIn->getData(); m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); } + return true; } template diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp index 094ed2bbf06..ea1e1be4e47 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp @@ -45,7 +45,7 @@ class TransformNode : public Node TransformNode( const std::string& instanceName, TransformOperator op ); void init() override; - void execute() override; + bool execute() override; /// Sets the operator on the node void setOperator( TransformOperator op ); diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl index 2a75f07b843..0a2a0b71a56 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl @@ -26,7 +26,7 @@ void TransformNode::init() { } template -void TransformNode::execute() { +bool TransformNode::execute() { auto f = m_portOperator->isLinked() ? m_portOperator->getData() : m_operator; // The following test will always be true if the node was integrated in a compiled graph if ( m_portIn->isLinked() ) { @@ -36,6 +36,7 @@ void TransformNode::execute() { // SequenceContainer std::transform( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); } + return true; } template diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index 8eb20cfaf97..31f253f211b 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -23,7 +23,7 @@ class SinkNode : public Node * \brief initialize the interface port data pointer */ void init() override; - void execute() override; + bool execute() override; /** * Get the delivered data diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl index 34c05577264..02a81dc394c 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl @@ -27,8 +27,9 @@ void SinkNode::init() { } template -void SinkNode::execute() { +bool SinkNode::execute() { m_data = m_portIn->getData(); + return true; } template diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index c7cfc9b63ba..4c7e62c924f 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -25,7 +25,7 @@ class FunctionSourceNode : public Node explicit FunctionSourceNode( const std::string& name ) : FunctionSourceNode( name, FunctionSourceNode::getTypename() ) {} - void execute() override; + bool execute() override; /** \brief Set the function to be delivered by the node. * @param data diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl index 05791dfd494..37aab835b80 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl @@ -15,13 +15,14 @@ FunctionSourceNode::FunctionSourceNode( const std::string& instanceN } template -void FunctionSourceNode::execute() { +bool FunctionSourceNode::execute() { auto interfacePort = static_cast*>( m_interface[0] ); if ( interfacePort->isLinked() ) { m_data = &( interfacePort->getData() ); } else { m_data = &m_localData; } m_portOut->setData( m_data ); + return true; } template diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index 2356e82258d..463daf60692 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -27,7 +27,7 @@ class SingleDataSourceNode : public Node explicit SingleDataSourceNode( const std::string& name ) : SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) {} - void execute() override; + bool execute() override; /** \brief Set the data to be delivered by the node. * @param data diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl index e97a97945ee..d2616e51359 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl @@ -15,7 +15,7 @@ SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, } template -void SingleDataSourceNode::execute() { +bool SingleDataSourceNode::execute() { // interfaces ports are at the same index as output ports auto interfacePort = static_cast*>( m_interface[0] ); if ( interfacePort->isLinked() ) { @@ -27,6 +27,7 @@ void SingleDataSourceNode::execute() { m_data = &m_localData; } m_portOut->setData( m_data ); + return true; } template From 213e59dc2127b226dba234fd63bc718df59ce43e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 6 Nov 2022 10:37:55 +0100 Subject: [PATCH 094/239] [tests] custom node update --- tests/unittest/Dataflow/customnodes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 61a8f808bda..c36beaf83f3 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -41,12 +41,13 @@ class FilterSelector final : public Node explicit FilterSelector( const std::string& name ) : FilterSelector( name, getTypename() ) {} - void execute() override { + bool execute() override { // get operator parameters if ( m_portName->isLinked() ) { m_operatorName = m_portName->getData(); } if ( m_portThreshold->isLinked() ) { m_threshold = m_portThreshold->getData(); } // compute the result associated to the output port m_currentFunction = m_functions[m_operatorName]; + return true; } /** \brief Set the function name used to select the function to deliver. From 53927e1be1e3e87f442086a7f60af0540b402ce3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 14 Nov 2022 09:12:47 +0100 Subject: [PATCH 095/239] [doc] fix typo --- doc/concepts/nodesystem.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/concepts/nodesystem.md b/doc/concepts/nodesystem.md index 7546d21f233..54c05739422 100644 --- a/doc/concepts/nodesystem.md +++ b/doc/concepts/nodesystem.md @@ -179,7 +179,7 @@ the graph can be executed and a reference to the resulting vector can be fetched ### 4. Multiple run of the graph on different input -As accessors use pointers and references on the data sent to / fetched from the graph, the HellowGraph example shows how +As accessors use pointers and references on the data sent to / fetched from the graph, the HelloGraph example shows how to change the input using different available means so that every run of the graph process different values. See the file examples/DataflowExamples/HelloGraph/main.cpp for all the details. From 8e1707ebd3e09c7e6c2dac95d83c140004979e24 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 14 Nov 2022 09:13:23 +0100 Subject: [PATCH 096/239] [dataflow-core] prepare better ownership transfer --- src/Dataflow/Core/DataflowGraph.cpp | 8 +++-- src/Dataflow/Core/DataflowGraph.hpp | 54 +++++++++++++++++------------ src/Dataflow/Core/DataflowGraph.inl | 4 +-- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index ffa7e6ac966..44364f2cce6 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -238,6 +238,10 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { return true; } +bool DataflowGraph::canAdd( const Node* newNode ) const { + return findNode( newNode ) == -1; +}; + bool DataflowGraph::addNode( Node* newNode ) { std::map m_mapInputs; // Check if the new node already exists (= same name and type) @@ -401,7 +405,7 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { } // Todo, rewrite this method using std::find_if ? -int DataflowGraph::findNode( const Node* node ) { +int DataflowGraph::findNode( const Node* node ) const { for ( size_t i = 0; i < m_nodes.size(); i++ ) { if ( *m_nodes[i] == *node ) { return i; } } @@ -610,7 +614,7 @@ std::vector DataflowGraph::getAllDataGetters() { return r; } -Node* DataflowGraph::getNode( const std::string& instanceNameNode ) { +Node* DataflowGraph::getNode( const std::string& instanceNameNode ) const { for ( const auto& node : m_nodes ) { if ( node->getInstanceName() == instanceNameNode ) { return node.get(); } } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 344a9d159ce..487e7c78bcb 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -14,8 +14,10 @@ namespace Core { * if sources must be used through interface port only, delete the set_data on all sources * \todo Are node aliases a should-have (to make editing more user friendly)??? */ + /** - * \brief Represent a set of connected nodes that definea computational graph + * \brief Represent a set of connected nodes that define a computational graph + * Ownership of nodes is given to the graph at construction time. * \todo make a "graph embedding node" that allow to seemlesly integrate a graph as a node in * another graph * --> Edition of a graph will allow loading and saving to a file directly @@ -28,7 +30,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node public: /// Constructor. /// The nodes pointing to external data are created here. - /// @param name The name of the render graph. + /// \param name The name of the render graph. explicit DataflowGraph( const std::string& name ); virtual ~DataflowGraph() = default; @@ -42,7 +44,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// get the node factory set associated with from the graph. /// returns nullptr if no factoryset is associated with the graph - std::shared_ptr getNodeFactories(); + std::shared_ptr getNodeFactories() const; /// Add a factory to the factoryset of the graph. /// Creates the factoryset if it does not exists @@ -53,29 +55,35 @@ class RA_DATAFLOW_API DataflowGraph : public Node void addFactory( std::shared_ptr f ); /// Loads nodes and links from a JSON file. - /// @param jsonFilePath The path to the JSON file. + /// \param jsonFilePath The path to the JSON file. bool loadFromJson( const std::string& jsonFilePath ); /// Saves nodes and links to a JSON file. - /// @param jsonFilePath The path to the JSON file. + /// \param jsonFilePath The path to the JSON file. void saveToJson( const std::string& jsonFilePath ); + /// \brief Test if a node can be added to a graph + /// \param newNode const naked pointer to the candidate node + /// \return true if ownership could be transferred to the graph. + virtual bool canAdd( const Node* newNode ) const; /// Adds a node to the render graph. Adds interface ports to the node newNode and the /// corresponding input and output ports to the graph. - /// @param newNode The node to add to the render graph. + /// \param newNode The node to add to the render graph. + /// ownership of the node is transfered to the graph + /// \todo move ine unique_ptr and return an optional with the raw pointer virtual bool addNode( Node* newNode ); /// Removes a node from the render graph. Removes input and output ports of the graph /// corresponding to interface ports of the node. - /// @param node The node to remove from the render graph. + /// \param node The node to remove from the render graph. virtual bool removeNode( Node* node ); /// Connects two nodes of the render graph. /// The two nodes must already be in the render graph (with the addNode(Node* newNode) /// function), the first node's in port must be free and the connected in port and out port must /// have the same type of data. - /// @param nodeFrom The node that contains the out port. - /// @param nodeFromOutputName The name of the out port in nodeFrom. - /// @param nodeTo The node that contains the in port. - /// @param nodeToInputName The name of the in port in nodeTo. + /// \param nodeFrom The node that contains the out port. + /// \param nodeFromOutputName The name of the out port in nodeFrom. + /// \param nodeTo The node that contains the in port. + /// \param nodeToInputName The name of the in port in nodeTo. bool addLink( Node* nodeFrom, const std::string& nodeFromOutputName, Node* nodeTo, @@ -86,9 +94,9 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Gets the nodes const std::vector>* getNodes() const; - /// Gets a specific node according to its instance name as a parameter. - /// @param instanceNameNode The instance name of the node. - Node* getNode( const std::string& instanceNameNode ); + /// Gets a specific node according to its instance name. + /// \param instanceNameNode The instance name of the node. + Node* getNode( const std::string& instanceNameNode ) const; /// Gets the nodes ordered by level (after compilation) const std::vector>* getNodesByLevel() const; @@ -101,7 +109,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node bool compile() override; /// Gets the number of nodes - size_t getNodesCount(); + size_t getNodesCount() const; /// Deletes all nodes from the render graph. virtual void clearNodes(); @@ -119,7 +127,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Allows to set data to the graph from the caller . /// \note As ownership is shared with the caller, the graph must survive the returned /// pointer to be able to use the dataSetter.. - /// @params portName The name of the input port of the graph + /// \params portName The name of the input port of the graph std::shared_ptr getDataSetter( std::string portName ); /// \brief disconnect the data setting port from its inputs. @@ -131,7 +139,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Allows to get the data stored at this port after the execution of the graph. /// \note ownership is left to the graph. The graph must survive the returned /// pointer to be able to use the dataGetter.. - /// @params portName the name of the output port + /// \params portName the name of the output port PortBase* getDataGetter( std::string portName ); using DataSetterDesc = std::tuple, std::string, std::string>; @@ -169,20 +177,20 @@ class RA_DATAFLOW_API DataflowGraph : public Node // Internal helper functions /// Internal compilation function that allows to go back in the render graph while filling an /// information map. - /// @param current The current node. - /// @param infoNodes The map that contains information about nodes. + /// \param current The current node. + /// \param infoNodes The map that contains information about nodes. void backtrackGraph( Node* current, std::unordered_map>>& infoNodes ); /// Internal compilation function that allows to go through the render graph, using an /// information map. - /// @param current The current node. - /// @param infoNodes The map that contains information about nodes. + /// \param current The current node. + /// \param infoNodes The map that contains information about nodes. int goThroughGraph( Node* current, std::unordered_map>>& infoNodes ); /// Returns the index of the given node in the graph. /// if there is none, returns -1. - /// @param name The name of the node to find. - int findNode( const Node* node ); + /// \param name The name of the node to find. + int findNode( const Node* node ) const; /// Data setters management : used to pass parameter to the graph when the graph is not embedded /// into another graph (inputs are here for this case). diff --git a/src/Dataflow/Core/DataflowGraph.inl b/src/Dataflow/Core/DataflowGraph.inl index 274bec42f41..4c16dab78e1 100644 --- a/src/Dataflow/Core/DataflowGraph.inl +++ b/src/Dataflow/Core/DataflowGraph.inl @@ -8,7 +8,7 @@ namespace Core { inline void DataflowGraph::setNodeFactories( std::shared_ptr factories ) { m_factories = factories; } -inline std::shared_ptr DataflowGraph::getNodeFactories() { +inline std::shared_ptr DataflowGraph::getNodeFactories() const { return m_factories; } @@ -28,7 +28,7 @@ inline const std::vector>* DataflowGraph::getNodes() const inline const std::vector>* DataflowGraph::getNodesByLevel() const { return &m_nodesByLevel; } -inline size_t DataflowGraph::getNodesCount() { +inline size_t DataflowGraph::getNodesCount() const { return m_nodes.size(); } inline const std::string& DataflowGraph::getTypename() { From 952ed6917721cc99ac3b4a7fd8335b175d9990b9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 11 Jan 2023 14:16:52 +0100 Subject: [PATCH 097/239] [dataflow-core] refactor removeNode --- src/Dataflow/Core/DataflowGraph.cpp | 3 ++- src/Dataflow/Core/DataflowGraph.hpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 44364f2cce6..7d6072f5a29 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -270,7 +270,7 @@ bool DataflowGraph::addNode( Node* newNode ) { } } -bool DataflowGraph::removeNode( Node* node ) { +bool DataflowGraph::removeNode( Node*& node ) { // This is to prevent graph destruction from the graph editor, depending on how it is used if ( m_nodesAndLinksProtected ) { return false; } @@ -305,6 +305,7 @@ bool DataflowGraph::removeNode( Node* node ) { } } m_nodes.erase( m_nodes.begin() + index ); + node = nullptr; m_ready = false; m_shouldBeSaved = true; return true; diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 487e7c78bcb..b46780d08b9 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -75,7 +75,8 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Removes a node from the render graph. Removes input and output ports of the graph /// corresponding to interface ports of the node. /// \param node The node to remove from the render graph. - virtual bool removeNode( Node* node ); + /// \return true if the node was removed and the given pointer is set to nullptr, false else + virtual bool removeNode( Node*& node ); /// Connects two nodes of the render graph. /// The two nodes must already be in the render graph (with the addNode(Node* newNode) /// function), the first node's in port must be free and the connected in port and out port must From a3751258057c4f1bde3ae6a9d6169051df66be88 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 11 Jan 2023 14:17:05 +0100 Subject: [PATCH 098/239] [unittests] refactor removeNode --- tests/unittest/Dataflow/graphinspect.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unittest/Dataflow/graphinspect.cpp b/tests/unittest/Dataflow/graphinspect.cpp index f333f796e16..9202fd3d91b 100644 --- a/tests/unittest/Dataflow/graphinspect.cpp +++ b/tests/unittest/Dataflow/graphinspect.cpp @@ -77,6 +77,7 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { REQUIRE( n->getInstanceName() == "validation value" ); c = g.removeNode( n ); REQUIRE( c == true ); + REQUIRE( n == nullptr ); c = g.compile(); REQUIRE( c == true ); // Simplified graph after compilation From 493343ac174675bf5f3959b6ebd5050cfcff7450 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 14:18:22 +0100 Subject: [PATCH 099/239] [dataflow] explicitly transfer ownership when adding a node to the graph --- src/Dataflow/Core/DataflowGraph.cpp | 19 ++++++++++--------- src/Dataflow/Core/DataflowGraph.hpp | 5 ++--- src/Dataflow/Core/NodeFactory.cpp | 5 ++++- .../QtGui/GraphEditor/GraphEditorView.cpp | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 7d6072f5a29..0101be1f4d6 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -242,31 +242,32 @@ bool DataflowGraph::canAdd( const Node* newNode ) const { return findNode( newNode ) == -1; }; -bool DataflowGraph::addNode( Node* newNode ) { +std::pair DataflowGraph::addNode( std::unique_ptr newNode ) { std::map m_mapInputs; // Check if the new node already exists (= same name and type) - if ( findNode( newNode ) == -1 ) { - if ( newNode->getInputs().empty() ) { + if ( canAdd( newNode.get() ) ) { + auto addedNode = newNode.get(); + m_nodes.emplace_back( std::move( newNode ) ); + if ( addedNode->getInputs().empty() ) { // it is a source node, add its interface port as input and data setter to the graph - auto& interfaces = newNode->buildInterfaces( this ); + auto& interfaces = addedNode->buildInterfaces( this ); for ( auto p : interfaces ) { addSetter( p ); } } - if ( newNode->getOutputs().empty() ) { + if ( addedNode->getOutputs().empty() ) { // it is a sink node, add its interface port as output to the graph - auto& interfaces = newNode->buildInterfaces( this ); + auto& interfaces = addedNode->buildInterfaces( this ); for ( auto p : interfaces ) { addGetter( p ); } } - m_nodes.emplace_back( newNode ); m_ready = false; m_shouldBeSaved = true; - return true; + return { true, addedNode }; } else { - return false; + return { false, newNode.get() }; } } diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index b46780d08b9..061321fa661 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -69,9 +69,8 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Adds a node to the render graph. Adds interface ports to the node newNode and the /// corresponding input and output ports to the graph. /// \param newNode The node to add to the render graph. - /// ownership of the node is transfered to the graph - /// \todo move ine unique_ptr and return an optional with the raw pointer - virtual bool addNode( Node* newNode ); + /// ownership of the node is transferred to the graph + virtual std::pair addNode( std::unique_ptr newNode ); /// Removes a node from the render graph. Removes input and output ports of the graph /// corresponding to interface ports of the node. /// \param node The node to remove from the render graph. diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index ae0b2923afc..23da62176e6 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -24,7 +24,10 @@ Node* NodeFactory::createNode( std::string& nodeType, auto it = m_nodesCreators.find( nodeType ); if ( it != m_nodesCreators.end() ) { auto node = it->second.first( data ); - if ( owningGraph != nullptr ) { owningGraph->addNode( node ); } + if ( owningGraph != nullptr ) { + auto [added, n] = owningGraph->addNode( std::unique_ptr( node ) ); + return n; + } return node; } return nullptr; diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 2b4508e2651..506a61b6225 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -88,7 +88,7 @@ void GraphEditorView::buildAdapterRegistry( const NodeFactorySet& factories ) { [f, this, creatorFactory]() -> std::unique_ptr { nlohmann::json data; auto node = f( data ); - this->m_dataflowGraph->addNode( node ); + this->m_dataflowGraph->addNode( std::unique_ptr( node ) ); this->m_dataflowGraph->addFactory( creatorFactory ); return std::make_unique( this->m_dataflowGraph, node ); }, From f081999c232323eeac8dd2e94eb7a6e320380c14 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 14:18:54 +0100 Subject: [PATCH 100/239] [unittests] explicitly transfer ownership when adding a node to the graph --- tests/unittest/Dataflow/customnodes.cpp | 14 ++++---- tests/unittest/Dataflow/nodes.cpp | 38 ++++++++++----------- tests/unittest/Dataflow/serialization.cpp | 8 ++--- tests/unittest/Dataflow/sourcesandsinks.cpp | 7 ++-- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index c36beaf83f3..94358c79e7a 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -137,13 +137,13 @@ DataflowGraph* buildgraph( const std::string& name ) { auto fl = new Functionals::FilterNode>( "fl" ); auto g = new DataflowGraph( name ); - g->addNode( ds ); - g->addNode( rs ); - g->addNode( ts ); - g->addNode( ss ); - g->addNode( nm ); - g->addNode( fs ); - g->addNode( fl ); + g->addNode( std::unique_ptr( ds ) ); + g->addNode( std::unique_ptr( rs ) ); + g->addNode( std::unique_ptr( ts ) ); + g->addNode( std::unique_ptr( ss ) ); + g->addNode( std::unique_ptr( nm ) ); + g->addNode( std::unique_ptr( fs ) ); + g->addNode( std::unique_ptr( fl ) ); bool ok; ok = g->addLink( ds, "to", fl, "in" ); diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index de99615f16a..25940aabc0f 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -22,23 +22,23 @@ createGraph( auto g = new DataflowGraph { name }; auto source_a = new Sources::SingleDataSourceNode( "a" ); - g->addNode( source_a ); + g->addNode( std::unique_ptr( source_a ) ); auto a = g->getDataSetter( "a_to" ); REQUIRE( a->getNode() == g ); auto source_b = new Sources::SingleDataSourceNode( "b" ); - g->addNode( source_b ); + g->addNode( std::unique_ptr( source_b ) ); auto b = g->getDataSetter( "b_to" ); REQUIRE( b->getNode() == g ); auto sink = new Sinks::SinkNode( "r" ); - g->addNode( sink ); + g->addNode( std::unique_ptr( sink ) ); auto r = g->getDataGetter( "r_from" ); REQUIRE( r->getNode() == g ); auto op = new TestNode( "operator", f ); // op->setOperator( f ); - g->addNode( op ); + g->addNode( std::unique_ptr( op ) ); REQUIRE( g->addLink( source_a, "to", op, "a" ) ); REQUIRE( g->addLink( op, "r", sink, "from" ) ); @@ -196,9 +196,9 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { auto opNode = dynamic_cast( g->getNode( "operator" ) ); REQUIRE( opNode != nullptr ); if ( opNode ) { - typename TestNode::BinaryOperator f = []( typename TestNode::Arg1_type a, - typename TestNode::Arg2_type b ) -> - typename TestNode::Res_type { return a / b; }; + typename TestNode::BinaryOperator f = []( typename TestNode::Arg1_type arg1, + typename TestNode::Arg2_type arg2 ) -> + typename TestNode::Res_type { return arg1 / arg2; }; opNode->setOperator( f ); } g->execute(); @@ -318,15 +318,15 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { // Node for coparing the results of the computation graph auto validator = new Functionals::BinaryOpNode( "validator" ); - g->addNode( nodeS ); - g->addNode( nodeD ); - g->addNode( nodeN ); - g->addNode( nodeM ); - g->addNode( nodeR ); - g->addNode( meanCalculator ); - g->addNode( doubleMeanCalculator ); - g->addNode( nodeT ); - g->addNode( nodeRD ); + g->addNode( std::unique_ptr( nodeS ) ); + g->addNode( std::unique_ptr( nodeD ) ); + g->addNode( std::unique_ptr( nodeN ) ); + g->addNode( std::unique_ptr( nodeM ) ); + g->addNode( std::unique_ptr( nodeR ) ); + g->addNode( std::unique_ptr( meanCalculator ) ); + g->addNode( std::unique_ptr( doubleMeanCalculator ) ); + g->addNode( std::unique_ptr( nodeT ) ); + g->addNode( std::unique_ptr( nodeRD ) ); bool linkAdded; linkAdded = g->addLink( nodeS, "to", meanCalculator, "in" ); @@ -348,9 +348,9 @@ TEST_CASE( "Dataflow/Core/Nodes", "[Dataflow][Core][Nodes]" ) { linkAdded = g->addLink( nodeM, "f", doubleMeanCalculator, "f" ); REQUIRE( linkAdded == true ); - g->addNode( nodePred ); - g->addNode( sinkB ); - g->addNode( validator ); + g->addNode( std::unique_ptr( nodePred ) ); + g->addNode( std::unique_ptr( sinkB ) ); + g->addNode( std::unique_ptr( validator ) ); linkAdded = g->addLink( meanCalculator, "out", validator, "a" ); REQUIRE( linkAdded == true ); linkAdded = g->addLink( doubleMeanCalculator, "out", validator, "b" ); diff --git a/tests/unittest/Dataflow/serialization.cpp b/tests/unittest/Dataflow/serialization.cpp index 2293f6ba5dd..b01e2e54193 100644 --- a/tests/unittest/Dataflow/serialization.cpp +++ b/tests/unittest/Dataflow/serialization.cpp @@ -20,13 +20,13 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { DataflowGraph g { "original graph" }; auto source_a = new Sources::SingleDataSourceNode( "a" ); - g.addNode( source_a ); + g.addNode( std::unique_ptr( source_a ) ); auto a = g.getDataSetter( "a_to" ); auto source_b = new Sources::SingleDataSourceNode( "b" ); - g.addNode( source_b ); + g.addNode( std::unique_ptr( source_b ) ); auto b = g.getDataSetter( "b_to" ); auto sink = new Sinks::SinkNode( "r" ); - g.addNode( sink ); + g.addNode( std::unique_ptr( sink ) ); auto r = g.getDataGetter( "r_from" ); using TestNode = Functionals::BinaryOpNode; TestNode::BinaryOperator add = []( TestNode::Arg1_type a, @@ -35,7 +35,7 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { }; auto op = new TestNode( "addition" ); op->setOperator( add ); - g.addNode( op ); + g.addNode( std::unique_ptr( op ) ); g.addLink( source_a, "to", op, "a" ); g.addLink( op, "r", sink, "from" ); g.addLink( source_b, "to", op, "b" ); diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp index e628d0d81c1..b71bc6412b7 100644 --- a/tests/unittest/Dataflow/sourcesandsinks.cpp +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -16,10 +16,9 @@ template void testGraph( const std::string& name, T in, T& out ) { auto g = new DataflowGraph { name }; auto source = new Sources::SingleDataSourceNode( "in" ); - - auto sink = new Sinks::SinkNode( "out" ); - g->addNode( source ); - g->addNode( sink ); + auto sink = new Sinks::SinkNode( "out" ); + g->addNode( std::unique_ptr( source ) ); + g->addNode( std::unique_ptr( sink ) ); auto linked = g->addLink( source, "to", sink, "from" ); if ( !linked ) { std::cerr << "Error linking source and sink nodes.\n"; } REQUIRE( linked ); From e265a496c26cfc307db7ff1376159c2a6d21b1ee Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 14:19:11 +0100 Subject: [PATCH 101/239] [examples] explicitly transfer ownership when adding a node to the graph --- .../FunctionalsGraph/main.cpp | 12 +++++------ .../GraphSerialization/main.cpp | 6 +++--- examples/DataflowExamples/HelloGraph/main.cpp | 21 ++++++++++++------- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/examples/DataflowExamples/FunctionalsGraph/main.cpp b/examples/DataflowExamples/FunctionalsGraph/main.cpp index f4758de48ca..136cd27ed9c 100644 --- a/examples/DataflowExamples/FunctionalsGraph/main.cpp +++ b/examples/DataflowExamples/FunctionalsGraph/main.cpp @@ -37,12 +37,12 @@ int main( int argc, char* argv[] ) { //! [Creating Nodes] //! [Adding Nodes to the graph] - g.addNode( sourceNode ); - g.addNode( mapSource ); - g.addNode( transformNode ); - g.addNode( reduceNode ); - g.addNode( sinkNode ); - g.addNode( scalarSinkNode ); + g.addNode( std::unique_ptr( sourceNode ) ); + g.addNode( std::unique_ptr( mapSource ) ); + g.addNode( std::unique_ptr( transformNode ) ); + g.addNode( std::unique_ptr( reduceNode ) ); + g.addNode( std::unique_ptr( sinkNode ) ); + g.addNode( std::unique_ptr( scalarSinkNode ) ); //! [Adding Nodes to the graph] //! [Creating links between Nodes] diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp index 950cf251f85..09b19982ed4 100644 --- a/examples/DataflowExamples/GraphSerialization/main.cpp +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -51,9 +51,9 @@ int main( int argc, char* argv[] ) { //! [Creating Nodes] //! [Adding Nodes to the graph] - g.addNode( sourceNode ); - g.addNode( filterNode ); - g.addNode( sinkNode ); + g.addNode( std::unique_ptr( sourceNode ) ); + g.addNode( std::unique_ptr( filterNode ) ); + g.addNode( std::unique_ptr( sinkNode ) ); //! [Adding Nodes to the graph] //! [Creating links between Nodes] diff --git a/examples/DataflowExamples/HelloGraph/main.cpp b/examples/DataflowExamples/HelloGraph/main.cpp index 834d554ff66..fdceb6aa4f4 100644 --- a/examples/DataflowExamples/HelloGraph/main.cpp +++ b/examples/DataflowExamples/HelloGraph/main.cpp @@ -16,17 +16,22 @@ int main( int argc, char* argv[] ) { //! [Creating an empty graph] //! [Creating Nodes] - auto sourceNode = new Sources::SingleDataSourceNode( "Source" ); - auto predicateNode = new Sources::ScalarUnaryPredicateSource( "Selector" ); - auto filterNode = new Functionals::FilterNode( "Filter" ); - auto sinkNode = new Sinks::SinkNode( "Sink" ); + auto addedSourceNode = std::make_unique>( "Source" ); + auto addedPredicateNode = std::make_unique( "Selector" ); + auto addedFilterNode = std::make_unique>( "Filter" ); + auto addedSinkNode = std::make_unique>( "Sink" ); + // get non-owning aliases to ease future node management + auto sourceNode = addedSourceNode.get(); + auto predicateNode = addedPredicateNode.get(); + auto filterNode = addedFilterNode.get(); + auto sinkNode = addedSinkNode.get(); //! [Creating Nodes] //! [Adding Nodes to the graph] - g.addNode( sourceNode ); - g.addNode( predicateNode ); - g.addNode( filterNode ); - g.addNode( sinkNode ); + g.addNode( std::move( addedSourceNode ) ); + g.addNode( std::move( addedPredicateNode ) ); + g.addNode( std::move( addedFilterNode ) ); + g.addNode( std::move( addedSinkNode ) ); //! [Adding Nodes to the graph] //! [Creating links between Nodes] From 4064edfbac5f3efa3e41d77672ec4d4f23308c7a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 15:04:42 +0100 Subject: [PATCH 102/239] [dataflow-core] remove .inl files --- src/Dataflow/Core/CMakeLists.txt | 4 +- src/Dataflow/Core/DataflowGraph.hpp | 36 ++- src/Dataflow/Core/DataflowGraph.inl | 41 ---- src/Dataflow/Core/EditableParameter.hpp | 21 +- src/Dataflow/Core/EditableParameter.inl | 26 --- src/Dataflow/Core/Enumerator.hpp | 57 ++++- src/Dataflow/Core/Enumerator.inl | 62 ------ src/Dataflow/Core/Node.hpp | 136 +++++++++++- src/Dataflow/Core/Node.inl | 142 ------------ src/Dataflow/Core/NodeFactory.hpp | 70 +++++- src/Dataflow/Core/NodeFactory.inl | 75 ------- .../Core/Nodes/Functionals/FilterNode.hpp | 72 +++++- .../Core/Nodes/Functionals/FilterNode.inl | 81 ------- .../Core/Nodes/Functionals/ReduceNode.hpp | 80 ++++++- .../Core/Nodes/Functionals/ReduceNode.inl | 87 -------- .../Core/Nodes/Functionals/TransformNode.hpp | 71 +++++- .../Core/Nodes/Functionals/TransformNode.inl | 78 ------- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 54 ++++- src/Dataflow/Core/Nodes/Sinks/SinkNode.inl | 63 ------ .../Core/Nodes/Sources/FunctionSource.hpp | 59 ++++- .../Core/Nodes/Sources/FunctionSource.inl | 67 ------ .../Nodes/Sources/SingleDataSourceNode.hpp | 77 ++++++- .../Nodes/Sources/SingleDataSourceNode.inl | 85 ------- src/Dataflow/Core/Port.hpp | 204 ++++++++++++++++- src/Dataflow/Core/Port.inl | 210 ------------------ src/Dataflow/Core/TypeDemangler.hpp | 26 ++- src/Dataflow/Core/TypeDemangler.inl | 30 --- src/Dataflow/Core/filelist.cmake | 16 -- 28 files changed, 939 insertions(+), 1091 deletions(-) delete mode 100644 src/Dataflow/Core/DataflowGraph.inl delete mode 100644 src/Dataflow/Core/EditableParameter.inl delete mode 100644 src/Dataflow/Core/Enumerator.inl delete mode 100644 src/Dataflow/Core/Node.inl delete mode 100644 src/Dataflow/Core/NodeFactory.inl delete mode 100644 src/Dataflow/Core/Nodes/Functionals/FilterNode.inl delete mode 100644 src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl delete mode 100644 src/Dataflow/Core/Nodes/Functionals/TransformNode.inl delete mode 100644 src/Dataflow/Core/Nodes/Sinks/SinkNode.inl delete mode 100644 src/Dataflow/Core/Nodes/Sources/FunctionSource.inl delete mode 100644 src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl delete mode 100644 src/Dataflow/Core/Port.inl delete mode 100644 src/Dataflow/Core/TypeDemangler.inl diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt index 3d3b0c8a0f0..36699772aad 100644 --- a/src/Dataflow/Core/CMakeLists.txt +++ b/src/Dataflow/Core/CMakeLists.txt @@ -7,7 +7,7 @@ include(filelist.cmake) add_library( ${ra_dataflowcore_target} SHARED ${dataflow_core_sources} ${dataflow_core_headers} - ${dataflow_core_inlines} ${dataflow_core_private} + ${dataflow_core_private} ) find_package(stduuid REQUIRED NO_DEFAULT_PATH) @@ -28,7 +28,7 @@ configure_radium_target(${ra_dataflowcore_target}) # configure the library only. The package is a sub-package and should be configured independently configure_radium_library( TARGET ${ra_dataflowcore_target} COMPONENT TARGET_DIR "Dataflow/Core" - FILES "${dataflow_core_headers};${dataflow_core_inlines}" + FILES "${dataflow_core_headers}" ) # Generate cmake package configure_radium_package( diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 061321fa661..71dbbe3d8ca 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -240,8 +240,40 @@ class RA_DATAFLOW_API DataflowGraph : public Node bool m_nodesAndLinksProtected { false }; }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +inline void DataflowGraph::setNodeFactories( std::shared_ptr factories ) { + m_factories = factories; +} +inline std::shared_ptr DataflowGraph::getNodeFactories() const { + return m_factories; +} + +inline void DataflowGraph::addFactory( const std::string& name, std::shared_ptr f ) { + if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } + m_factories->addFactory( name, f ); +} + +inline void DataflowGraph::addFactory( std::shared_ptr f ) { + if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } + m_factories->addFactory( f->getName(), f ); +} + +inline const std::vector>* DataflowGraph::getNodes() const { + return &m_nodes; +} +inline const std::vector>* DataflowGraph::getNodesByLevel() const { + return &m_nodesByLevel; +} +inline size_t DataflowGraph::getNodesCount() const { + return m_nodes.size(); +} +inline const std::string& DataflowGraph::getTypename() { + static std::string demangledTypeName { "Core DataflowGraph" }; + return demangledTypeName; +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/DataflowGraph.inl b/src/Dataflow/Core/DataflowGraph.inl deleted file mode 100644 index 4c16dab78e1..00000000000 --- a/src/Dataflow/Core/DataflowGraph.inl +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { - -inline void DataflowGraph::setNodeFactories( std::shared_ptr factories ) { - m_factories = factories; -} -inline std::shared_ptr DataflowGraph::getNodeFactories() const { - return m_factories; -} - -inline void DataflowGraph::addFactory( const std::string& name, std::shared_ptr f ) { - if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } - m_factories->addFactory( name, f ); -} - -inline void DataflowGraph::addFactory( std::shared_ptr f ) { - if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } - m_factories->addFactory( f->getName(), f ); -} - -inline const std::vector>* DataflowGraph::getNodes() const { - return &m_nodes; -} -inline const std::vector>* DataflowGraph::getNodesByLevel() const { - return &m_nodesByLevel; -} -inline size_t DataflowGraph::getNodesCount() const { - return m_nodes.size(); -} -inline const std::string& DataflowGraph::getTypename() { - static std::string demangledTypeName { "Core DataflowGraph" }; - return demangledTypeName; -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp index 955693cdf49..c853c0bdad0 100644 --- a/src/Dataflow/Core/EditableParameter.hpp +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -62,8 +62,25 @@ struct EditableParameter : public EditableParameterBase { std::vector additionalData; }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +inline EditableParameterBase::EditableParameterBase( std::string& name, size_t hashedType ) : + m_name( name ), m_hashedType( hashedType ) {} + +template +EditableParameter::EditableParameter( std::string name, T& data ) : + EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {} + +template +void EditableParameter::addAdditionalData( T newData ) { + additionalData.push_back( newData ); +} + +inline std::string EditableParameterBase::getName() { + return m_name; +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/EditableParameter.inl b/src/Dataflow/Core/EditableParameter.inl deleted file mode 100644 index e14d7e9fdcf..00000000000 --- a/src/Dataflow/Core/EditableParameter.inl +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { - -inline EditableParameterBase::EditableParameterBase( std::string& name, size_t hashedType ) : - m_name( name ), m_hashedType( hashedType ) {} - -template -EditableParameter::EditableParameter( std::string name, T& data ) : - EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {} - -template -void EditableParameter::addAdditionalData( T newData ) { - additionalData.push_back( newData ); -} - -inline std::string EditableParameterBase::getName() { - return m_name; -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Enumerator.hpp b/src/Dataflow/Core/Enumerator.hpp index 2bc55939ad4..2b9c1ea570b 100644 --- a/src/Dataflow/Core/Enumerator.hpp +++ b/src/Dataflow/Core/Enumerator.hpp @@ -35,8 +35,61 @@ class Enumerator : public Ra::Core::Utils::Observable&> const T& operator[]( size_t p ) const; }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +Enumerator::Enumerator( std::initializer_list values ) : + m_values { values }, m_currentValue { m_values.data() } {} + +template +const T& Enumerator::get() const { + return *m_currentValue; +} + +template +size_t Enumerator::size() const { + return m_values.size(); +} + +template +bool Enumerator::set( size_t p ) { + if ( p < m_values.size() ) { + m_currentValue = m_values.data() + p; + m_currentIndex = p; + this->notify( *this ); + return true; + } + else { + return false; + } +} + +template +bool Enumerator::set( const T& v ) { + size_t p = 0; + for ( const auto& e : m_values ) { + if ( e == v ) { return set( p ); } + p++; + } + return false; +} + +template +typename std::vector::const_iterator Enumerator::begin() const { + return m_values.cbegin(); +} + +template +typename std::vector::const_iterator Enumerator::end() const { + return m_values.cend(); +} + +template +const T& Enumerator::operator[]( size_t p ) const { + return m_values.at( p ); +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Enumerator.inl b/src/Dataflow/Core/Enumerator.inl deleted file mode 100644 index 9c6e855d13e..00000000000 --- a/src/Dataflow/Core/Enumerator.inl +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { - -template -Enumerator::Enumerator( std::initializer_list values ) : - m_values { values }, m_currentValue { m_values.data() } {} - -template -const T& Enumerator::get() const { - return *m_currentValue; -} - -template -size_t Enumerator::size() const { - return m_values.size(); -} - -template -bool Enumerator::set( size_t p ) { - if ( p < m_values.size() ) { - m_currentValue = m_values.data() + p; - m_currentIndex = p; - this->notify( *this ); - return true; - } - else { - return false; - } -} - -template -bool Enumerator::set( const T& v ) { - size_t p = 0; - for ( const auto& e : m_values ) { - if ( e == v ) { return set( p ); } - p++; - } - return false; -} - -template -typename std::vector::const_iterator Enumerator::begin() const { - return m_values.cbegin(); -} - -template -typename std::vector::const_iterator Enumerator::end() const { - return m_values.cend(); -} - -template -const T& Enumerator::operator[]( size_t p ) const { - return m_values.at( p ); -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 24e84e8de6d..3b8008d0c42 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -225,8 +226,139 @@ class RA_DATAFLOW_API Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +inline void Node::init() { + m_initialized = true; +} + +inline void Node::destroy() { + m_interface.clear(); + m_inputs.clear(); + m_outputs.clear(); + m_editableParameters.clear(); +} + +inline const nlohmann::json& Node::getJsonMetaData() { + return m_extraJsonData; +} + +inline const std::string& Node::getTypeName() const { + return m_typeName; +} + +inline const std::string& Node::getInstanceName() const { + return m_instanceName; +} + +inline void Node::setInstanceName( const std::string& newName ) { + m_instanceName = newName; +} + +inline const std::vector>& Node::getInputs() { + return m_inputs; +} + +inline const std::vector>& Node::getOutputs() { + return m_outputs; +} + +inline const std::vector& Node::buildInterfaces( Node* parent ) { + m_interface.clear(); + m_interface.shrink_to_fit(); + std::vector>* readFrom; + if ( m_inputs.empty() ) { readFrom = &m_outputs; } + else { + readFrom = &m_inputs; + } + m_interface.reserve( readFrom->size() ); + for ( const auto& p : *readFrom ) { + // Todo, ensure thereis no twice the same port .... + m_interface.emplace_back( p->reflect( parent, getInstanceName() + '_' + p->getName() ) ); + } + return m_interface; +} + +inline const std::vector& Node::getInterfaces() const { + return m_interface; +} + +inline const std::vector>& Node::getEditableParameters() { + return m_editableParameters; +} + +inline bool Node::operator==( const Node& o_node ) { + return ( m_typeName == o_node.getTypeName() ) && ( m_instanceName == o_node.getInstanceName() ); +} + +inline bool Node::addInput( PortBase* in ) { + if ( !in->is_input() ) { return false; } + bool found = false; + for ( auto& input : m_inputs ) { + if ( input->getName() == in->getName() ) { found = true; } + } + if ( !found ) { m_inputs.emplace_back( in ); } + return !found; +} + +template +void Node::addOutput( PortOut* out, T* data ) { + bool found = false; + for ( auto& output : m_outputs ) { + if ( output->getName() == out->getName() ) { found = true; } + } + if ( !found ) { + m_outputs.emplace_back( out ); + out->setData( data ); + } +} + +inline bool Node::addEditableParameter( EditableParameterBase* editableParameter ) { + bool found = false; + for ( auto& edit : m_editableParameters ) { + if ( edit.get()->getName() == editableParameter->getName() ) { found = true; } + } + if ( !found ) { m_editableParameters.emplace_back( editableParameter ); } + return !found; +} + +inline bool Node::removeEditableParameter( const std::string& name ) { + bool found = false; + auto it = m_editableParameters.begin(); + while ( it != m_editableParameters.end() ) { + if ( ( *it ).get()->getName() == name ) { + m_editableParameters.erase( it ); + found = true; + break; + } + ++it; + } + return found; +} + +template +inline EditableParameter* Node::getEditableParameter( const std::string& name ) { + auto it = m_editableParameters.begin(); + while ( it != m_editableParameters.end() ) { + if ( ( *it ).get()->getName() == name ) { + auto p = dynamic_cast*>( ( *it ).get() ); + if ( p != nullptr ) { return *p; } + } + ++it; + } + return nullptr; +} + +inline const std::string& Node::getTypename() { + static std::string demangledTypeName { "Abstract Node" }; + return demangledTypeName; +} + +inline bool Node::compile() { + return true; +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Node.inl b/src/Dataflow/Core/Node.inl deleted file mode 100644 index a70486f42bf..00000000000 --- a/src/Dataflow/Core/Node.inl +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once -#include - -#include - -namespace Ra { -namespace Dataflow { -namespace Core { - -inline void Node::init() { - m_initialized = true; -} - -inline void Node::destroy() { - m_interface.clear(); - m_inputs.clear(); - m_outputs.clear(); - m_editableParameters.clear(); -} - -inline const nlohmann::json& Node::getJsonMetaData() { - return m_extraJsonData; -} - -inline const std::string& Node::getTypeName() const { - return m_typeName; -} - -inline const std::string& Node::getInstanceName() const { - return m_instanceName; -} - -inline void Node::setInstanceName( const std::string& newName ) { - m_instanceName = newName; -} - -inline const std::vector>& Node::getInputs() { - return m_inputs; -} - -inline const std::vector>& Node::getOutputs() { - return m_outputs; -} - -inline const std::vector& Node::buildInterfaces( Node* parent ) { - m_interface.clear(); - m_interface.shrink_to_fit(); - std::vector>* readFrom; - if ( m_inputs.empty() ) { readFrom = &m_outputs; } - else { - readFrom = &m_inputs; - } - m_interface.reserve( readFrom->size() ); - for ( const auto& p : *readFrom ) { - // Todo, ensure thereis no twice the same port .... - m_interface.emplace_back( p->reflect( parent, getInstanceName() + '_' + p->getName() ) ); - } - return m_interface; -} - -inline const std::vector& Node::getInterfaces() const { - return m_interface; -} - -inline const std::vector>& Node::getEditableParameters() { - return m_editableParameters; -} - -inline bool Node::operator==( const Node& o_node ) { - return ( m_typeName == o_node.getTypeName() ) && ( m_instanceName == o_node.getInstanceName() ); -} - -inline bool Node::addInput( PortBase* in ) { - if ( !in->is_input() ) { return false; } - bool found = false; - for ( auto& input : m_inputs ) { - if ( input->getName() == in->getName() ) { found = true; } - } - if ( !found ) { m_inputs.emplace_back( in ); } - return !found; -} - -template -void Node::addOutput( PortOut* out, T* data ) { - bool found = false; - for ( auto& output : m_outputs ) { - if ( output->getName() == out->getName() ) { found = true; } - } - if ( !found ) { - m_outputs.emplace_back( out ); - out->setData( data ); - } -} - -inline bool Node::addEditableParameter( EditableParameterBase* editableParameter ) { - bool found = false; - for ( auto& edit : m_editableParameters ) { - if ( edit.get()->getName() == editableParameter->getName() ) { found = true; } - } - if ( !found ) { m_editableParameters.emplace_back( editableParameter ); } - return !found; -} - -inline bool Node::removeEditableParameter( const std::string& name ) { - bool found = false; - auto it = m_editableParameters.begin(); - while ( it != m_editableParameters.end() ) { - if ( ( *it ).get()->getName() == name ) { - m_editableParameters.erase( it ); - found = true; - break; - } - ++it; - } - return found; -} - -template -inline EditableParameter* Node::getEditableParameter( const std::string& name ) { - auto it = m_editableParameters.begin(); - while ( it != m_editableParameters.end() ) { - if ( ( *it ).get()->getName() == name ) { - auto p = dynamic_cast*>( ( *it ).get() ); - if ( p != nullptr ) { return *p; } - } - ++it; - } - return nullptr; -} - -inline const std::string& Node::getTypename() { - static std::string demangledTypeName { "Abstract Node" }; - return demangledTypeName; -} - -inline bool Node::compile() { - return true; -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index ca28c586373..7bb07fc9d12 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -236,8 +236,74 @@ RA_DATAFLOW_API bool unregisterFactory( NodeFactorySet::key_type factoryName ); RA_DATAFLOW_API NodeFactorySet::mapped_type getDataFlowBuiltInsFactory(); } // namespace NodeFactoriesManager +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +bool NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory ) { + return registerNodeCreator( T::getTypename(), std::move( nodeCreator ), nodeCategory ); +} + +template +bool NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, + const std::string& nodeCategory ) { + return registerNodeCreator( + T::getTypename(), + [this, instanceNamePrefix]( const nlohmann::json& data ) { + auto node = new T( instanceNamePrefix + std::to_string( this->nextNodeId() ) ); + node->fromJson( data ); + return node; + }, + nodeCategory ); +} + +inline const NodeFactory::ContainerType& NodeFactory::getFactoryMap() const { + return m_nodesCreators; +} + +inline bool NodeFactorySet::addFactory( NodeFactorySet::key_type factoryname, + NodeFactorySet::mapped_type factory ) { + const auto [loc, inserted] = insert( { std::move( factoryname ), std::move( factory ) } ); + return inserted; +} + +inline Ra::Core::Utils::optional +NodeFactorySet::hasFactory( const NodeFactorySet::key_type& factoryname ) { + auto f = m_factories.find( factoryname ); + if ( f != m_factories.end() ) { return f->second; } + else { + return {}; + } +} + +inline bool NodeFactorySet::removeFactory( const NodeFactorySet::key_type& factoryname ) { + return erase( factoryname ); +} +inline NodeFactorySet::const_iterator NodeFactorySet::begin() const { + return m_factories.begin(); +} +inline NodeFactorySet::const_iterator NodeFactorySet::end() const { + return m_factories.end(); +} +inline NodeFactorySet::const_iterator NodeFactorySet::cbegin() const { + return m_factories.cbegin(); +} +inline NodeFactorySet::const_iterator NodeFactorySet::cend() const { + return m_factories.cend(); +} +inline NodeFactorySet::const_iterator +NodeFactorySet::find( const NodeFactorySet::key_type& key ) const { + return m_factories.find( key ); +} +inline std::pair +NodeFactorySet::insert( NodeFactorySet::value_type value ) { + return m_factories.insert( std::move( value ) ); +} +inline size_t NodeFactorySet::erase( const NodeFactorySet::key_type& key ) { + return m_factories.erase( key ); +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/NodeFactory.inl b/src/Dataflow/Core/NodeFactory.inl deleted file mode 100644 index 6547969fc1f..00000000000 --- a/src/Dataflow/Core/NodeFactory.inl +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { - -template -bool NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, - const std::string& nodeCategory ) { - return registerNodeCreator( T::getTypename(), std::move( nodeCreator ), nodeCategory ); -} - -template -bool NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, - const std::string& nodeCategory ) { - return registerNodeCreator( - T::getTypename(), - [this, instanceNamePrefix]( const nlohmann::json& data ) { - auto node = new T( instanceNamePrefix + std::to_string( this->nextNodeId() ) ); - node->fromJson( data ); - return node; - }, - nodeCategory ); -} - -inline const NodeFactory::ContainerType& NodeFactory::getFactoryMap() const { - return m_nodesCreators; -} - -inline bool NodeFactorySet::addFactory( NodeFactorySet::key_type factoryname, - NodeFactorySet::mapped_type factory ) { - const auto [loc, inserted] = insert( { std::move( factoryname ), std::move( factory ) } ); - return inserted; -} - -inline Ra::Core::Utils::optional -NodeFactorySet::hasFactory( const NodeFactorySet::key_type& factoryname ) { - auto f = m_factories.find( factoryname ); - if ( f != m_factories.end() ) { return f->second; } - else { - return {}; - } -} - -inline bool NodeFactorySet::removeFactory( const NodeFactorySet::key_type& factoryname ) { - return erase( factoryname ); -} -inline NodeFactorySet::const_iterator NodeFactorySet::begin() const { - return m_factories.begin(); -} -inline NodeFactorySet::const_iterator NodeFactorySet::end() const { - return m_factories.end(); -} -inline NodeFactorySet::const_iterator NodeFactorySet::cbegin() const { - return m_factories.cbegin(); -} -inline NodeFactorySet::const_iterator NodeFactorySet::cend() const { - return m_factories.cend(); -} -inline NodeFactorySet::const_iterator -NodeFactorySet::find( const NodeFactorySet::key_type& key ) const { - return m_factories.find( key ); -} -inline std::pair -NodeFactorySet::insert( NodeFactorySet::value_type value ) { - return m_factories.insert( std::move( value ) ); -} -inline size_t NodeFactorySet::erase( const NodeFactorySet::key_type& key ) { - return m_factories.erase( key ); -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp index 09c148f71fb..f72f85ca906 100644 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/FilterNode.hpp @@ -75,9 +75,77 @@ class FilterNode : public Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +FilterNode::FilterNode( const std::string& instanceName ) : + FilterNode( instanceName, getTypename(), []( v_t ) { return true; } ) {} + +template +FilterNode::FilterNode( const std::string& instanceName, UnaryPredicate predicate ) : + FilterNode( instanceName, getTypename(), predicate ) {} + +template +void FilterNode::setFilterFunction( UnaryPredicate predicate ) { + m_predicate = predicate; +} + +template +void FilterNode::init() { + Node::init(); + m_elements.clear(); +} + +template +bool FilterNode::execute() { + auto f = m_portPredicate->isLinked() ? m_portPredicate->getData() : m_predicate; + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portIn->isLinked() ) { + const auto& inData = m_portIn->getData(); + m_elements.clear(); + // m_elements.reserve( inData.size() ); // --> this is not a requirement of + // SequenceContainer + std::copy_if( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); + } + return true; +} + +template +const std::string& FilterNode::getTypename() { + static std::string demangledName = + std::string { "Filter<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; +} + +template +FilterNode::FilterNode( const std::string& instanceName, + const std::string& typeName, + UnaryPredicate filterFunction ) : + Node( instanceName, typeName ), m_predicate( filterFunction ) { + + addInput( m_portIn ); + m_portIn->mustBeLinked(); + addInput( m_portPredicate ); + addOutput( m_portOut, &m_elements ); +} + +template +void FilterNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string { "Filtering function could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +bool FilterNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; +} + } // namespace Functionals } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl b/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl deleted file mode 100644 index 9c189cadb68..00000000000 --- a/src/Dataflow/Core/Nodes/Functionals/FilterNode.inl +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once -#include - -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Functionals { - -template -FilterNode::FilterNode( const std::string& instanceName ) : - FilterNode( instanceName, getTypename(), []( v_t ) { return true; } ) {} - -template -FilterNode::FilterNode( const std::string& instanceName, UnaryPredicate predicate ) : - FilterNode( instanceName, getTypename(), predicate ) {} - -template -void FilterNode::setFilterFunction( UnaryPredicate predicate ) { - m_predicate = predicate; -} - -template -void FilterNode::init() { - Node::init(); - m_elements.clear(); -} - -template -bool FilterNode::execute() { - auto f = m_portPredicate->isLinked() ? m_portPredicate->getData() : m_predicate; - // The following test will always be true if the node was integrated in a compiled graph - if ( m_portIn->isLinked() ) { - const auto& inData = m_portIn->getData(); - m_elements.clear(); - // m_elements.reserve( inData.size() ); // --> this is not a requirement of - // SequenceContainer - std::copy_if( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); - } - return true; -} - -template -const std::string& FilterNode::getTypename() { - static std::string demangledName = - std::string { "Filter<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; - return demangledName; -} - -template -FilterNode::FilterNode( const std::string& instanceName, - const std::string& typeName, - UnaryPredicate filterFunction ) : - Node( instanceName, typeName ), m_predicate( filterFunction ) { - - addInput( m_portIn ); - m_portIn->mustBeLinked(); - addInput( m_portPredicate ); - addOutput( m_portOut, &m_elements ); -} - -template -void FilterNode::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = - std::string { "Filtering function could not be serialized for " } + getTypeName(); - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG - << "Unable to save data when serializing a " << getTypeName() << "."; -} - -template -bool FilterNode::fromJsonInternal( const nlohmann::json& ) { - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG - << "Unable to read data when un-serializing a " << getTypeName() << "."; - return true; -} - -} // namespace Functionals -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp index 0649ab83dcd..15b34e279b5 100644 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.hpp @@ -84,9 +84,85 @@ class ReduceNode : public Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +ReduceNode::ReduceNode( const std::string& instanceName ) : + ReduceNode( + instanceName, + getTypename(), + []( const v_t& a, const v_t& ) -> v_t { return a; }, + v_t {} ) {} + +template +ReduceNode::ReduceNode( const std::string& instanceName, + ReduceOperator op, + v_t initialValue ) : + ReduceNode( instanceName, getTypename(), op, initialValue ) {} + +template +void ReduceNode::setOperator( ReduceOperator op, v_t initialValue ) { + m_operator = op; + m_init = initialValue; +} + +template +void ReduceNode::init() { + Node::init(); + m_result = m_init; +} + +template +bool ReduceNode::execute() { + auto f = m_portF->isLinked() ? m_portF->getData() : m_operator; + auto iv = m_portInit->isLinked() ? m_portInit->getData() : m_init; + m_result = iv; + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portIn->isLinked() ) { + const auto& inData = m_portIn->getData(); + m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); + } + return true; +} + +template +const std::string& ReduceNode::getTypename() { + static std::string demangledName = + std::string { "Reduce<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; +} + +template +ReduceNode::ReduceNode( const std::string& instanceName, + const std::string& typeName, + ReduceOperator op, + v_t initialValue ) : + Node( instanceName, typeName ), m_operator( op ), m_init( initialValue ) { + + addInput( m_portIn ); + m_portIn->mustBeLinked(); + addInput( m_portF ); + addInput( m_portInit ); + addOutput( m_portOut, &m_result ); +} + +template +void ReduceNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string { "Reduce operator could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +bool ReduceNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; +} + } // namespace Functionals } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl b/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl deleted file mode 100644 index 727b025b6df..00000000000 --- a/src/Dataflow/Core/Nodes/Functionals/ReduceNode.inl +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Functionals { - -template -ReduceNode::ReduceNode( const std::string& instanceName ) : - ReduceNode( - instanceName, - getTypename(), - []( const v_t& a, const v_t& ) -> v_t { return a; }, - v_t {} ) {} - -template -ReduceNode::ReduceNode( const std::string& instanceName, - ReduceOperator op, - v_t initialValue ) : - ReduceNode( instanceName, getTypename(), op, initialValue ) {} - -template -void ReduceNode::setOperator( ReduceOperator op, v_t initialValue ) { - m_operator = op; - m_init = initialValue; -} - -template -void ReduceNode::init() { - Node::init(); - m_result = m_init; -} - -template -bool ReduceNode::execute() { - auto f = m_portF->isLinked() ? m_portF->getData() : m_operator; - auto iv = m_portInit->isLinked() ? m_portInit->getData() : m_init; - m_result = iv; - // The following test will always be true if the node was integrated in a compiled graph - if ( m_portIn->isLinked() ) { - const auto& inData = m_portIn->getData(); - m_result = std::accumulate( inData.begin(), inData.end(), iv, f ); - } - return true; -} - -template -const std::string& ReduceNode::getTypename() { - static std::string demangledName = - std::string { "Reduce<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; - return demangledName; -} - -template -ReduceNode::ReduceNode( const std::string& instanceName, - const std::string& typeName, - ReduceOperator op, - v_t initialValue ) : - Node( instanceName, typeName ), m_operator( op ), m_init( initialValue ) { - - addInput( m_portIn ); - m_portIn->mustBeLinked(); - addInput( m_portF ); - addInput( m_portInit ); - addOutput( m_portOut, &m_result ); -} - -template -void ReduceNode::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = - std::string { "Reduce operator could not be serialized for " } + getTypeName(); - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG - << "Unable to save data when serializing a " << getTypeName() << "."; -} - -template -bool ReduceNode::fromJsonInternal( const nlohmann::json& ) { - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG - << "Unable to read data when un-serializing a " << getTypeName() << "."; - return true; -} - -} // namespace Functionals -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp index ea1e1be4e47..c2b1763de56 100644 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp +++ b/src/Dataflow/Core/Nodes/Functionals/TransformNode.hpp @@ -72,9 +72,76 @@ class TransformNode : public Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +TransformNode::TransformNode( const std::string& instanceName ) : + TransformNode( instanceName, getTypename(), []( v_t ) { return v_t {}; } ) {} + +template +TransformNode::TransformNode( const std::string& instanceName, TransformOperator op ) : + TransformNode( instanceName, getTypename(), op ) {} + +template +void TransformNode::setOperator( TransformOperator op ) { + m_operator = op; +} + +template +void TransformNode::init() { + Node::init(); + m_elements.clear(); +} + +template +bool TransformNode::execute() { + auto f = m_portOperator->isLinked() ? m_portOperator->getData() : m_operator; + // The following test will always be true if the node was integrated in a compiled graph + if ( m_portIn->isLinked() ) { + const auto& inData = m_portIn->getData(); + m_elements.clear(); + // m_elements.reserve( inData.size() ); // --> this is not a requirement of + // SequenceContainer + std::transform( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); + } + return true; +} + +template +const std::string& TransformNode::getTypename() { + static std::string demangledName = + std::string { "Transform<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; +} + +template +TransformNode::TransformNode( const std::string& instanceName, + const std::string& typeName, + TransformOperator op ) : + Node( instanceName, typeName ), m_operator( op ) { + addInput( m_portIn ); + m_portIn->mustBeLinked(); + addInput( m_portOperator ); + addOutput( m_portOut, &m_elements ); +} + +template +void TransformNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string { "Transform operator could not be serialized for " } + getTypeName(); + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +bool TransformNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG + << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; +} + } // namespace Functionals } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl b/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl deleted file mode 100644 index 0a2a0b71a56..00000000000 --- a/src/Dataflow/Core/Nodes/Functionals/TransformNode.inl +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Functionals { - -template -TransformNode::TransformNode( const std::string& instanceName ) : - TransformNode( instanceName, getTypename(), []( v_t ) { return v_t {}; } ) {} - -template -TransformNode::TransformNode( const std::string& instanceName, TransformOperator op ) : - TransformNode( instanceName, getTypename(), op ) {} - -template -void TransformNode::setOperator( TransformOperator op ) { - m_operator = op; -} - -template -void TransformNode::init() { - Node::init(); - m_elements.clear(); -} - -template -bool TransformNode::execute() { - auto f = m_portOperator->isLinked() ? m_portOperator->getData() : m_operator; - // The following test will always be true if the node was integrated in a compiled graph - if ( m_portIn->isLinked() ) { - const auto& inData = m_portIn->getData(); - m_elements.clear(); - // m_elements.reserve( inData.size() ); // --> this is not a requirement of - // SequenceContainer - std::transform( inData.begin(), inData.end(), std::back_inserter( m_elements ), f ); - } - return true; -} - -template -const std::string& TransformNode::getTypename() { - static std::string demangledName = - std::string { "Transform<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; - return demangledName; -} - -template -TransformNode::TransformNode( const std::string& instanceName, - const std::string& typeName, - TransformOperator op ) : - Node( instanceName, typeName ), m_operator( op ) { - addInput( m_portIn ); - m_portIn->mustBeLinked(); - addInput( m_portOperator ); - addOutput( m_portOut, &m_elements ); -} - -template -void TransformNode::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = - std::string { "Transform operator could not be serialized for " } + getTypeName(); - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG - << "Unable to save data when serializing a " << getTypeName() << "."; -} - -template -bool TransformNode::fromJsonInternal( const nlohmann::json& ) { - LOG( Ra::Core::Utils::logWARNING ) // TODO make this logDEBUG - << "Unable to read data when un-serializing a " << getTypeName() << "."; - return true; -} - -} // namespace Functionals -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index 31f253f211b..d2c2f0354cd 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -53,9 +53,59 @@ class SinkNode : public Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +SinkNode::SinkNode( const std::string& instanceName, const std::string& typeName ) : + Node( instanceName, typeName ) { + + m_portIn->mustBeLinked(); + addInput( m_portIn ); +} + +template +void SinkNode::init() { + // this should be done only once (or when the address of local data changes) + // What if interfaces were not created before init (e.g. call of init on a node not added to a + // graph) Todo : assert on the existence of the interface + auto interfacePort = static_cast*>( m_interface[0] ); + interfacePort->setData( &m_data ); + Node::init(); +} + +template +bool SinkNode::execute() { + m_data = m_portIn->getData(); + return true; +} + +template +T SinkNode::getData() const { + return m_data; +} + +template +const T& SinkNode::getDataByRef() const { + return m_data; +} + +template +const std::string& SinkNode::getTypename() { + static std::string demangledName = + std::string { "Sink<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledName; +} + +template +void SinkNode::toJsonInternal( nlohmann::json& ) const {} + +template +bool SinkNode::fromJsonInternal( const nlohmann::json& ) { + return true; +} + } // namespace Sinks } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl b/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl deleted file mode 100644 index 02a81dc394c..00000000000 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.inl +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once -#include - -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Sinks { - -template -SinkNode::SinkNode( const std::string& instanceName, const std::string& typeName ) : - Node( instanceName, typeName ) { - - m_portIn->mustBeLinked(); - addInput( m_portIn ); -} - -template -void SinkNode::init() { - // this should be done only once (or when the address of local data changes) - // What if interfaces were not created before init (e.g. call of init on a node not added to a - // graph) Todo : assert on the existence of the interface - auto interfacePort = static_cast*>( m_interface[0] ); - interfacePort->setData( &m_data ); - Node::init(); -} - -template -bool SinkNode::execute() { - m_data = m_portIn->getData(); - return true; -} - -template -T SinkNode::getData() const { - return m_data; -} - -template -const T& SinkNode::getDataByRef() const { - return m_data; -} - -template -const std::string& SinkNode::getTypename() { - static std::string demangledName = - std::string { "Sink<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; - return demangledName; -} - -template -void SinkNode::toJsonInternal( nlohmann::json& ) const {} - -template -bool SinkNode::fromJsonInternal( const nlohmann::json& ) { - return true; -} - -} // namespace Sinks -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index 4c7e62c924f..f608563334b 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -57,9 +57,64 @@ class FunctionSourceNode : public Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +FunctionSourceNode::FunctionSourceNode( const std::string& instanceName, + const std::string& typeName ) : + Node( instanceName, typeName ) { + addOutput( m_portOut, m_data ); +} + +template +bool FunctionSourceNode::execute() { + auto interfacePort = static_cast*>( m_interface[0] ); + if ( interfacePort->isLinked() ) { m_data = &( interfacePort->getData() ); } + else { + m_data = &m_localData; + } + m_portOut->setData( m_data ); + return true; +} + +template +void FunctionSourceNode::setData( function_type* data ) { + m_localData = *data; + m_data = &m_localData; + m_portOut->setData( m_data ); +} + +template +typename FunctionSourceNode::function_type* +FunctionSourceNode::getData() const { + return m_data; +} + +template +const std::string& FunctionSourceNode::getTypename() { + static std::string demangledTypeName = + std::string { "Source<" } + Ra::Dataflow::Core::simplifiedDemangledType() + + ">"; + return demangledTypeName; +} + +template +void FunctionSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = std::string( "Unable to save data when serializing a FunctionSourceNode<" ) + + Ra::Dataflow::Core::simplifiedDemangledType() + ">."; + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +bool FunctionSourceNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; +} + } // namespace Sources } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl b/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl deleted file mode 100644 index 37aab835b80..00000000000 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.inl +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once -#include -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Sources { - -template -FunctionSourceNode::FunctionSourceNode( const std::string& instanceName, - const std::string& typeName ) : - Node( instanceName, typeName ) { - addOutput( m_portOut, m_data ); -} - -template -bool FunctionSourceNode::execute() { - auto interfacePort = static_cast*>( m_interface[0] ); - if ( interfacePort->isLinked() ) { m_data = &( interfacePort->getData() ); } - else { - m_data = &m_localData; - } - m_portOut->setData( m_data ); - return true; -} - -template -void FunctionSourceNode::setData( function_type* data ) { - m_localData = *data; - m_data = &m_localData; - m_portOut->setData( m_data ); -} - -template -typename FunctionSourceNode::function_type* -FunctionSourceNode::getData() const { - return m_data; -} - -template -const std::string& FunctionSourceNode::getTypename() { - static std::string demangledTypeName = - std::string { "Source<" } + Ra::Dataflow::Core::simplifiedDemangledType() + - ">"; - return demangledTypeName; -} - -template -void FunctionSourceNode::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = std::string( "Unable to save data when serializing a FunctionSourceNode<" ) + - Ra::Dataflow::Core::simplifiedDemangledType() + ">."; - LOG( Ra::Core::Utils::logDEBUG ) - << "Unable to save data when serializing a " << getTypeName() << "."; -} - -template -bool FunctionSourceNode::fromJsonInternal( const nlohmann::json& ) { - LOG( Ra::Core::Utils::logDEBUG ) - << "Unable to read data when un-serializing a " << getTypeName() << "."; - return true; -} - -} // namespace Sources -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp index 463daf60692..12c4b4d3dcb 100644 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp +++ b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.hpp @@ -80,9 +80,82 @@ class SingleDataSourceNode : public Node static const std::string& getTypename(); }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +template +SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, + const std::string& typeName ) : + Node( instanceName, typeName ) { + addOutput( m_portOut, m_data ); +} + +template +bool SingleDataSourceNode::execute() { + // interfaces ports are at the same index as output ports + auto interfacePort = static_cast*>( m_interface[0] ); + if ( interfacePort->isLinked() ) { + // use external storage to deliver data + m_data = &( interfacePort->getData() ); + } + else { + // use local storage to deliver data + m_data = &m_localData; + } + m_portOut->setData( m_data ); + return true; +} + +template +void SingleDataSourceNode::setData( T* data ) { + /// \warning this will copy data into local storage + m_localData = *data; +} + +template +void SingleDataSourceNode::setData( T& data ) { + m_localData = data; +} + +template +T* SingleDataSourceNode::getData() const { + return m_data; +} + +template +void SingleDataSourceNode::setEditable( const std::string& name ) { + Node::addEditableParameter( new EditableParameter( name, m_localData ) ); +} + +template +void SingleDataSourceNode::removeEditable( const std::string& name ) { + Node::removeEditableParameter( name ); +} + +template +const std::string& SingleDataSourceNode::getTypename() { + static std::string demangledTypeName = + std::string { "Source<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; + return demangledTypeName; +} + +template +void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["comment"] = + std::string( "Unable to save data when serializing a SingleDataSourceNode<" ) + + Ra::Dataflow::Core::simplifiedDemangledType() + ">."; + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to save data when serializing a " << getTypeName() << "."; +} + +template +bool SingleDataSourceNode::fromJsonInternal( const nlohmann::json& ) { + LOG( Ra::Core::Utils::logDEBUG ) + << "Unable to read data when un-serializing a " << getTypeName() << "."; + return true; +} + } // namespace Sources } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl b/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl deleted file mode 100644 index d2616e51359..00000000000 --- a/src/Dataflow/Core/Nodes/Sources/SingleDataSourceNode.inl +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once -#include -#include - -namespace Ra { -namespace Dataflow { -namespace Core { -namespace Sources { - -template -SingleDataSourceNode::SingleDataSourceNode( const std::string& instanceName, - const std::string& typeName ) : - Node( instanceName, typeName ) { - addOutput( m_portOut, m_data ); -} - -template -bool SingleDataSourceNode::execute() { - // interfaces ports are at the same index as output ports - auto interfacePort = static_cast*>( m_interface[0] ); - if ( interfacePort->isLinked() ) { - // use external storage to deliver data - m_data = &( interfacePort->getData() ); - } - else { - // use local storage to deliver data - m_data = &m_localData; - } - m_portOut->setData( m_data ); - return true; -} - -template -void SingleDataSourceNode::setData( T* data ) { - /// \warning this will copy data into local storage - m_localData = *data; -} - -template -void SingleDataSourceNode::setData( T& data ) { - m_localData = data; -} - -template -T* SingleDataSourceNode::getData() const { - return m_data; -} - -template -void SingleDataSourceNode::setEditable( const std::string& name ) { - Node::addEditableParameter( new EditableParameter( name, m_localData ) ); -} - -template -void SingleDataSourceNode::removeEditable( const std::string& name ) { - Node::removeEditableParameter( name ); -} - -template -const std::string& SingleDataSourceNode::getTypename() { - static std::string demangledTypeName = - std::string { "Source<" } + Ra::Dataflow::Core::simplifiedDemangledType() + ">"; - return demangledTypeName; -} - -template -void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { - data["comment"] = - std::string( "Unable to save data when serializing a SingleDataSourceNode<" ) + - Ra::Dataflow::Core::simplifiedDemangledType() + ">."; - LOG( Ra::Core::Utils::logDEBUG ) - << "Unable to save data when serializing a " << getTypeName() << "."; -} - -template -bool SingleDataSourceNode::fromJsonInternal( const nlohmann::json& ) { - LOG( Ra::Core::Utils::logDEBUG ) - << "Unable to read data when un-serializing a " << getTypeName() << "."; - return true; -} - -} // namespace Sources -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index b5f96dbc20d..66b59b0bf9b 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include + #include #include @@ -206,8 +208,206 @@ class PortIn : public PortBase, std::string getTypeName() override; }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +inline PortBase::PortBase( const std::string& name, size_t type, Node* node ) : + m_name( name ), m_type( type ), m_node( node ) {} + +inline const std::string& PortBase::getName() { + return m_name; +} + +inline size_t PortBase::getType() { + return m_type; +} + +inline Node* PortBase::getNode() { + return m_node; +} + +inline bool PortBase::hasData() { + return false; +} + +inline bool PortBase::isLinked() { + return m_isLinked; +} + +inline bool PortBase::isLinkMandatory() { + return m_isLinkMandatory; +} + +inline void PortBase::mustBeLinked() { + m_isLinkMandatory = true; +} + +inline bool PortBase::accept( PortBase* other ) { + return m_type == other->getType(); +} + +template +PortOut::PortOut( const std::string& name, Node* node ) : + PortBase( name, typeid( T ).hash_code(), node ) {} + +template +T& PortOut::getData() { + return *m_data; +} + +template +void PortOut::setData( T* data ) { + m_data = data; +} + +template +bool PortOut::hasData() { + return m_data != nullptr; +} + +template +PortBase* PortOut::getLink() { + return nullptr; +} + +template +bool PortOut::accept( PortBase* ) { + return false; +} + +template +bool PortOut::connect( PortBase* o ) { + m_isLinked = o->connect( this ); + return m_isLinked; +} + +template +std::string PortOut::getTypeName() { + return simplifiedDemangledType(); +} + +template +std::string PortIn::getTypeName() { + return simplifiedDemangledType(); +} + +template +void PortBase::setData( T* data ) { + if ( !is_input() ) { + auto thisOut = dynamic_cast*>( this ); + if ( thisOut ) { + thisOut->setData( data ); + return; + } + LOG( Ra::Core::Utils::logERROR ) + << "Unable to set data with type " << simplifiedDemangledType( *data ) << " on port " + << getName() << " which expect " << getTypeName() << ".\n"; + std::abort(); + } + LOG( Ra::Core::Utils::logERROR ) + << "Could not call PortBase::setData(T* data) on the input port " << getName() << ".\n"; + std::abort(); +} + +template +void PortBase::getData( T& t ) { + if ( !is_input() ) { + auto thisOut = dynamic_cast*>( this ); + if ( thisOut ) { + t = thisOut->getData(); + return; + } + LOG( Ra::Core::Utils::logERROR ) + << "Unable to get data with type " << simplifiedDemangledType() << " on port " + << getName() << " which expect " << getTypeName() << ".\n"; + std::abort(); + } + LOG( Ra::Core::Utils::logERROR ) + << "Could not call PortBase::getData( T& t ) on the input port " << getName() << ".\n"; + std::abort(); +} + +template +T& PortBase::getData() { + if ( !is_input() ) { + auto thisOut = dynamic_cast*>( this ); + if ( thisOut ) { return thisOut->getData(); } + LOG( Ra::Core::Utils::logERROR ) + << "Unable to get data with type " << simplifiedDemangledType() << " on port " + << getName() << " which expect " << getTypeName() << ".\n"; + std::abort(); + } + LOG( Ra::Core::Utils::logERROR ) + << "Could not call T& PortBase::getData() on the input port " << getName() << ".\n"; + std::abort(); +} + +template +bool PortOut::disconnect() { + return false; +} + +template +PortBase* PortOut::reflect( Node* node, std::string name ) { + return new PortIn( name, node ); +} + +/** + * PortIn is an Observable&> that notifies its observers at connect/disconnect event + * @tparam T + */ +template +PortIn::PortIn( const std::string& name, Node* node ) : + PortBase( name, typeid( T ).hash_code(), node ) {} + +template +PortBase* PortIn::getLink() { + return m_from; +} + +template +T& PortIn::getData() { + return m_from->getData(); +} + +template +bool PortIn::accept( PortBase* other ) { + if ( !m_from && ( other->getType() == getType() ) ) { return PortBase::accept( other ); } + return false; +} + +template +bool PortIn::connect( PortBase* other ) { + if ( accept( other ) ) { + m_from = static_cast*>( other ); + m_isLinked = true; + // notify after connect + this->notify( getName(), *this, true ); + } + return m_isLinked; +} + +template +bool PortIn::disconnect() { + if ( m_isLinked ) { + // notify before disconnect + this->notify( getName(), *this, false ); + m_from = nullptr; + m_isLinked = false; + return true; + } + return false; +} +template +PortBase* PortIn::reflect( Node* node, std::string name ) { + return new PortOut( name, node ); +} + +template +bool PortIn::is_input() { + return true; +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/Port.inl b/src/Dataflow/Core/Port.inl deleted file mode 100644 index a7b6e599405..00000000000 --- a/src/Dataflow/Core/Port.inl +++ /dev/null @@ -1,210 +0,0 @@ -#pragma once -#include -#include - -#include - -namespace Ra { -namespace Dataflow { -namespace Core { - -inline PortBase::PortBase( const std::string& name, size_t type, Node* node ) : - m_name( name ), m_type( type ), m_node( node ) {} - -inline const std::string& PortBase::getName() { - return m_name; -} - -inline size_t PortBase::getType() { - return m_type; -} - -inline Node* PortBase::getNode() { - return m_node; -} - -inline bool PortBase::hasData() { - return false; -} - -inline bool PortBase::isLinked() { - return m_isLinked; -} - -inline bool PortBase::isLinkMandatory() { - return m_isLinkMandatory; -} - -inline void PortBase::mustBeLinked() { - m_isLinkMandatory = true; -} - -inline bool PortBase::accept( PortBase* other ) { - return m_type == other->getType(); -} - -template -PortOut::PortOut( const std::string& name, Node* node ) : - PortBase( name, typeid( T ).hash_code(), node ) {} - -template -T& PortOut::getData() { - return *m_data; -} - -template -void PortOut::setData( T* data ) { - m_data = data; -} - -template -bool PortOut::hasData() { - return m_data != nullptr; -} - -template -PortBase* PortOut::getLink() { - return nullptr; -} - -template -bool PortOut::accept( PortBase* ) { - return false; -} - -template -bool PortOut::connect( PortBase* o ) { - m_isLinked = o->connect( this ); - return m_isLinked; -} - -template -std::string PortOut::getTypeName() { - return simplifiedDemangledType(); -} - -template -std::string PortIn::getTypeName() { - return simplifiedDemangledType(); -} - -template -void PortBase::setData( T* data ) { - if ( !is_input() ) { - auto thisOut = dynamic_cast*>( this ); - if ( thisOut ) { - thisOut->setData( data ); - return; - } - LOG( Ra::Core::Utils::logERROR ) - << "Unable to set data with type " << simplifiedDemangledType( *data ) << " on port " - << getName() << " which expect " << getTypeName() << ".\n"; - std::abort(); - } - LOG( Ra::Core::Utils::logERROR ) - << "Could not call PortBase::setData(T* data) on the input port " << getName() << ".\n"; - std::abort(); -} - -template -void PortBase::getData( T& t ) { - if ( !is_input() ) { - auto thisOut = dynamic_cast*>( this ); - if ( thisOut ) { - t = thisOut->getData(); - return; - } - LOG( Ra::Core::Utils::logERROR ) - << "Unable to get data with type " << simplifiedDemangledType() << " on port " - << getName() << " which expect " << getTypeName() << ".\n"; - std::abort(); - } - LOG( Ra::Core::Utils::logERROR ) - << "Could not call PortBase::getData( T& t ) on the input port " << getName() << ".\n"; - std::abort(); -} - -template -T& PortBase::getData() { - if ( !is_input() ) { - auto thisOut = dynamic_cast*>( this ); - if ( thisOut ) { return thisOut->getData(); } - LOG( Ra::Core::Utils::logERROR ) - << "Unable to get data with type " << simplifiedDemangledType() << " on port " - << getName() << " which expect " << getTypeName() << ".\n"; - std::abort(); - } - LOG( Ra::Core::Utils::logERROR ) - << "Could not call T& PortBase::getData() on the input port " << getName() << ".\n"; - std::abort(); -} - -template -bool PortOut::disconnect() { - return false; -} - -template -PortBase* PortOut::reflect( Node* node, std::string name ) { - return new PortIn( name, node ); -} - -/** - * PortIn is an Observable&> that notifies its observers at connect/disconnect event - * @tparam T - */ -template -PortIn::PortIn( const std::string& name, Node* node ) : - PortBase( name, typeid( T ).hash_code(), node ) {} - -template -PortBase* PortIn::getLink() { - return m_from; -} - -template -T& PortIn::getData() { - return m_from->getData(); -} - -template -bool PortIn::accept( PortBase* other ) { - if ( !m_from && ( other->getType() == getType() ) ) { return PortBase::accept( other ); } - return false; -} - -template -bool PortIn::connect( PortBase* other ) { - if ( accept( other ) ) { - m_from = static_cast*>( other ); - m_isLinked = true; - // notify after connect - this->notify( getName(), *this, true ); - } - return m_isLinked; -} - -template -bool PortIn::disconnect() { - if ( m_isLinked ) { - // notify before disconnect - this->notify( getName(), *this, false ); - m_from = nullptr; - m_isLinked = false; - return true; - } - return false; -} -template -PortBase* PortIn::reflect( Node* node, std::string name ) { - return new PortOut( name, node ); -} - -template -bool PortIn::is_input() { - return true; -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index 7c47277a937..fd65a6ae26a 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include + namespace Ra { namespace Dataflow { namespace Core { @@ -13,8 +15,28 @@ const char* simplifiedDemangledType() noexcept; template const char* simplifiedDemangledType( const T& ) noexcept; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + +namespace TypeInternal { +RA_DATAFLOW_API std::string makeTypeReadable( std::string ); +} + +template +const char* simplifiedDemangledType() noexcept { + static auto demangled_name = []() { + std::string demangledType = + TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); + return demangledType; + }(); + return demangled_name.data(); +} + +template +const char* simplifiedDemangledType( const T& ) noexcept { + return simplifiedDemangledType(); +} + } // namespace Core } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Core/TypeDemangler.inl b/src/Dataflow/Core/TypeDemangler.inl deleted file mode 100644 index e77abe98c87..00000000000 --- a/src/Dataflow/Core/TypeDemangler.inl +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once -#include - -#include -namespace Ra { -namespace Dataflow { -namespace Core { - -namespace TypeInternal { -RA_DATAFLOW_API std::string makeTypeReadable( std::string ); -} - -template -const char* simplifiedDemangledType() noexcept { - static auto demangled_name = []() { - std::string demangledType = - TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); - return demangledType; - }(); - return demangled_name.data(); -} - -template -const char* simplifiedDemangledType( const T& ) noexcept { - return simplifiedDemangledType(); -} - -} // namespace Core -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Core/filelist.cmake b/src/Dataflow/Core/filelist.cmake index c90f42accc7..7ff6168557c 100644 --- a/src/Dataflow/Core/filelist.cmake +++ b/src/Dataflow/Core/filelist.cmake @@ -29,22 +29,6 @@ set(dataflow_core_headers TypeDemangler.hpp ) -set(dataflow_core_inlines - DataflowGraph.inl - EditableParameter.inl - Enumerator.inl - Node.inl - NodeFactory.inl - Nodes/Functionals/FilterNode.inl - Nodes/Functionals/ReduceNode.inl - Nodes/Functionals/TransformNode.inl - Nodes/Sinks/SinkNode.inl - Nodes/Sources/FunctionSource.inl - Nodes/Sources/SingleDataSourceNode.inl - Port.inl - TypeDemangler.inl -) - set(dataflow_core_private Nodes/Private/FunctionalsNodeFactory.hpp Nodes/Private/FunctionalsNodeFactory.cpp Nodes/Private/SinksNodeFactory.hpp Nodes/Private/SinksNodeFactory.cpp From 4cc6cf735b5c0b713df20dad108a0bfd48f599ba Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 15:05:05 +0100 Subject: [PATCH 103/239] [dataflow-qtgui] remove .inl files --- src/Dataflow/QtGui/CMakeLists.txt | 4 ++-- src/Dataflow/QtGui/filelist.cmake | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Dataflow/QtGui/CMakeLists.txt b/src/Dataflow/QtGui/CMakeLists.txt index 5a257cb85a5..c3d2a17472e 100644 --- a/src/Dataflow/QtGui/CMakeLists.txt +++ b/src/Dataflow/QtGui/CMakeLists.txt @@ -25,7 +25,7 @@ find_package(PowerSlider REQUIRED) add_library( ${ra_dataflowqtgui_target} SHARED ${dataflow_qtgui_sources} ${dataflow_qtgui_headers} - ${dataflow_qtgui_inlines} ${dataflow_qtgui_resources} + ${dataflow_qtgui_resources} ) # This one should be extracted directly from parent project properties. @@ -47,7 +47,7 @@ configure_radium_target(${ra_dataflowqtgui_target}) # configure the library only. The package is a sub-package and should be configured independently configure_radium_library( TARGET ${ra_dataflowqtgui_target} COMPONENT TARGET_DIR "Dataflow/QtGui" - FILES "${dataflow_qtgui_headers};${dataflow_qtgui_inlines}" + FILES "${dataflow_qtgui_headers}" ) # Generate cmake package configure_radium_package( diff --git a/src/Dataflow/QtGui/filelist.cmake b/src/Dataflow/QtGui/filelist.cmake index a0185babbaf..ac4590ae925 100644 --- a/src/Dataflow/QtGui/filelist.cmake +++ b/src/Dataflow/QtGui/filelist.cmake @@ -12,6 +12,4 @@ set(dataflow_qtgui_headers GraphEditor/WidgetFactory.hpp ) -set(dataflow_qtgui_inlines) - set(dataflow_qtgui_resources GraphEditor/GraphEditor.qrc) From 3cb978ac7279220f1662fb1af53b7301b36764d6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 18:54:11 +0100 Subject: [PATCH 104/239] [dataflow-core] fix codacy warning --- src/Dataflow/Core/EditableParameter.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp index c853c0bdad0..b5b459a5fda 100644 --- a/src/Dataflow/Core/EditableParameter.hpp +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -26,7 +26,7 @@ struct RA_DATAFLOW_API EditableParameterBase { EditableParameterBase& operator=( const EditableParameterBase& ) = delete; /// Construct an base editable parameter from its name and type hash - EditableParameterBase( std::string& name, size_t hashedType ); + EditableParameterBase( const std::string& name, size_t hashedType ); ///@} virtual ~EditableParameterBase() = default; @@ -45,7 +45,7 @@ struct EditableParameter : public EditableParameterBase { EditableParameter& operator=( const EditableParameter& ) = delete; /// Construct an editable parameter from its name and type hash - EditableParameter( std::string name, T& data ); + EditableParameter( const std::string& name, T& data ); ///@} /// Add constraints or associated data to the editable. @@ -65,11 +65,11 @@ struct EditableParameter : public EditableParameterBase { // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- -inline EditableParameterBase::EditableParameterBase( std::string& name, size_t hashedType ) : +inline EditableParameterBase::EditableParameterBase( const std::string& name, size_t hashedType ) : m_name( name ), m_hashedType( hashedType ) {} template -EditableParameter::EditableParameter( std::string name, T& data ) : +EditableParameter::EditableParameter( const std::string& name, T& data ) : EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {} template From e1095a4efe67f79d6a41d07a9ab90da637c189ff Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 22:23:41 +0100 Subject: [PATCH 105/239] [dataflow-core] improve trace and robustness --- src/Dataflow/Core/DataflowGraph.cpp | 90 +++++++++++++++---------- src/Dataflow/Core/DataflowGraph.hpp | 21 ++++-- src/Dataflow/Core/EditableParameter.hpp | 53 +++++++++------ 3 files changed, 102 insertions(+), 62 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 0101be1f4d6..5b8f452dea5 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -177,10 +177,11 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { Node* nodeTo { nullptr }; std::string toInput { "" }; - if ( nodeById.find( l["out_id"] ) != nodeById.end() ) { - nodeFrom = nodeById[l["out_id"]]; + auto nodeId = l["out_id"]; + auto itNode = nodeById.find( nodeId ); + if ( itNode != nodeById.end() ) { + nodeFrom = itNode->second; int fromIndex = l["out_index"]; - if ( fromIndex >= 0 && fromIndex < int( nodeFrom->getOutputs().size() ) ) { fromOutput = nodeFrom->getOutputs()[fromIndex]->getName(); } @@ -195,13 +196,15 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } else { LOG( logERROR ) << "Error when reading JSON file \"" - << "\": Could not find a node associated with id " << l["out_id"] + << "\": Could not find a node associated with id " << nodeId << ". Link not added."; return false; } - if ( nodeById.find( l["in_id"] ) != nodeById.end() ) { - nodeTo = nodeById[l["in_id"]]; + nodeId = l["in_id"]; + itNode = nodeById.find( nodeId ); + if ( itNode != nodeById.end() ) { + nodeTo = itNode->second; int toIndex = l["in_index"]; if ( toIndex >= 0 && toIndex < int( nodeTo->getInputs().size() ) ) { @@ -218,15 +221,12 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } else { LOG( logERROR ) << "Error when reading JSON file \"" - << "\": Could not find a node associated with id " << l["in_id"] + << "\": Could not find a node associated with id " << nodeId << ". Link not added."; return false; } - if ( nodeFrom && ( fromOutput != "" ) && nodeTo && ( toInput != "" ) ) { - addLink( nodeFrom, fromOutput, nodeTo, toInput ); - } - else { + if ( !addLink( nodeFrom, fromOutput, nodeTo, toInput ) ) { LOG( logERROR ) << "Error when reading JSON file \"" << "\": Could not add a link (missing or wrong information, please refer to " @@ -342,7 +342,7 @@ bool DataflowGraph::addLink( Node* nodeFrom, if ( foundFrom == -1 ) { LOG( logERROR ) << "DataflowGraph::addLink Unable to find output port " << nodeFromOutputName << " from initial node " - << nodeFrom->getInstanceName(); + << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ")"; return false; } @@ -358,21 +358,41 @@ bool DataflowGraph::addLink( Node* nodeFrom, } if ( foundTo == -1 ) { LOG( logERROR ) << "DataflowGraph::addLink Unable to find input port " << nodeFromOutputName - << " from destination node " << nodeTo->getInstanceName(); + << " from destination node " << nodeTo->getInstanceName() << " (" + << nodeTo->getTypeName() << ")"; return false; } // Compare types - // TODO fix the variable naming ... if ( nodeTo->getInputs()[foundTo]->getType() != nodeFrom->getOutputs()[foundFrom]->getType() ) { + LOG( logERROR ) << "DataflowGraph::addLink type mismatch from " + << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ") /" + << nodeFrom->getOutputs()[foundFrom]->getName() << " (" << foundFrom + << " ++ " << nodeFrom->getOutputs()[foundFrom]->getType() << ")" + << " to " << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() + << ") / " << nodeTo->getInputs()[foundTo]->getName() << " (" << foundTo + << " ++ " << nodeTo->getInputs()[foundTo]->getType() << ") "; return false; } // Check if input is connected - if ( nodeTo->getInputs()[foundTo]->isLinked() ) { return false; } + if ( nodeTo->getInputs()[foundTo]->isLinked() ) { + LOG( logERROR ) + << "DataflowGraph::addLink destination port not available (already linked) for " + << nodeTo->getInstanceName() << " (" << nodeFrom->getTypeName() << "), port " + << nodeTo->getInputs()[foundTo]->getName(); + return false; + } // Try to connect ports if ( !nodeTo->getInputs()[foundTo]->connect( nodeFrom->getOutputs()[foundFrom].get() ) ) { + LOG( logERROR ) << "DataflowGraph::addLink unable to connect from " + << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ") /" + << nodeFrom->getOutputs()[foundFrom]->getName() << " (" << foundFrom + << " ++ " << nodeFrom->getOutputs()[foundFrom]->getType() << ")" + << " to " << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() + << ") / " << nodeTo->getInputs()[foundTo]->getName() << " (" << foundTo + << " ++ " << nodeTo->getInputs()[foundTo]->getType() << ") "; return false; } // The state of the graph changes, set it to not ready @@ -406,12 +426,13 @@ bool DataflowGraph::removeLink( Node* node, const std::string& nodeInputName ) { return true; } -// Todo, rewrite this method using std::find_if ? int DataflowGraph::findNode( const Node* node ) const { - for ( size_t i = 0; i < m_nodes.size(); i++ ) { - if ( *m_nodes[i] == *node ) { return i; } + auto foundIt = std::find_if( + m_nodes.begin(), m_nodes.end(), [node]( const auto& p ) { return *p == *node; } ); + if ( foundIt != m_nodes.end() ) { return std::distance( m_nodes.begin(), foundIt ); } + else { + return -1; } - return -1; } bool DataflowGraph::compile() { @@ -436,8 +457,7 @@ bool DataflowGraph::compile() { int maxLevel = 0; for ( auto& infNode : infoNodes ) { auto n = infNode.first; - // n->setResourcesDir( m_resourceDir ); // --> This must be configured per node, e.g. by the - // factory Compute the nodes' level starting from sources + // Compute the nodes' level starting from sources if ( n->getInputs().empty() ) { // Tag successors for ( auto const successor : infNode.second.second ) { @@ -550,7 +570,7 @@ bool DataflowGraph::addSetter( PortBase* in ) { inline bool DataflowGraph::addGetter( PortBase* out ) { if ( out->is_input() ) { return false; } - // This is very similar than addOutput, except the data can't be set, they will be in the init + // This is very similar to addOutput, except the data can't be set, they will be in the init // of any Sink bool found = false; // TODO check if this verification is needed ? @@ -561,7 +581,7 @@ inline bool DataflowGraph::addGetter( PortBase* out ) { return !found; } -bool DataflowGraph::releaseDataSetter( std::string portName ) { +bool DataflowGraph::releaseDataSetter( const std::string& portName ) { auto setter = m_dataSetters.find( portName ); if ( setter != m_dataSetters.end() ) { auto [desc, in] = setter->second; @@ -572,11 +592,11 @@ bool DataflowGraph::releaseDataSetter( std::string portName ) { } // Why is this method useful if it is the same than getDataSetter ? -bool DataflowGraph::activateDataSetter( std::string portName ) { +bool DataflowGraph::activateDataSetter( const std::string& portName ) { return getDataSetter( portName ) != nullptr; } -std::shared_ptr DataflowGraph::getDataSetter( std::string portName ) { +std::shared_ptr DataflowGraph::getDataSetter( const std::string& portName ) { auto setter = m_dataSetters.find( portName ); if ( setter != m_dataSetters.end() ) { auto [desc, in] = setter->second; @@ -597,13 +617,11 @@ std::vector DataflowGraph::getAllDataSetters() { return r; } -/// Not sure this method do the right thing ... if we want to get data from the port, it must be an -/// output port ... - -PortBase* DataflowGraph::getDataGetter( std::string portName ) { - for ( auto& portOut : m_outputs ) { - if ( portOut->getName() == portName ) { return portOut.get(); } - } +PortBase* DataflowGraph::getDataGetter( const std::string& portName ) { + auto portIt = std::find_if( m_outputs.begin(), m_outputs.end(), [portName]( const auto& p ) { + return p->getName() == portName; + } ); + if ( portIt != m_outputs.end() ) { return portIt->get(); } return nullptr; } @@ -617,9 +635,11 @@ std::vector DataflowGraph::getAllDataGetters() { } Node* DataflowGraph::getNode( const std::string& instanceNameNode ) const { - for ( const auto& node : m_nodes ) { - if ( node->getInstanceName() == instanceNameNode ) { return node.get(); } - } + auto nodeIt = + std::find_if( m_nodes.begin(), m_nodes.end(), [instanceNameNode]( const auto& n ) { + return n->getInstanceName() == instanceNameNode; + } ); + if ( nodeIt != m_nodes.end() ) { return nodeIt->get(); } LOG( logERROR ) << "DataflowGraph::getNode : The node with the instance name " << instanceNameNode << " has not been found"; return nullptr; diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 71dbbe3d8ca..d8d25fd3ec8 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -128,26 +128,35 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \note As ownership is shared with the caller, the graph must survive the returned /// pointer to be able to use the dataSetter.. /// \params portName The name of the input port of the graph - std::shared_ptr getDataSetter( std::string portName ); + std::shared_ptr getDataSetter( const std::string& portName ); /// \brief disconnect the data setting port from its inputs. - bool releaseDataSetter( std::string portName ); + bool releaseDataSetter( const std::string& portName ); /// \brief connect the data setting port from its inputs. - bool activateDataSetter( std::string portName ); + bool activateDataSetter( const std::string& portName ); /// Returns an alias to the named output port of the graph. /// Allows to get the data stored at this port after the execution of the graph. /// \note ownership is left to the graph. The graph must survive the returned /// pointer to be able to use the dataGetter.. /// \params portName the name of the output port - PortBase* getDataGetter( std::string portName ); + PortBase* getDataGetter( const std::string& portName ); + /// \brief Data setter descriptor. + /// A Data setter descriptor is composed of an output port (linked by construction to an input + /// port of the graph), its name and its type. + /// Use setData on the output port to pass data to the graph using DataSetterDesc = std::tuple, std::string, std::string>; + + /// \brief Data getter descriptor. + /// A Data getter descriptor is composed of an output port (belonging to any node of the graph), + /// its name and its type. + /// Use getData on the output port to extract data from the graph using DataGetterDesc = std::tuple; /// Creates a vector that stores all the DataSetters (\see getDataSetter) of the graph. - /// A tuple is composed of an output port connected to an input port of the graph, its name its - /// type. \note If called multiple times for the same port, only the last returned result is + + /// \note If called multiple times for the same port, only the last returned result is /// usable. /// TODO : Verify why, when listing the data setters, they are connected ... std::vector getAllDataSetters(); diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp index b5b459a5fda..b34319c39d5 100644 --- a/src/Dataflow/Core/EditableParameter.hpp +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -2,9 +2,11 @@ #include #include -#include +#include #include +#include + namespace Ra { namespace Dataflow { namespace Core { @@ -26,13 +28,24 @@ struct RA_DATAFLOW_API EditableParameterBase { EditableParameterBase& operator=( const EditableParameterBase& ) = delete; /// Construct an base editable parameter from its name and type hash - EditableParameterBase( const std::string& name, size_t hashedType ); + EditableParameterBase( const std::string& name, std::type_index typeIdx ); ///@} virtual ~EditableParameterBase() = default; - std::string getName(); + std::string getName() const; + std::type_index getType() const; + /// Constraints on the edited data : json object describing the constraints + /// Add constraints or associated data to the editable. + void setConstraints( const nlohmann::json& constraints ); + /// get the constraints or the associated data from the editable. + const nlohmann::json& getConstraints() const; + + private: std::string m_name { "" }; - size_t m_hashedType { 0 }; + std::type_index m_typeIdx; + + /// Constraints on the edited data + nlohmann::json m_constraints; }; template @@ -48,39 +61,37 @@ struct EditableParameter : public EditableParameterBase { EditableParameter( const std::string& name, T& data ); ///@} - /// Add constraints or associated data to the editable. - /// \todo, replace this with a json object describing the constraints - /// with value convertible to T .... - void addAdditionalData( T newData ); - /// The data to edit. /// This is a reference to any data stored in a node and that the user could change T& m_data; - - /// Constraints on the edited data - /// \todo, replace by a json desc of the constraints. - std::vector additionalData; }; // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- -inline EditableParameterBase::EditableParameterBase( const std::string& name, size_t hashedType ) : - m_name( name ), m_hashedType( hashedType ) {} +inline EditableParameterBase::EditableParameterBase( const std::string& name, + std::type_index typeIdx ) : + m_name( name ), m_typeIdx( typeIdx ) {} template EditableParameter::EditableParameter( const std::string& name, T& data ) : - EditableParameterBase( name, typeid( T ).hash_code() ), m_data( data ) {} + EditableParameterBase( name, typeid( T ) ), m_data( data ) {} -template -void EditableParameter::addAdditionalData( T newData ) { - additionalData.push_back( newData ); +inline std::string EditableParameterBase::getName() const { + return m_name; } -inline std::string EditableParameterBase::getName() { - return m_name; +inline std::type_index EditableParameterBase::getType() const { + return m_typeIdx; } +inline void EditableParameterBase::setConstraints( const nlohmann::json& constraints ) { + m_constraints = constraints; +} + +inline const nlohmann::json& EditableParameterBase::getConstraints() const { + return m_constraints; +} } // namespace Core } // namespace Dataflow } // namespace Ra From cbc5f325a0a7cf714719f6c8afdae2de417c6563 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 22:24:06 +0100 Subject: [PATCH 106/239] [dataflow-qtgui] improve robustness --- .../QtGui/GraphEditor/NodeAdapterModel.cpp | 6 +- .../QtGui/GraphEditor/WidgetFactory.cpp | 59 +++++++++---------- .../QtGui/GraphEditor/WidgetFactory.hpp | 5 +- 3 files changed, 34 insertions(+), 36 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index 68859012456..ed5fa93f173 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -200,8 +200,8 @@ QWidget* getWidget( Node* node ) { QWidget* newWidget = WidgetFactory::createWidget( edtParam ); if ( newWidget ) { newWidget->setParent( controlPanel ); - newWidget->setObjectName( edtParam->m_name.c_str() ); - controlPanel->addLabel( edtParam->m_name ); + newWidget->setObjectName( edtParam->getName().c_str() ); + controlPanel->addLabel( edtParam->getName() ); controlPanel->addWidget( newWidget ); if ( i != node->getEditableParameters().size() - 1 ) { controlPanel->addSeparator(); } @@ -218,7 +218,7 @@ void updateWidget( Node* node, QWidget* widget ) { for ( size_t i = 0; i < node->getEditableParameters().size(); i++ ) { auto edtParam = node->getEditableParameters()[i].get(); if ( !WidgetFactory::updateWidget( widget, edtParam ) ) { - std::cerr << "Unable to update parameter " << edtParam->m_name << "\n"; + std::cerr << "Unable to update parameter " << edtParam->getName() << "\n"; } } } diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index 254e126a475..f58f4b28f6f 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -5,12 +5,8 @@ #include #include -// #include - #include #include -// #include -// #include #include #include @@ -33,40 +29,40 @@ using namespace Ra::Engine::Data; namespace WidgetFactory { using WidgetFunctionPair = std::pair; -std::unordered_map widgetsfunctions; +std::unordered_map widgetsfunctions; -void registerWidgetInternal( size_t hashedType, +void registerWidgetInternal( std::type_index typeIdx, WidgetCreatorFunc widgetCreator, WidgetUpdaterFunc widgetUpdater ) { - if ( widgetsfunctions.find( hashedType ) == widgetsfunctions.end() ) { - widgetsfunctions[hashedType] = { std::move( widgetCreator ), std::move( widgetUpdater ) }; + if ( widgetsfunctions.find( typeIdx ) == widgetsfunctions.end() ) { + widgetsfunctions[typeIdx] = { std::move( widgetCreator ), std::move( widgetUpdater ) }; } else { - std::cerr - << "WidgetFactory: trying to add an already existing widget builder for hashed type " - << hashedType << "." << std::endl; + // TODO, when PR #1027 will be merged, demangle the type name + std::cerr << "WidgetFactory: trying to add an already existing widget builder for type " + << typeIdx.name() << "." << std::endl; } } QWidget* createWidget( EditableParameterBase* editableParameter ) { - if ( widgetsfunctions.find( editableParameter->m_hashedType ) != widgetsfunctions.end() ) { - return widgetsfunctions[editableParameter->m_hashedType].first( editableParameter ); + if ( widgetsfunctions.find( editableParameter->getType() ) != widgetsfunctions.end() ) { + return widgetsfunctions[editableParameter->getType()].first( editableParameter ); } else { + // TODO, when PR #1027 will be merged, demangle the type name std::cerr << "WidgetFactory: no defined widget builder for hashed type " - << editableParameter->m_hashedType << "." << std::endl; + << editableParameter->getType().name() << "." << std::endl; } return nullptr; } bool updateWidget( QWidget* widget, EditableParameterBase* editableParameter ) { - if ( widgetsfunctions.find( editableParameter->m_hashedType ) != widgetsfunctions.end() ) { - return widgetsfunctions[editableParameter->m_hashedType].second( widget, - editableParameter ); + if ( widgetsfunctions.find( editableParameter->getType() ) != widgetsfunctions.end() ) { + return widgetsfunctions[editableParameter->getType()].second( widget, editableParameter ); } else { std::cerr << "WidgetFactory: no defined widget updater for hashed type " - << editableParameter->m_hashedType << "." << std::endl; + << editableParameter->getType().name() << "." << std::endl; } return false; } @@ -80,7 +76,7 @@ void initializeWidgetFactory() { auto editable = dynamic_cast>*>( editableParameter ); - auto controlPanel = new ControlPanel( editable->m_name, false ); + auto controlPanel = new ControlPanel( editable->getName(), false ); auto envmpClbck = [editable, controlPanel]( const std::string& files ) { if ( files.empty() ) { editable->m_data = nullptr; } else { @@ -126,15 +122,18 @@ void initializeWidgetFactory() { []( EditableParameterBase* editableParameter ) { auto editable = dynamic_cast*>( editableParameter ); auto powerSlider = new PowerSlider(); - powerSlider->setObjectName( editable->m_name.c_str() ); + powerSlider->setObjectName( editable->getName().c_str() ); editable->m_data = 0.0; powerSlider->setValue( editable->m_data ); + // todo : manage constraints + /* if ( editable->additionalData.size() >= 2 ) { powerSlider->setRange( editable->additionalData[0], editable->additionalData[1] ); } else { powerSlider->setRange( 0.0, 9999.0 ); } + */ PowerSlider::connect( powerSlider, &PowerSlider::valueChanged, [editable]( Scalar value ) { editable->m_data = value; } ); @@ -142,7 +141,7 @@ void initializeWidgetFactory() { }, []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast*>( editableParameter ); - auto slider = widget->findChild( editableParameter->m_name.c_str() ); + auto slider = widget->findChild( editableParameter->getName().c_str() ); if ( slider ) { slider->setValue( editable->m_data ); return true; @@ -159,7 +158,7 @@ void initializeWidgetFactory() { []( EditableParameterBase* editableParameter ) { auto editable = dynamic_cast*>( editableParameter ); auto checkBox = new QCheckBox(); - checkBox->setObjectName( editable->m_name.c_str() ); + checkBox->setObjectName( editable->getName().c_str() ); checkBox->setCheckState( editable->m_data ? Qt::CheckState::Checked : Qt::CheckState::Unchecked ); QCheckBox::connect( checkBox, &QCheckBox::stateChanged, [editable]( int state ) { @@ -172,7 +171,7 @@ void initializeWidgetFactory() { }, []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast*>( editableParameter ); - auto checkBox = widget->findChild( editableParameter->m_name.c_str() ); + auto checkBox = widget->findChild( editableParameter->getName().c_str() ); if ( checkBox ) { checkBox->setCheckState( editable->m_data ? Qt::Checked : Qt::Unchecked ); return true; @@ -189,7 +188,7 @@ void initializeWidgetFactory() { []( EditableParameterBase* editableParameter ) { auto editable = dynamic_cast*>( editableParameter ); - auto controlPanel = new ControlPanel( editable->m_name, false ); + auto controlPanel = new ControlPanel( editable->getName(), false ); auto clrCbk = [editable]( const Ra::Core::Utils::Color& clr ) { editable->m_data = clr; }; @@ -216,7 +215,7 @@ void initializeWidgetFactory() { []( EditableParameterBase* editableParameter ) { auto editable = dynamic_cast*>( editableParameter ); auto button = new QPushButton( "Show graph" ); - button->setObjectName( editableParameter->m_name.c_str() ); + button->setObjectName( editableParameter->getName().c_str() ); QPushButton::connect( button, &QPushButton::clicked, [editable]() { // Display a window to see the graph /*auto graph = reinterpret_cast( editable ); @@ -234,7 +233,7 @@ void initializeWidgetFactory() { }, []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast*>( editableParameter ); - auto button = widget->findChild( editable->m_name.c_str() ); + auto button = widget->findChild( editable->getName().c_str() ); if ( button ) { return true; } else { return false; @@ -285,7 +284,7 @@ void initializeWidgetFactory() { auto editable = dynamic_cast>*>( editableParameter ); auto selector = new QComboBox(); - selector->setObjectName( editable->m_name.c_str() ); + selector->setObjectName( editable->getName().c_str() ); for ( const auto& e : editable->m_data ) { selector->addItem( e.c_str() ); } @@ -298,7 +297,7 @@ void initializeWidgetFactory() { []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast>*>( editableParameter ); - auto comboBox = widget->findChild( editable->m_name.c_str() ); + auto comboBox = widget->findChild( editable->getName().c_str() ); if ( comboBox ) { comboBox->setCurrentText( editable->m_data.get().c_str() ); return true; @@ -315,7 +314,7 @@ void initializeWidgetFactory() { []( EditableParameterBase* editableParameter ) { auto editable = dynamic_cast*>( editableParameter ); auto line = new QLineEdit(); - line->setObjectName( editable->m_name.c_str() ); + line->setObjectName( editable->getName().c_str() ); QLineEdit::connect( line, &QLineEdit::textEdited, [editable]( const QString& string ) { editable->m_data = string.toStdString(); } ); @@ -323,7 +322,7 @@ void initializeWidgetFactory() { }, []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast*>( editableParameter ); - auto line = widget->findChild( editable->m_name.c_str() ); + auto line = widget->findChild( editable->getName().c_str() ); if ( line ) { line->setText( editable->m_data.c_str() ); return true; diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp index b06a09552fc..63f4b054fe7 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp @@ -36,7 +36,7 @@ using WidgetUpdaterFunc = std::function void registerWidget( WidgetCreatorFunc widgetCreator, WidgetUpdaterFunc widgetUpdater ) { - registerWidgetInternal( - typeid( T ).hash_code(), std::move( widgetCreator ), std::move( widgetUpdater ) ); + registerWidgetInternal( typeid( T ), std::move( widgetCreator ), std::move( widgetUpdater ) ); } /** From 9dd7b3185d9ac6f0f6bf3c47f237029d2edcd289 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 22:39:02 +0100 Subject: [PATCH 107/239] [dataflow-qtgui] add constraints on powerslider when available --- .../QtGui/GraphEditor/WidgetFactory.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index f58f4b28f6f..b46b39e4f3f 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -123,17 +123,14 @@ void initializeWidgetFactory() { auto editable = dynamic_cast*>( editableParameter ); auto powerSlider = new PowerSlider(); powerSlider->setObjectName( editable->getName().c_str() ); - editable->m_data = 0.0; + editable->m_data = 0.0_ra; powerSlider->setValue( editable->m_data ); - // todo : manage constraints - /* - if ( editable->additionalData.size() >= 2 ) { - powerSlider->setRange( editable->additionalData[0], editable->additionalData[1] ); - } - else { - powerSlider->setRange( 0.0, 9999.0 ); - } - */ + const auto& constraints = editable->getConstraints(); + Scalar minValue = 0_ra; + Scalar maxValue = 1000_ra; + if ( constraints.contains( "min" ) ) { minValue = Scalar( constraints["min"] ); } + if ( constraints.contains( "max" ) ) { maxValue = Scalar( constraints["max"] ); } + powerSlider->setRange( minValue, maxValue ); PowerSlider::connect( powerSlider, &PowerSlider::valueChanged, [editable]( Scalar value ) { editable->m_data = value; } ); From fe8c591e1e1e7a3b52ba0af02b2af7e7fb6ce496 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 09:14:46 +0100 Subject: [PATCH 108/239] [dataflow-core] improve NodeFactoriesManager interface --- src/Dataflow/Core/NodeFactory.cpp | 8 ++++++-- src/Dataflow/Core/NodeFactory.hpp | 8 ++++++++ src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 3 +-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index 23da62176e6..f2f1fbfdb5a 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -42,8 +42,8 @@ bool NodeFactory::registerNodeCreator( std::string nodeType, return true; } LOG( Ra::Core::Utils::logWARNING ) - << "NodeFactory: trying to add an already existing node creator for type " << nodeType - << "."; + << "NodeFactory (" << getName() + << ") : trying to add an already existing node creator for type " << nodeType << "."; return false; } @@ -80,6 +80,10 @@ bool registerFactory( NodeFactorySet::mapped_type factory ) { return fctMngr.addFactory( std::move( factoryName ), std::move( factory ) ); } +NodeFactorySet::mapped_type createFactory( const std::string& name ) { + return NodeFactorySet::mapped_type { new NodeFactorySet::mapped_type::element_type( name ) }; +} + NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ) { auto& fctMngr = getFactoryManager(); auto factory = fctMngr.find( factoryName ); diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index 7bb07fc9d12..64d244ca8aa 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -215,6 +215,14 @@ RA_DATAFLOW_API NodeFactorySet& getFactoryManager(); */ RA_DATAFLOW_API bool registerFactory( NodeFactorySet::mapped_type factory ); +/** + * \brief Create a factory to be customized and later added to the manager + * \param name The name of the factory to create + * \return a configurable factory. + * \note The created factory is not registered into the manager. + */ +RA_DATAFLOW_API NodeFactorySet::mapped_type createFactory( const std::string& name ); + /** * \brief Gets the given factory from the manager * \param factoryName diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 45ff4a396da..7994962f111 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -12,8 +12,7 @@ namespace Core { namespace NodeFactoriesManager { void registerStandardFactories() { - NodeFactorySet::mapped_type coreFactory { new NodeFactorySet::mapped_type::element_type( - NodeFactoriesManager::dataFlowBuiltInsFactoryName ) }; + auto coreFactory = createFactory( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); /* --- Sources --- */ Private::registerSourcesFactories( coreFactory ); From 5c1e038cedeb09928cf6d22f8452ae0e710c53e0 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 09:15:47 +0100 Subject: [PATCH 109/239] [unittests] update to use NodeFactoriesManager interface --- tests/unittest/Dataflow/customnodes.cpp | 32 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 94358c79e7a..c8d2325fb9d 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -116,7 +116,8 @@ class FilterSelector final : public Node std::map m_functions { { "true", []( const T& ) { return true; } }, { "false", []( const T& ) { return false; } }, - { "<", [this]( const T& v ) { return v < this->m_threshold; } } }; + { "<", [this]( const T& v ) { return v < this->m_threshold; } }, + { ">", [this]( const T& v ) { return v > this->m_threshold; } } }; std::string m_operatorName { "true" }; function_type m_currentFunction = m_functions[m_operatorName]; @@ -206,11 +207,13 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { // execute the graph that filter out nothing // execute - g->execute(); - // Get results as references (ne need to get them again later) + r = g->execute(); + REQUIRE( r ); + // Get results as references (no need to get them again later) auto& vres = filteredCollection->getData(); auto& vop = generatedOperator->getData(); + REQUIRE( vop == "true" ); REQUIRE( vres.size() == testVector.size() ); std::cout << "Result after applying operator " << vop << " (from " << op << " ) and threshold " << threshold << ": \n\t"; @@ -221,7 +224,9 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { // change operator to filter out everything op = "false"; - g->execute(); + r = g->execute(); + REQUIRE( r ); + REQUIRE( vop == "false" ); REQUIRE( vres.size() == 0 ); std::cout << "Result after applying operator " << vop << " (from " << op @@ -233,7 +238,8 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { // Change operator to keep element less than threshold op = "<"; - g->execute(); + r = g->execute(); + REQUIRE( r ); std::cout << "Result after applying operator " << vop << " (from " << op << " ) and threshold " << threshold << ": \n\t"; @@ -242,11 +248,23 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { } std::cout << '\n'; REQUIRE( *( std::max_element( vres.begin(), vres.end() ) ) < threshold ); + + // Change operator to keep element greater than threshold + op = ">"; + r = g->execute(); + REQUIRE( r ); + + std::cout << "Result after applying operator " << vop << " (from " << op + << " ) and threshold " << threshold << ": \n\t"; + for ( auto ord : vres ) { + std::cout << ord << ' '; + } + std::cout << '\n'; + REQUIRE( *( std::max_element( vres.begin(), vres.end() ) ) > threshold ); } SECTION( "Serialization of a custom graph" ) { // Create and fill the factory for the custom nodes - NodeFactorySet::mapped_type customFactory { - new NodeFactorySet::mapped_type::element_type( "CustomNodesUnitTests" ) }; + auto customFactory = NodeFactoriesManager::createFactory( "CustomNodesUnitTests" ); // add node creators to the factory bool registered; From 1d51e7b4184fdc9f3c09c988157886711acaa99d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 09:15:58 +0100 Subject: [PATCH 110/239] [examples] update to use NodeFactoriesManager interface --- examples/DataflowExamples/GraphSerialization/main.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp index 09b19982ed4..2cb9c77e7e0 100644 --- a/examples/DataflowExamples/GraphSerialization/main.cpp +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -19,9 +19,7 @@ int main( int argc, char* argv[] ) { // custom node type are either specialization of templated nodes or user-define nodes class // create the custom node factory - NodeFactorySet::mapped_type customFactory { - new NodeFactorySet::mapped_type::element_type( "ExampleCustomFactory" ) }; - + auto customFactory = NodeFactoriesManager::createFactory( "ExampleCustomFactory" ); // add node creators to the factory customFactory->registerNodeCreator>( Sources::SingleDataSourceNode::getTypename() + "_", "Custom" ); From dd95895a5317d7d398bacf961101c2c336d01ed0 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 09:50:41 +0100 Subject: [PATCH 111/239] [cmake] fix configuration issue for interface libraries on windows in debug mode --- cmake/RadiumSetupFunctions.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/RadiumSetupFunctions.cmake b/cmake/RadiumSetupFunctions.cmake index bf5362d6b7a..39e807b228d 100644 --- a/cmake/RadiumSetupFunctions.cmake +++ b/cmake/RadiumSetupFunctions.cmake @@ -976,7 +976,7 @@ function(configure_radium_library) if(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_definitions(${ARGS_TARGET} ${PropertyQualifier} _DEBUG) - if(MSVC OR MSVC_IDE) + if((MSVC OR MSVC_IDE) AND NOT (TargetType STREQUAL INTERFACE_LIBRARY)) install(FILES $ DESTINATION bin) endif() endif() From ff09a820847960c269ff4e134ed6fd7754a555b3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 17:20:58 +0100 Subject: [PATCH 112/239] [dataflow-core] fix double deletion problem --- src/Dataflow/Core/DataflowGraph.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 5b8f452dea5..e6b6e44c494 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -47,10 +47,8 @@ bool DataflowGraph::execute() { } void DataflowGraph::destroy() { - std::for_each( m_nodesByLevel.begin(), m_nodesByLevel.end(), []( auto& level ) { - std::for_each( level.begin(), level.end(), []( auto node ) { node->destroy(); } ); - level.clear(); - } ); + std::for_each( + m_nodesByLevel.begin(), m_nodesByLevel.end(), []( auto& level ) { level.clear(); } ); m_nodesByLevel.clear(); m_nodes.clear(); m_factories.reset(); From a364b93f79357d6b67847d35b0a3fcde305c3675 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 18:59:30 +0100 Subject: [PATCH 113/239] [dataflow-core] fix memory leaks --- src/Dataflow/Core/Node.cpp | 8 +++++--- src/Dataflow/Core/Node.hpp | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index 28a5bdd2f5d..979e839bd91 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -9,15 +9,17 @@ namespace Core { using namespace Ra::Core::Utils; bool Node::s_uuidGeneratorInitialized { false }; -uuids::uuid_random_generator* Node::s_uidGenerator { nullptr }; +std::unique_ptr Node::s_uidGenerator { nullptr }; +std::unique_ptr Node::s_uuidSeeds { nullptr }; void Node::createUuidGenerator() { std::random_device rd; auto seed_data = std::array {}; std::generate( std::begin( seed_data ), std::end( seed_data ), std::ref( rd ) ); std::seed_seq seq( std::begin( seed_data ), std::end( seed_data ) ); - auto generator = new std::mt19937( seq ); - s_uidGenerator = new uuids::uuid_random_generator( *generator ); + s_uuidSeeds = std::make_unique( seq ); + s_uidGenerator = std::make_unique( s_uuidSeeds.get() ); + // delete generator; s_uuidGeneratorInitialized = true; } diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 3b8008d0c42..270450d9b05 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -216,7 +216,8 @@ class RA_DATAFLOW_API Node /// generator for uuid static bool s_uuidGeneratorInitialized; - static uuids::uuid_random_generator* s_uidGenerator; + static std::unique_ptr s_uidGenerator; + static std::unique_ptr s_uuidSeeds; static void createUuidGenerator(); public: From 000a0d7787ffc537276df601b3480ae9f10de861 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 18:59:51 +0100 Subject: [PATCH 114/239] [dataflow-core] remove compilation warning --- src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 506a61b6225..5b688950752 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -86,8 +86,8 @@ void GraphEditorView::buildAdapterRegistry( const NodeFactorySet& factories ) { m_editorRegistry->registerModel( typeName.c_str(), [f, this, creatorFactory]() -> std::unique_ptr { - nlohmann::json data; - auto node = f( data ); + nlohmann::json jsondata; + auto node = f( jsondata ); this->m_dataflowGraph->addNode( std::unique_ptr( node ) ); this->m_dataflowGraph->addFactory( creatorFactory ); return std::make_unique( this->m_dataflowGraph, node ); From 3afc95b6a44aadff7e4833a3a4354df97f010fff Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 3 Feb 2023 12:50:09 +0100 Subject: [PATCH 115/239] [dataflow-core] improve error management --- src/Dataflow/Core/DataflowGraph.cpp | 51 +++++++++++++---------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index e6b6e44c494..2970b46125f 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -184,8 +184,8 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { fromOutput = nodeFrom->getOutputs()[fromIndex]->getName(); } else { - LOG( logERROR ) << "Error when reading JSON file \"" - << "\": Output index " << fromIndex << " for node \"" + LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" + << ": Output index " << fromIndex << " for node \"" << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ")\" must be between 0 and " << nodeFrom->getOutputs().size() - 1 << ". Link not added."; @@ -193,8 +193,8 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } } else { - LOG( logERROR ) << "Error when reading JSON file \"" - << "\": Could not find a node associated with id " << nodeId + LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" + << ": Could not find a node associated with id " << nodeId << ". Link not added."; return false; } @@ -209,25 +209,25 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { toInput = nodeTo->getInputs()[toIndex]->getName(); } else { - LOG( logERROR ) << "Error when reading JSON file \"" - << "\": Input index " << toIndex << " for node \"" - << nodeFrom->getInstanceName() << " (" - << nodeFrom->getTypeName() << ")\" must be between 0 and " + LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" + << ": Input index " << toIndex << " for node \"" + << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() + << ")\" must be between 0 and " << nodeTo->getInputs().size() - 1 << ". Link not added."; return false; } } else { - LOG( logERROR ) << "Error when reading JSON file \"" - << "\": Could not find a node associated with id " << nodeId + LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" + << ": Could not find a node associated with id " << nodeId << ". Link not added."; return false; } if ( !addLink( nodeFrom, fromOutput, nodeTo, toInput ) ) { LOG( logERROR ) - << "Error when reading JSON file \"" - << "\": Could not add a link (missing or wrong information, please refer to " + << "DataflowGraph::loadFromJson: error when parsing JSON" + << ": Could not add a link (missing or wrong information, please refer to " "the previous error messages). Link not added."; return false; } @@ -265,7 +265,7 @@ std::pair DataflowGraph::addNode( std::unique_ptr newNode ) { return { true, addedNode }; } else { - return { false, newNode.get() }; + return { false, newNode.release() }; } } @@ -317,13 +317,15 @@ bool DataflowGraph::addLink( Node* nodeFrom, const std::string& nodeToInputName ) { // Check node "from" existence in the graph if ( findNode( nodeFrom ) == -1 ) { - LOG( logERROR ) << "DataflowGraph::addLink Unable to find initial node."; + LOG( logERROR ) << "DataflowGraph::addLink Unable to find initial node " + << nodeFrom->getInstanceName(); return false; } // Check node "to" existence in the graph if ( findNode( nodeTo ) == -1 ) { - LOG( logERROR ) << "DataflowGraph::addLink Unable to find destination node."; + LOG( logERROR ) << "DataflowGraph::addLink Unable to find destination node " + << nodeTo->getInstanceName(); return false; } @@ -364,12 +366,12 @@ bool DataflowGraph::addLink( Node* nodeFrom, // Compare types if ( nodeTo->getInputs()[foundTo]->getType() != nodeFrom->getOutputs()[foundFrom]->getType() ) { LOG( logERROR ) << "DataflowGraph::addLink type mismatch from " - << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ") /" + << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ") / " << nodeFrom->getOutputs()[foundFrom]->getName() << " (" << foundFrom - << " ++ " << nodeFrom->getOutputs()[foundFrom]->getType() << ")" + << " with type " << nodeFrom->getOutputs()[foundFrom]->getType() << ")" << " to " << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() << ") / " << nodeTo->getInputs()[foundTo]->getName() << " (" << foundTo - << " ++ " << nodeTo->getInputs()[foundTo]->getType() << ") "; + << " with type " << nodeTo->getInputs()[foundTo]->getType() << ") "; return false; } @@ -382,17 +384,8 @@ bool DataflowGraph::addLink( Node* nodeFrom, return false; } - // Try to connect ports - if ( !nodeTo->getInputs()[foundTo]->connect( nodeFrom->getOutputs()[foundFrom].get() ) ) { - LOG( logERROR ) << "DataflowGraph::addLink unable to connect from " - << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ") /" - << nodeFrom->getOutputs()[foundFrom]->getName() << " (" << foundFrom - << " ++ " << nodeFrom->getOutputs()[foundFrom]->getType() << ")" - << " to " << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() - << ") / " << nodeTo->getInputs()[foundTo]->getName() << " (" << foundTo - << " ++ " << nodeTo->getInputs()[foundTo]->getType() << ") "; - return false; - } + // port can be connected + nodeTo->getInputs()[foundTo]->connect( nodeFrom->getOutputs()[foundFrom].get() ); // The state of the graph changes, set it to not ready m_ready = false; m_shouldBeSaved = true; From 9b5f92787dde4c30502c65fe5977b0be97b3de92 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 3 Feb 2023 12:50:47 +0100 Subject: [PATCH 116/239] [unittests] improve coverage --- tests/unittest/CMakeLists.txt | 2 +- tests/unittest/Dataflow/graph.cpp | 360 ++++++++++++++++++ tests/unittest/Dataflow/graphinspect.cpp | 101 ----- .../unittest/data/Dataflow/NotAJsonFile.json | 1 + 4 files changed, 362 insertions(+), 102 deletions(-) create mode 100644 tests/unittest/Dataflow/graph.cpp delete mode 100644 tests/unittest/Dataflow/graphinspect.cpp create mode 100644 tests/unittest/data/Dataflow/NotAJsonFile.json diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index ade0b81db75..a538a8a6b8e 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -32,7 +32,7 @@ set(test_src Core/variableset.cpp Core/vectorarray.cpp Dataflow/customnodes.cpp - Dataflow/graphinspect.cpp + Dataflow/graph.cpp Dataflow/nodes.cpp Dataflow/serialization.cpp Dataflow/sourcesandsinks.cpp diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp new file mode 100644 index 00000000000..b38a54d1c15 --- /dev/null +++ b/tests/unittest/Dataflow/graph.cpp @@ -0,0 +1,360 @@ +#include + +#include + +#include +#include +#include + +using namespace Ra::Dataflow::Core; + +TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { + SECTION( "Creation of a graph" ) { + DataflowGraph g( "Test Graph" ); + auto result = g.loadFromJson( "data/Dataflow/NotAJsonFile.json" ); + REQUIRE( !result ); + nlohmann::json emptyJson = {}; + result = g.fromJson( emptyJson ); + REQUIRE( result ); + nlohmann::json noId = { { "model", { "name", "Core DataflowGraph" } } }; + result = g.fromJson( noId ); + REQUIRE( !result ); + g.destroy(); + + nlohmann::json noModel = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" } }; + result = g.fromJson( noModel ); + REQUIRE( !result ); + g.destroy(); + + nlohmann::json noGraph = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", { "name", "Core DataflowGraph" } } }; + result = g.fromJson( noGraph ); + REQUIRE( result ); + g.destroy(); + + nlohmann::json wrongFactory = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", { { "factories", { "NotAFactory" } } } } } } }; + result = g.fromJson( wrongFactory ); + REQUIRE( !result ); + g.destroy(); + + nlohmann::json NotANode = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "tototo" }, + { "model", + { { "instance", "NotANode" }, + { "name", "NotANode" } } } } } } } } } } }; + result = g.fromJson( NotANode ); + REQUIRE( !result ); + g.destroy(); + + nlohmann::json wrongConnection = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, + { "connections", { { { "out_id", "wrongId" } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + wrongConnection = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 2 } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + wrongConnection = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 0 }, + { "in_id", "wrongId" }, + { "in_index", 2 } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + wrongConnection = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 0 }, + { "in_id", "{1111111b-2222-3333-4444-555555555555}" }, + { "in_index", 2 } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + wrongConnection = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 0 }, + { "in_id", "{1111111b-2222-3333-4444-555555555555}" }, + { "in_index", 0 } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + nlohmann::json goodSimpleGraph = { + { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "model", + { { "instance", "Test Graph Inline" }, + { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", + { { "instance", "SinkFloat" }, { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 0 }, + { "in_id", "{1111111b-2222-3333-4444-555555555555}" }, + { "in_index", 0 } } } } } } } } }; + result = g.fromJson( goodSimpleGraph ); + REQUIRE( result ); + + // trying to add a duplicated node + auto duplicatedNodeName = new Sources::SingleDataSourceNode( "SourceFloat" ); + auto [r, rejectedNode] = g.addNode( std::unique_ptr( duplicatedNodeName ) ); + REQUIRE( !r ); + REQUIRE( rejectedNode == duplicatedNodeName ); + delete duplicatedNodeName; + + auto sinkFloatNode = g.getNode( "Sink" ); + REQUIRE( sinkFloatNode == nullptr ); + sinkFloatNode = g.getNode( "SinkFloat" ); + REQUIRE( sinkFloatNode != nullptr ); + auto sourceFloatNode = g.getNode( "SourceFloat" ); + REQUIRE( sourceFloatNode != nullptr ); + + auto sourceIntNode = new Sources::IntSource( "SourceInt" ); + auto sinkIntNode = new Sinks::IntSink( "SinkInt" ); + // node not found + result = g.removeLink( sinkIntNode, "from" ); + REQUIRE( !result ); + + // "from" node not found + result = g.addLink( sourceIntNode, "out", sinkIntNode, "in" ); + REQUIRE( !result ); + + auto resPair = g.addNode( std::unique_ptr( sourceIntNode ) ); + REQUIRE( resPair.first ); + // "to" node not found + result = g.addLink( sourceIntNode, "out", sinkIntNode, "in" ); + REQUIRE( !result ); + + resPair = g.addNode( std::unique_ptr( sinkIntNode ) ); + REQUIRE( resPair.first ); + // output port of "from" node not found + result = g.addLink( sourceIntNode, "out", sinkIntNode, "in" ); + REQUIRE( !result ); + + // input port of "to" node not found + result = g.addLink( sourceIntNode, "to", sinkIntNode, "in" ); + REQUIRE( !result ); + + // link OK + result = g.addLink( sourceIntNode, "to", sinkIntNode, "from" ); + REQUIRE( result ); + + // from por of "to" node already linked + result = g.addLink( sourceIntNode, "to", sinkIntNode, "from" ); + REQUIRE( !result ); + + // type mismatch + result = g.addLink( sourceIntNode, "to", sinkFloatNode, "from" ); + REQUIRE( !result ); + + // remove link OK + result = g.removeLink( sinkIntNode, "from" ); + REQUIRE( result ); + + // input port not found to remove its link + result = g.removeLink( sinkIntNode, "in" ); + REQUIRE( !result ); + + // compile the graph + result = g.compile(); + REQUIRE( result ); + + // clear the graph + g.clearNodes(); + + // Nodes can't be found + auto nullNode = g.getNode( "SourceInt" ); + REQUIRE( nullNode == nullptr ); + nullNode = g.getNode( "SinkInt" ); + REQUIRE( nullNode == nullptr ); + // Nodes can't be found + nullNode = g.getNode( "SourceFloat" ); + REQUIRE( nullNode == nullptr ); + nullNode = g.getNode( "SinkFloat" ); + REQUIRE( nullNode == nullptr ); + + // destroy everything + g.destroy(); + } + + SECTION( "Inspection of a graph" ) { + DataflowGraph g( "" ); + g.loadFromJson( "data/Dataflow/ExampleGraph.json" ); + + // Factories used by the graph + auto factories = g.getNodeFactories(); + REQUIRE( factories != nullptr ); + std::cout << "Used factories by the graph \"" << g.getInstanceName() << "\" with type \"" + << g.getTypeName() << "\" :\n"; + for ( const auto& f : *( factories.get() ) ) { + std::cout << "\t" << f.first << "\n"; + } + + // Nodes of the graph + auto nodes = g.getNodes(); + REQUIRE( nodes != nullptr ); + std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() + << ") :\n"; + REQUIRE( nodes->size() == g.getNodesCount() ); + for ( const auto& n : *( nodes ) ) { + std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() + << "\"\n"; + // Inspect input, output and interfaces of the node + std::cout << "\t\tInput ports :\n"; + for ( const auto& p : n->getInputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tOutput ports :\n"; + for ( const auto& p : n->getOutputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tInterface ports :\n"; + for ( const auto& p : n->getInterfaces() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + } + + // Nodes by level after the compilation + auto c = g.compile(); + REQUIRE( c == true ); + auto cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto n : ( *cn )[i] ) { + std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + } + } + + // describe the graph interface : inputs and outputs port of the whole graph (not of the + // nodes) + std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; + auto inputs = g.getAllDataSetters(); + std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + } + auto outputs = g.getAllDataGetters(); + std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + } + + // removing the boolean sink from the graph + auto n = g.getNode( "validation value" ); + REQUIRE( n->getInstanceName() == "validation value" ); + c = g.removeNode( n ); + REQUIRE( c == true ); + REQUIRE( n == nullptr ); + c = g.compile(); + REQUIRE( c == true ); + // Simplified graph after compilation + cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level, after sink deletion :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto nn : ( *cn )[i] ) { + std::cout << "\t\t\"" << nn->getInstanceName() << "\"\n"; + } + } + + // the source "Validator" is no more in level 0 as it is non reachable from a sink in the + // graph. + bool found = false; + for ( const auto nn : ( *cn )[0] ) { + if ( nn->getInstanceName() == "Validator" ) { found = true; } + } + REQUIRE( found == false ); + } +} diff --git a/tests/unittest/Dataflow/graphinspect.cpp b/tests/unittest/Dataflow/graphinspect.cpp deleted file mode 100644 index 9202fd3d91b..00000000000 --- a/tests/unittest/Dataflow/graphinspect.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include - -#include - -#include -using namespace Ra::Dataflow::Core; - -TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { - SECTION( "Inspection of a graph" ) { - DataflowGraph g( "" ); - g.loadFromJson( "data/Dataflow/ExampleGraph.json" ); - - // Factories used by the graph - auto factories = g.getNodeFactories(); - REQUIRE( factories != nullptr ); - std::cout << "Used factories by the graph \"" << g.getInstanceName() << "\" with type \"" - << g.getTypeName() << "\" :\n"; - for ( const auto& f : *( factories.get() ) ) { - std::cout << "\t" << f.first << "\n"; - } - - // Nodes of the graph - auto nodes = g.getNodes(); - REQUIRE( nodes != nullptr ); - std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() - << ") :\n"; - REQUIRE( nodes->size() == g.getNodesCount() ); - for ( const auto& n : *( nodes ) ) { - std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() - << "\"\n"; - // Inspect input, output and interfaces of the node - std::cout << "\t\tInput ports :\n"; - for ( const auto& p : n->getInputs() ) { - std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() - << "\n"; - } - std::cout << "\t\tOutput ports :\n"; - for ( const auto& p : n->getOutputs() ) { - std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() - << "\n"; - } - std::cout << "\t\tInterface ports :\n"; - for ( const auto& p : n->getInterfaces() ) { - std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() - << "\n"; - } - } - - // Nodes by level after the compilation - auto c = g.compile(); - REQUIRE( c == true ); - auto cn = g.getNodesByLevel(); - std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { - std::cout << "\tLevel " << i << " :\n"; - for ( const auto n : ( *cn )[i] ) { - std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; - } - } - - // describe the graph interface : inputs and outputs port of the whole graph (not of the - // nodes) - std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; - auto inputs = g.getAllDataSetters(); - std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : inputs ) { - std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; - } - auto outputs = g.getAllDataGetters(); - std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : outputs ) { - std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; - } - - // removing the boolean sink from the graph - auto n = g.getNode( "validation value" ); - REQUIRE( n->getInstanceName() == "validation value" ); - c = g.removeNode( n ); - REQUIRE( c == true ); - REQUIRE( n == nullptr ); - c = g.compile(); - REQUIRE( c == true ); - // Simplified graph after compilation - cn = g.getNodesByLevel(); - std::cout << "Nodes of the graph, sorted by level, after sink deletion :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { - std::cout << "\tLevel " << i << " :\n"; - for ( const auto nn : ( *cn )[i] ) { - std::cout << "\t\t\"" << nn->getInstanceName() << "\"\n"; - } - } - - // the source "Validator" is no more in level 0 as it is non reachable from a sink in the - // graph. - bool found = false; - for ( const auto nn : ( *cn )[0] ) { - if ( nn->getInstanceName() == "Validator" ) { found = true; } - } - REQUIRE( found == false ); - } -} diff --git a/tests/unittest/data/Dataflow/NotAJsonFile.json b/tests/unittest/data/Dataflow/NotAJsonFile.json new file mode 100644 index 00000000000..4fc8f92e8c1 --- /dev/null +++ b/tests/unittest/data/Dataflow/NotAJsonFile.json @@ -0,0 +1 @@ +# This file is not a valid json file From dcebe95a103d05ac4472b8616127ba33480c0830 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 08:41:12 +0100 Subject: [PATCH 117/239] [core] fix compilation warning --- src/Core/Random/RandomPointSet.cpp | 8 +++---- src/Core/Random/RandomPointSet.hpp | 27 +++++++++++++++++++++-- src/Core/Random/RandomPointSet.inl | 32 --------------------------- src/Core/filelist.cmake | 35 ++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 38 deletions(-) delete mode 100644 src/Core/Random/RandomPointSet.inl diff --git a/src/Core/Random/RandomPointSet.cpp b/src/Core/Random/RandomPointSet.cpp index 2797bfc6162..c375138d9e4 100644 --- a/src/Core/Random/RandomPointSet.cpp +++ b/src/Core/Random/RandomPointSet.cpp @@ -4,7 +4,7 @@ namespace Ra { namespace Core { namespace Random { -FibonacciSequence::FibonacciSequence( size_t number ) : n { std::max( size_t( 5 ), number ) } {}; +FibonacciSequence::FibonacciSequence( size_t number ) : n { std::max( size_t( 5 ), number ) } {} size_t FibonacciSequence::range() { return n; @@ -22,7 +22,7 @@ Scalar VanDerCorputSequence::operator()( unsigned int bits ) { return Scalar( float( bits ) * 2.3283064365386963e-10 ); // / 0x100000000 } -FibonacciPointSet::FibonacciPointSet( size_t n ) : seq( n ) {}; +FibonacciPointSet::FibonacciPointSet( size_t n ) : seq( n ) {} size_t FibonacciPointSet::range() { return seq.range(); @@ -31,7 +31,7 @@ Ra::Core::Vector2 FibonacciPointSet::operator()( size_t i ) { return { seq( i ), Scalar( i ) / Scalar( range() ) }; } -HammersleyPointSet::HammersleyPointSet( size_t number ) : n( number ) {}; +HammersleyPointSet::HammersleyPointSet( size_t number ) : n( number ) {} size_t HammersleyPointSet::range() { return n; @@ -42,7 +42,7 @@ Ra::Core::Vector2 HammersleyPointSet::operator()( size_t i ) { } MersenneTwisterPointSet::MersenneTwisterPointSet( size_t number ) : - gen( 0 ), seq( 0., 1. ), n( number ) {}; + gen( 0 ), seq( 0., 1. ), n( number ) {} size_t MersenneTwisterPointSet::range() { return n; diff --git a/src/Core/Random/RandomPointSet.hpp b/src/Core/Random/RandomPointSet.hpp index c6a1368c1ec..6d4c39a8a44 100644 --- a/src/Core/Random/RandomPointSet.hpp +++ b/src/Core/Random/RandomPointSet.hpp @@ -150,8 +150,31 @@ class SphericalPointSet Ra::Core::Vector3 operator()( size_t i ); }; +// ------------------------------------------------------------------------ +// ------------------------ inline methods -------------------------------- + +template +Ra::Core::Vector3 SphericalPointSet::projectOnSphere( const Ra::Core::Vector2&& pt ) { + Scalar theta = std::acos( 2 * pt[1] - 1 ); // 0 <= tetha <= pi + Scalar phi = 2_ra * Scalar( M_PI ) * pt[0]; + return { std::sin( theta ) * std::cos( phi ), + std::sin( theta ) * std::sin( phi ), + std::cos( theta ) }; +} + +template +SphericalPointSet::SphericalPointSet( size_t n ) : p( n ) {} + +template +size_t SphericalPointSet::range() { + return p.range(); +} + +template +Ra::Core::Vector3 SphericalPointSet::operator()( size_t i ) { + return projectOnSphere( p( i ) ); +} + } // namespace Random } // namespace Core } // namespace Ra - -#include diff --git a/src/Core/Random/RandomPointSet.inl b/src/Core/Random/RandomPointSet.inl deleted file mode 100644 index 525cbbfc329..00000000000 --- a/src/Core/Random/RandomPointSet.inl +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Core { -namespace Random { - -template -Ra::Core::Vector3 SphericalPointSet::projectOnSphere( const Ra::Core::Vector2&& pt ) { - Scalar theta = std::acos( 2 * pt[1] - 1 ); // 0 <= tetha <= pi - Scalar phi = 2_ra * Scalar( M_PI ) * pt[0]; - return { std::sin( theta ) * std::cos( phi ), - std::sin( theta ) * std::sin( phi ), - std::cos( theta ) }; -} - -template -SphericalPointSet::SphericalPointSet( size_t n ) : p( n ) {} - -template -size_t SphericalPointSet::range() { - return p.range(); -} - -template -Ra::Core::Vector3 SphericalPointSet::operator()( size_t i ) { - return projectOnSphere( p( i ) ); -} - -} // namespace Random -} // namespace Core -} // namespace Ra diff --git a/src/Core/filelist.cmake b/src/Core/filelist.cmake index 4760bf220f9..7397b4bafc8 100644 --- a/src/Core/filelist.cmake +++ b/src/Core/filelist.cmake @@ -138,3 +138,38 @@ set(core_headers Utils/TypesUtils.hpp Utils/Version.hpp ) + +set(core_inlines + Animation/HandleArray.inl + Animation/Sequence.inl + Asset/AnimationData.inl + Asset/BlinnPhongMaterialData.inl + Asset/Camera.inl + Asset/FileData.inl + Asset/GeometryData.inl + Asset/HandleData.inl + Asset/LightData.inl + Asset/MaterialData.inl + Containers/AdjacencyList.inl + Containers/Grid.inl + Containers/Tex.inl + Geometry/Curve2D.inl + Geometry/DistanceQueries.inl + Geometry/IndexedGeometry.inl + Geometry/MeshPrimitives.inl + Geometry/PolyLine.inl + Geometry/Spline.inl + Geometry/TopologicalMesh.inl + Geometry/TriangleMesh.inl + Geometry/deprecated/TopologicalMesh.inl + Math/DualQuaternion.inl + Math/LinearAlgebra.inl + Math/Math.inl + Math/Quadric.inl + Random/RandomPointSet.inl + Utils/Attribs.inl + Utils/BijectiveAssociation.inl + Utils/CircularIndex.inl + Utils/Index.inl + Utils/IndexMap.inl +) From a55e2e9c6e2cff0595636522a1370a19cdbdc9f9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 08:46:39 +0100 Subject: [PATCH 118/239] [dataflow-core] fix compilation warning --- src/Dataflow/Core/DataflowGraph.cpp | 2 +- .../Core/Nodes/Sources/CoreDataSources.hpp | 48 +++++++++---------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index 2970b46125f..c00573a30f2 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -238,7 +238,7 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { bool DataflowGraph::canAdd( const Node* newNode ) const { return findNode( newNode ) == -1; -}; +} std::pair DataflowGraph::addNode( std::unique_ptr newNode ) { std::map m_mapInputs; diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 730d90702b2..361d4e2f52d 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -23,7 +23,7 @@ namespace Sources { using PREFIX##UnaryFunctionSource = FunctionSourceNode; \ using PREFIX##BinaryFunctionSource = FunctionSourceNode; \ using PREFIX##UnaryPredicateSource = FunctionSourceNode; \ - using PREFIX##BinaryPredicateSource = FunctionSourceNode + using PREFIX##BinaryPredicateSource = FunctionSourceNode; using namespace Ra::Core; @@ -32,24 +32,24 @@ using namespace Ra::Core; // https://en.cppreference.com/w/cpp/container/vector_bool Right now, there is no // Ra::Core::VectorArray of bool using BooleanSource = SingleDataSourceNode; -DECLARE_COREDATA_SOURCES( Float, float ); -DECLARE_COREDATA_SOURCES( Double, double ); -DECLARE_COREDATA_SOURCES( Scalar, Scalar ); -DECLARE_COREDATA_SOURCES( Int, int ); -DECLARE_COREDATA_SOURCES( UInt, unsigned int ); -DECLARE_COREDATA_SOURCES( Color, Utils::Color ); -DECLARE_COREDATA_SOURCES( Vector2f, Vector2f ); -DECLARE_COREDATA_SOURCES( Vector2d, Vector2d ); -DECLARE_COREDATA_SOURCES( Vector3f, Vector3f ); -DECLARE_COREDATA_SOURCES( Vector3d, Vector3d ); -DECLARE_COREDATA_SOURCES( Vector4f, Vector4f ); -DECLARE_COREDATA_SOURCES( Vector4d, Vector4d ); -DECLARE_COREDATA_SOURCES( Vector2i, Vector2i ); -DECLARE_COREDATA_SOURCES( Vector3i, Vector3i ); -DECLARE_COREDATA_SOURCES( Vector4i, Vector4i ); -DECLARE_COREDATA_SOURCES( Vector2ui, Vector2ui ); -DECLARE_COREDATA_SOURCES( Vector3ui, Vector3ui ); -DECLARE_COREDATA_SOURCES( Vector4ui, Vector4ui ); +DECLARE_COREDATA_SOURCES( Float, float ) +DECLARE_COREDATA_SOURCES( Double, double ) +DECLARE_COREDATA_SOURCES( Scalar, Scalar ) +DECLARE_COREDATA_SOURCES( Int, int ) +DECLARE_COREDATA_SOURCES( UInt, unsigned int ) +DECLARE_COREDATA_SOURCES( Color, Utils::Color ) +DECLARE_COREDATA_SOURCES( Vector2f, Vector2f ) +DECLARE_COREDATA_SOURCES( Vector2d, Vector2d ) +DECLARE_COREDATA_SOURCES( Vector3f, Vector3f ) +DECLARE_COREDATA_SOURCES( Vector3d, Vector3d ) +DECLARE_COREDATA_SOURCES( Vector4f, Vector4f ) +DECLARE_COREDATA_SOURCES( Vector4d, Vector4d ) +DECLARE_COREDATA_SOURCES( Vector2i, Vector2i ) +DECLARE_COREDATA_SOURCES( Vector3i, Vector3i ) +DECLARE_COREDATA_SOURCES( Vector4i, Vector4i ) +DECLARE_COREDATA_SOURCES( Vector2ui, Vector2ui ) +DECLARE_COREDATA_SOURCES( Vector3ui, Vector3ui ) +DECLARE_COREDATA_SOURCES( Vector4ui, Vector4ui ) #undef DECLARE_COREDATA_SOURCES @@ -75,11 +75,11 @@ DECLARE_COREDATA_SOURCES( Vector4ui, Vector4ui ); return true; \ } -SPECIALIZE_EDITABLE_SOURCE( bool, boolean ); -SPECIALIZE_EDITABLE_SOURCE( float, number ); -SPECIALIZE_EDITABLE_SOURCE( double, number ); -SPECIALIZE_EDITABLE_SOURCE( int, value ); -SPECIALIZE_EDITABLE_SOURCE( unsigned int, value ); +SPECIALIZE_EDITABLE_SOURCE( bool, boolean ) +SPECIALIZE_EDITABLE_SOURCE( float, number ) +SPECIALIZE_EDITABLE_SOURCE( double, number ) +SPECIALIZE_EDITABLE_SOURCE( int, value ) +SPECIALIZE_EDITABLE_SOURCE( unsigned int, value ) // Color specialization need different implementation (as well as any Ra::Vectorxx) template <> From 02219a02d5c43d291801cfb8b77e59500990c233 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 08:47:31 +0100 Subject: [PATCH 119/239] [dataflow-qtgui] fix compilation warning --- src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp index 63f4b054fe7..e6ad3d52668 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.hpp @@ -74,7 +74,7 @@ RA_DATAFLOW_API bool updateWidget( QWidget* widget, EditableParameterBase* edita */ RA_DATAFLOW_API void initializeWidgetFactory(); -}; // namespace WidgetFactory +} // namespace WidgetFactory } // namespace GraphEditor } // namespace QtGui From 4be73adbbd2a7f00994260966168e36129648780 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 10:13:02 +0100 Subject: [PATCH 120/239] [dataflow] use std::type_index instead of hash for port type management --- src/Dataflow/Core/DataflowGraph.cpp | 7 ++-- src/Dataflow/Core/Port.hpp | 40 +++++++------------ .../QtGui/GraphEditor/NodeAdapterModel.cpp | 8 ++-- .../QtGui/GraphEditor/NodeAdapterModel.hpp | 3 +- 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index c00573a30f2..ed8370482fa 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -364,14 +364,15 @@ bool DataflowGraph::addLink( Node* nodeFrom, } // Compare types - if ( nodeTo->getInputs()[foundTo]->getType() != nodeFrom->getOutputs()[foundFrom]->getType() ) { + if ( !( nodeTo->getInputs()[foundTo]->getType() == + nodeFrom->getOutputs()[foundFrom]->getType() ) ) { LOG( logERROR ) << "DataflowGraph::addLink type mismatch from " << nodeFrom->getInstanceName() << " (" << nodeFrom->getTypeName() << ") / " << nodeFrom->getOutputs()[foundFrom]->getName() << " (" << foundFrom - << " with type " << nodeFrom->getOutputs()[foundFrom]->getType() << ")" + << " with type " << nodeFrom->getOutputs()[foundFrom]->getTypeName() << ")" << " to " << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() << ") / " << nodeTo->getInputs()[foundTo]->getName() << " (" << foundTo - << " with type " << nodeTo->getInputs()[foundTo]->getType() << ") "; + << " with type " << nodeTo->getInputs()[foundTo]->getTypeName() << ") "; return false; } diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index 66b59b0bf9b..1171dcef77a 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -33,8 +33,8 @@ class RA_DATAFLOW_API PortBase private: /// The name of the port. std::string m_name { "" }; - /// The port's data's type's hash. - size_t m_type { 0 }; + /// The port's data's type's index. + std::type_index m_type; /// A pointer to the node this port belongs to. Node* m_node { nullptr }; @@ -57,7 +57,7 @@ class RA_DATAFLOW_API PortBase /// @param name The name of the port. /// @param type The data's type's hash. /// @param node The pointer to the node associated with the port. - PortBase( const std::string& name, size_t type, Node* node ); + PortBase( const std::string& name, std::type_index type, Node* node ); /// @} /// \brief make PortBase a base abstract class @@ -65,8 +65,8 @@ class RA_DATAFLOW_API PortBase /// Gets the port's name. const std::string& getName(); - /// Gets the hash of the type of the data. - size_t getType(); + /// Gets the type of the data (efficient for comparisons). + std::type_index getType(); /// Gets a pointer to the node this port belongs to. Node* getNode(); virtual bool hasData(); @@ -105,7 +105,7 @@ class RA_DATAFLOW_API PortBase template void setData( T* data ); - virtual std::string getTypeName() = 0; + std::string getTypeName(); }; /** @@ -154,8 +154,6 @@ class PortOut : public PortBase bool disconnect() override; /// Returns a portIn of the same type PortBase* reflect( Node* node, std::string name ) override; - - std::string getTypeName() override; }; /** @@ -204,24 +202,26 @@ class PortIn : public PortBase, PortBase* reflect( Node* node, std::string name ) override; /// Returns true if the port is an input port bool is_input() override; - - std::string getTypeName() override; }; // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- -inline PortBase::PortBase( const std::string& name, size_t type, Node* node ) : +inline PortBase::PortBase( const std::string& name, std::type_index type, Node* node ) : m_name( name ), m_type( type ), m_node( node ) {} inline const std::string& PortBase::getName() { return m_name; } -inline size_t PortBase::getType() { +inline std::type_index PortBase::getType() { return m_type; } +inline std::string PortBase::getTypeName() { + return TypeInternal::makeTypeReadable( m_type.name() ); +} + inline Node* PortBase::getNode() { return m_node; } @@ -247,8 +247,7 @@ inline bool PortBase::accept( PortBase* other ) { } template -PortOut::PortOut( const std::string& name, Node* node ) : - PortBase( name, typeid( T ).hash_code(), node ) {} +PortOut::PortOut( const std::string& name, Node* node ) : PortBase( name, typeid( T ), node ) {} template T& PortOut::getData() { @@ -281,16 +280,6 @@ bool PortOut::connect( PortBase* o ) { return m_isLinked; } -template -std::string PortOut::getTypeName() { - return simplifiedDemangledType(); -} - -template -std::string PortIn::getTypeName() { - return simplifiedDemangledType(); -} - template void PortBase::setData( T* data ) { if ( !is_input() ) { @@ -357,8 +346,7 @@ PortBase* PortOut::reflect( Node* node, std::string name ) { * @tparam T */ template -PortIn::PortIn( const std::string& name, Node* node ) : - PortBase( name, typeid( T ).hash_code(), node ) {} +PortIn::PortIn( const std::string& name, Node* node ) : PortBase( name, typeid( T ), node ) {} template PortBase* PortIn::getLink() { diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index ed5fa93f173..1c1d20bddb7 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -106,12 +106,12 @@ QtNodes::NodeDataType NodeAdapterModel::dataType( QtNodes::PortType portType, switch ( portType ) { case QtNodes::PortType::In: { std::string mandatory = ( m_node->getInputs()[portIndex]->isLinkMandatory() ) ? "*" : ""; - return IOToDataType( m_node->getInputs()[portIndex]->getType(), + return IOToDataType( m_node->getInputs()[portIndex]->getTypeName(), m_node->getInputs()[portIndex]->getName() + mandatory ); } case QtNodes::PortType::Out: { - return IOToDataType( m_node->getOutputs()[portIndex]->getType(), + return IOToDataType( m_node->getOutputs()[portIndex]->getTypeName(), m_node->getOutputs()[portIndex]->getName() ); } default: @@ -145,9 +145,9 @@ void NodeAdapterModel::setInData( std::shared_ptr data, int p checkConnections(); } -QtNodes::NodeDataType NodeAdapterModel::IOToDataType( size_t hashType, +QtNodes::NodeDataType NodeAdapterModel::IOToDataType( const std::string& typeName, const std::string& ioName ) const { - return QtNodes::NodeDataType { std::to_string( hashType ).c_str(), ioName.c_str() }; + return QtNodes::NodeDataType { typeName.c_str(), ioName.c_str() }; } void NodeAdapterModel::updateState() { diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp index f538752b067..b7ae7633d50 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp @@ -43,7 +43,8 @@ class RA_DATAFLOW_API NodeAdapterModel : public QtNodes::NodeDataModel void addMetaData( QJsonObject& json ) override; private: - QtNodes::NodeDataType IOToDataType( size_t hashType, const std::string& ioName ) const; + QtNodes::NodeDataType IOToDataType( const std::string& typeName, + const std::string& ioName ) const; void checkConnections() const; From 2c7ae867eeb95078204c27ef1cbf19a9bc5e4b21 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 10:48:31 +0100 Subject: [PATCH 121/239] [dataflow] make Node's uuid generator private --- src/Dataflow/Core/Node.cpp | 1 + src/Dataflow/Core/Node.hpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index 979e839bd91..d28d13c9ebe 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -13,6 +13,7 @@ std::unique_ptr Node::s_uidGenerator { nullptr }; std::unique_ptr Node::s_uuidSeeds { nullptr }; void Node::createUuidGenerator() { + if ( s_uuidGeneratorInitialized ) { return; } std::random_device rd; auto seed_data = std::array {}; std::generate( std::begin( seed_data ), std::end( seed_data ), std::ref( rd ) ); diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 270450d9b05..674059c70a6 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -214,6 +214,7 @@ class RA_DATAFLOW_API Node /// The uuid of the node uuids::uuid m_uuid; + private: /// generator for uuid static bool s_uuidGeneratorInitialized; static std::unique_ptr s_uidGenerator; From 66e7bcf2ecc1f06299b12cd295b9157f7ea8bca5 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 11:57:31 +0100 Subject: [PATCH 122/239] [core] add type demangler from typeindex --- src/Core/Utils/TypesUtils.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index fc20cbe6cae..a8e97c79229 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -10,6 +10,7 @@ #endif #include +#include #include @@ -25,6 +26,9 @@ const char* demangleType() noexcept; template const char* demangleType( const T& ) noexcept; +/// Return the human readable version of the given type name +std::string demangleType( const std::type_index& typeName ) noexcept; + // Check if a type is a container with access to its element type and number // adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable namespace detail { From 935e77b41b5fb159a431f5395f11f1786a58826a Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 11:58:03 +0100 Subject: [PATCH 123/239] [dataflow] add type demangler from typeindex --- src/Dataflow/Core/Port.hpp | 2 +- src/Dataflow/Core/TypeDemangler.hpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index 1171dcef77a..9aba0598511 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -219,7 +219,7 @@ inline std::type_index PortBase::getType() { } inline std::string PortBase::getTypeName() { - return TypeInternal::makeTypeReadable( m_type.name() ); + return simplifiedDemangledType( m_type ); } inline Node* PortBase::getNode() { diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index fd65a6ae26a..137b711e272 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -15,6 +15,8 @@ const char* simplifiedDemangledType() noexcept; template const char* simplifiedDemangledType( const T& ) noexcept; +std::string simplifiedDemangledType( const std::type_index& typeName ) noexcept; + // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- @@ -37,6 +39,10 @@ const char* simplifiedDemangledType( const T& ) noexcept { return simplifiedDemangledType(); } +inline std::string simplifiedDemangledType( const std::type_index& typeName ) noexcept { + return TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType( typeName ) ); +} + } // namespace Core } // namespace Dataflow } // namespace Ra From b5b880f3cf6eb4afeeb249082b2dd14ac7448b3c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 12:53:38 +0100 Subject: [PATCH 124/239] [unittests] add test on type demangler from typeindex --- tests/unittest/Core/typeutils.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unittest/Core/typeutils.cpp b/tests/unittest/Core/typeutils.cpp index f68a0922bce..5abb6c13690 100644 --- a/tests/unittest/Core/typeutils.cpp +++ b/tests/unittest/Core/typeutils.cpp @@ -26,6 +26,9 @@ TEST_CASE( "Core/Utils/TypesUtils", "[Core][Utils][TypesUtils]" ) { demangledName = std::string( demangleType() ); REQUIRE( demangledName == "TypeTests::TypeName_struct" ); + + demangledName = demangleType( std::type_index( typeid( std::vector ) ) ); + REQUIRE( demangledName == "std::vector>" ); } SECTION( "Demangle from instance" ) { From 811b871c0d5f3dc5eca15c3ab79aff391cca56c5 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 13:07:41 +0100 Subject: [PATCH 125/239] [core] add missing RA_CORE_API --- src/Core/Utils/TypesUtils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index a8e97c79229..3e095365cdf 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -27,7 +27,7 @@ template const char* demangleType( const T& ) noexcept; /// Return the human readable version of the given type name -std::string demangleType( const std::type_index& typeName ) noexcept; +RA_CORE_API std::string demangleType( const std::type_index& typeName ) noexcept; // Check if a type is a container with access to its element type and number // adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable From d9a78791cb86453b47f8c9e2bf34889cb1048a7c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 13:07:58 +0100 Subject: [PATCH 126/239] [dataflow] add missing RA_DATAFLOW_API --- src/Dataflow/Core/TypeDemangler.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index 137b711e272..0a685ab6d0c 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -15,7 +15,7 @@ const char* simplifiedDemangledType() noexcept; template const char* simplifiedDemangledType( const T& ) noexcept; -std::string simplifiedDemangledType( const std::type_index& typeName ) noexcept; +RA_DATAFLOW_API std::string simplifiedDemangledType( const std::type_index& typeName ) noexcept; // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- From 493fa484c830cef716b293e628f1ed93176d419f Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 14:50:00 +0100 Subject: [PATCH 127/239] [core] fix typeutils on windows --- src/Core/Utils/TypesUtils.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index 3e095365cdf..fa1ee4348b0 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -1,6 +1,5 @@ #pragma once - -#include +#include #ifndef _WIN32 # include @@ -20,14 +19,14 @@ namespace Utils { /// Return the human readable version of the type name T template -const char* demangleType() noexcept; +std::string demangleType() noexcept; /// Return the human readable version of the given object's type template -const char* demangleType( const T& ) noexcept; +std::string demangleType( const T& ) noexcept; /// Return the human readable version of the given type name -RA_CORE_API std::string demangleType( const std::type_index& typeName ) noexcept; +std::string demangleType( const std::type_index& typeIndex ) noexcept; // Check if a type is a container with access to its element type and number // adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable From 23d3364cac666f58fbec7f71991d07711464f35c Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 17 Mar 2023 17:19:51 +0100 Subject: [PATCH 128/239] [core] fix merge conflicts --- src/Core/Utils/TypesUtils.hpp | 78 ++++++++++++++++------------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index fa1ee4348b0..6305cad088f 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -118,55 +118,49 @@ struct TypeList { }; #ifdef _WIN32 -// On windows (since MSVC 2019), typeid( T ).name() returns the demangled name -template -const char* demangleType() noexcept { - static auto demangled_name = []() { - std::string retval { typeid( T ).name() }; - removeAllInString( retval, "class " ); - removeAllInString( retval, "struct " ); - replaceAllInString( retval, ",", ", " ); - replaceAllInString( retval, "> >", ">>" ); - return retval; - }(); - - return demangled_name.data(); +// On windows (since MSVC 2019), typeid( T ).name() (and then typeIndex.name() returns the demangled +// name +inline std::string demangleType( const std::type_index& typeIndex ) noexcept { + std::string retval = typeIndex.name(); + removeAllInString( retval, "class " ); + removeAllInString( retval, "struct " ); + removeAllInString( retval, "__cdecl" ); + replaceAllInString( retval, "& __ptr64", "&" ); + replaceAllInString( retval, ",", ", " ); + replaceAllInString( retval, " >", ">" ); + replaceAllInString( retval, "__int64", "long" ); + replaceAllInString( retval, "const &", "const&" ); + return retval; } #else +// On Linux/macos, use the C++ ABI demangler +inline std::string demangleType( const std::type_index& typeIndex ) noexcept { + int error = 0; + std::string retval; + char* name = abi::__cxa_demangle( typeIndex.name(), 0, 0, &error ); + if ( error == 0 ) { retval = name; } + else { + // error : -1 --> memory allocation failed + // error : -2 --> not a valid mangled name + // error : other --> __cxa_demangle + retval = std::string( "Type demangler error : " ) + std::to_string( error ); + } + std::free( name ); + removeAllInString( retval, "__1::" ); // or "::__1" ? + replaceAllInString( retval, " >", ">" ); + return retval; +} +#endif template -const char* demangleType() noexcept { +std::string demangleType() noexcept { // once per one type - static auto demangled_name = []() { - int error = 0; - std::string retval; - char* name = abi::__cxa_demangle( typeid( T ).name(), 0, 0, &error ); - - switch ( error ) { - case 0: - retval = name; - break; - case -1: - retval = "memory allocation failed"; - break; - case -2: - retval = "not a valid mangled name"; - break; - default: - retval = "__cxa_demangle failed"; - break; - } - std::free( name ); - removeAllInString( retval, "__1::" ); // or "::__1" ? - replaceAllInString( retval, "> >", ">>" ); - return retval; - }(); - - return demangled_name.data(); + static auto demangled_name = demangleType( std::type_index( typeid( T ) ) ); + return demangled_name; } -#endif + // calling with instances template -const char* demangleType( const T& ) noexcept { +std::string demangleType( const T& ) noexcept { return demangleType(); } From fddbb9afb239600f9ae5c4b235898c8a824593f7 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 8 Feb 2023 11:21:13 +0100 Subject: [PATCH 129/239] [unittests] Simplify typeutils --- tests/unittest/Core/typeutils.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/unittest/Core/typeutils.cpp b/tests/unittest/Core/typeutils.cpp index 5abb6c13690..12e4633fdda 100644 --- a/tests/unittest/Core/typeutils.cpp +++ b/tests/unittest/Core/typeutils.cpp @@ -16,15 +16,15 @@ TEST_CASE( "Core/Utils/TypesUtils", "[Core][Utils][TypesUtils]" ) { SECTION( "Demangle from typename" ) { using Ra::Core::Utils::demangleType; - REQUIRE( std::string( demangleType() ) == "int" ); - REQUIRE( std::string( demangleType() ) == "float" ); - REQUIRE( std::string( demangleType() ) == "unsigned int" ); - REQUIRE( std::string( demangleType() ) == "unsigned long" ); + REQUIRE( demangleType() == "int" ); + REQUIRE( demangleType() == "float" ); + REQUIRE( demangleType() == "unsigned int" ); + REQUIRE( demangleType() == "unsigned long" ); - auto demangledName = std::string( demangleType>() ); + auto demangledName = demangleType>(); REQUIRE( demangledName == "std::vector>" ); - demangledName = std::string( demangleType() ); + demangledName = demangleType(); REQUIRE( demangledName == "TypeTests::TypeName_struct" ); demangledName = demangleType( std::type_index( typeid( std::vector ) ) ); @@ -39,19 +39,19 @@ TEST_CASE( "Core/Utils/TypesUtils", "[Core][Utils][TypesUtils]" ) { unsigned int u { 3 }; size_t s { 4 }; - REQUIRE( std::string( demangleType( i ) ) == "int" ); - REQUIRE( std::string( demangleType( f ) ) == "float" ); - REQUIRE( std::string( demangleType( u ) ) == "unsigned int" ); - REQUIRE( std::string( demangleType( s ) ) == "unsigned long" ); + REQUIRE( demangleType( i ) == "int" ); + REQUIRE( demangleType( f ) == "float" ); + REQUIRE( demangleType( u ) == "unsigned int" ); + REQUIRE( demangleType( s ) == "unsigned long" ); #ifndef _WIN32 // this segfault on windows due to out_of_bound exception. why ??? std::vector v; - auto demangledName = std::string( demangleType( v ) ); + auto demangledName = demangleType( v ); REQUIRE( demangledName == "std::vector>" ); #endif TypeTests::TypeName_struct tns; - auto demangledNameFromStruct = std::string( demangleType( tns ) ); + auto demangledNameFromStruct = demangleType( tns ); REQUIRE( demangledNameFromStruct == "TypeTests::TypeName_struct" ); } From 6ae69dc897e606628df2bad224deb6f03c65d791 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 8 Feb 2023 11:21:35 +0100 Subject: [PATCH 130/239] [dataflow-core] Simplify type demangler --- src/Dataflow/Core/TypeDemangler.cpp | 7 ++++--- src/Dataflow/Core/TypeDemangler.hpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Dataflow/Core/TypeDemangler.cpp b/src/Dataflow/Core/TypeDemangler.cpp index b3f439f16b6..96ba64a3fac 100644 --- a/src/Dataflow/Core/TypeDemangler.cpp +++ b/src/Dataflow/Core/TypeDemangler.cpp @@ -13,7 +13,7 @@ namespace TypeInternal { /** \todo verify windows specific type demangling needs. * */ -RA_DATAFLOW_API std::string makeTypeReadable( std::string fullType ) { +RA_DATAFLOW_API std::string makeTypeReadable( const std::string& fullType ) { static std::map knownTypes { { "std::", "" }, { ", std::allocator", "" }, @@ -38,10 +38,11 @@ RA_DATAFLOW_API std::string makeTypeReadable( std::string fullType ) { // Windows (visual studio 2022) specific name fix { " __ptr64", "" } }; + auto processedType = fullType; for ( const auto& [key, value] : knownTypes ) { - Ra::Core::Utils::replaceAllInString( fullType, key, value ); + Ra::Core::Utils::replaceAllInString( processedType, key, value ); } - return fullType; + return processedType; } } // namespace TypeInternal diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index 0a685ab6d0c..84999e397ce 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -21,7 +21,7 @@ RA_DATAFLOW_API std::string simplifiedDemangledType( const std::type_index& type // ---------------------- inline methods --------------------------- namespace TypeInternal { -RA_DATAFLOW_API std::string makeTypeReadable( std::string ); +RA_DATAFLOW_API std::string makeTypeReadable( const std::string& ); } template From 72520782cd12da39700d90c11aa7d14bf0f06031 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 8 Feb 2023 23:29:41 +0100 Subject: [PATCH 131/239] [dataflow-core] fix missing methods --- src/Dataflow/Core/Node.hpp | 42 +++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 674059c70a6..78d5179af9e 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -79,11 +79,11 @@ class RA_DATAFLOW_API Node /// @{ /// \brief Gets the in ports of the node. /// Input ports are own to the node. - const std::vector>& getInputs(); + const std::vector>& getInputs() const; /// \brief Gets the out ports of the node. /// Output ports are own to the node. - const std::vector>& getOutputs(); + const std::vector>& getOutputs() const; /// \brief Build the interface ports of the node /// Derived node can override the default implementation that build an interface port for each @@ -138,7 +138,7 @@ class RA_DATAFLOW_API Node /// \brief Flag that checks if the node is already initialized bool m_initialized { false }; - /// \brief Sets the filesystem (real or virtual) location for the pass resources + /// \brief Sets the filesystem (real or virtual) location for the node resources inline void setResourcesDir( std::string resourcesRootDir ); protected: @@ -165,6 +165,11 @@ class RA_DATAFLOW_API Node /// \param in The in port to add. bool addInput( PortBase* in ); + /// \brief remove the given input port from the managed input ports + /// \param in the port to remove + /// \return true if the port was removed (the in pointer is the set to nullptr), false else + bool removeInput( PortBase*& in ); + /// Adds an out port to the node and the data associated with it. /// This function checks if there is no out port with the same name already associated with this /// node. @@ -173,6 +178,11 @@ class RA_DATAFLOW_API Node template void addOutput( PortOut* out, T* data ); + /// \brief remove the given output port from the managed input ports + /// \param out the port to remove + /// \return true if the port was removed (the out pointer is the set to nullptr), false else + bool removeOutput( PortBase*& out ); + /// \brief Adds an editable parameter to the node if it does not already exist. /// \note the node will take ownership of the editable object. /// \param editableParameter The editable parameter to add. @@ -258,11 +268,11 @@ inline void Node::setInstanceName( const std::string& newName ) { m_instanceName = newName; } -inline const std::vector>& Node::getInputs() { +inline const std::vector>& Node::getInputs() const { return m_inputs; } -inline const std::vector>& Node::getOutputs() { +inline const std::vector>& Node::getOutputs() const { return m_outputs; } @@ -304,6 +314,17 @@ inline bool Node::addInput( PortBase* in ) { return !found; } +inline bool Node::removeInput( PortBase*& in ) { + auto itP = std::find_if( + m_inputs.begin(), m_inputs.end(), [in]( const auto& p ) { return p.get() == in; } ); + if ( itP != m_inputs.end() ) { + m_inputs.erase( itP ); + in = nullptr; + return true; + } + return false; +} + template void Node::addOutput( PortOut* out, T* data ) { bool found = false; @@ -316,6 +337,17 @@ void Node::addOutput( PortOut* out, T* data ) { } } +inline bool Node::removeOutput( PortBase*& out ) { + auto outP = std::find_if( + m_outputs.begin(), m_outputs.end(), [out]( const auto& p ) { return p.get() == out; } ); + if ( outP != m_outputs.end() ) { + m_outputs.erase( outP ); + out = nullptr; + return true; + } + return false; +} + inline bool Node::addEditableParameter( EditableParameterBase* editableParameter ) { bool found = false; for ( auto& edit : m_editableParameters ) { From 46a7acab9cf1d3b2ecb7dace0c2a88d165ede9fb Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 8 Feb 2023 23:30:16 +0100 Subject: [PATCH 132/239] [dataflow-core] fix missing methods on graph --- src/Dataflow/Core/DataflowGraph.cpp | 71 +++++++++++++++++------------ src/Dataflow/Core/DataflowGraph.hpp | 32 +++++++++++-- 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index ed8370482fa..e250b9b24e8 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -52,7 +52,7 @@ void DataflowGraph::destroy() { m_nodesByLevel.clear(); m_nodes.clear(); m_factories.reset(); - m_dataSetters.clear(); + m_dataSetters.erase( m_dataSetters.begin(), m_dataSetters.end() ); Node::destroy(); m_ready = false; m_shouldBeSaved = true; @@ -244,22 +244,22 @@ std::pair DataflowGraph::addNode( std::unique_ptr newNode ) { std::map m_mapInputs; // Check if the new node already exists (= same name and type) if ( canAdd( newNode.get() ) ) { - auto addedNode = newNode.get(); - m_nodes.emplace_back( std::move( newNode ) ); - if ( addedNode->getInputs().empty() ) { + if ( newNode->getInputs().empty() ) { // it is a source node, add its interface port as input and data setter to the graph - auto& interfaces = addedNode->buildInterfaces( this ); + auto& interfaces = newNode->buildInterfaces( this ); for ( auto p : interfaces ) { addSetter( p ); } } - if ( addedNode->getOutputs().empty() ) { + if ( newNode->getOutputs().empty() ) { // it is a sink node, add its interface port as output to the graph - auto& interfaces = addedNode->buildInterfaces( this ); + auto& interfaces = newNode->buildInterfaces( this ); for ( auto p : interfaces ) { addGetter( p ); } } + auto addedNode = newNode.get(); + m_nodes.emplace_back( std::move( newNode ) ); m_ready = false; m_shouldBeSaved = true; return { true, addedNode }; @@ -278,29 +278,13 @@ bool DataflowGraph::removeNode( Node*& node ) { if ( ( index = findNode( node ) ) == -1 ) { return false; } else { if ( node->getInputs().empty() ) { // Check if it is a source node - for ( auto& port : - node->getInterfaces() ) { // Erase input ports of the graph associated - // to the interface ports of the node - for ( auto itG = m_inputs.begin(); itG != m_inputs.end(); ++itG ) { - if ( port->getName() == - ( *itG )->getName() ) { // Check if these ports are the same - m_inputs.erase( itG ); - } - break; - } + for ( auto& port : node->getInterfaces() ) { + removeSetter( port->getName() ); } } if ( node->getOutputs().empty() ) { // Check if it is a sink node - for ( auto& port : - node->getInterfaces() ) { // Erase input ports of the graph associated - // to the interface ports of the node - for ( auto itG = m_outputs.begin(); itG != m_outputs.end(); ++itG ) { - if ( port->getName() == - ( *itG )->getName() ) { // Check if these ports are the same - m_outputs.erase( itG ); - break; - } - } + for ( auto& port : node->getInterfaces() ) { + removeGetter( port->getName() ); } } m_nodes.erase( m_nodes.begin() + index ); @@ -495,6 +479,11 @@ void DataflowGraph::clearNodes() { m_nodesByLevel.shrink_to_fit(); m_nodes.erase( m_nodes.begin(), m_nodes.end() ); m_nodes.shrink_to_fit(); + m_inputs.erase( m_inputs.begin(), m_inputs.end() ); + m_inputs.shrink_to_fit(); + m_outputs.erase( m_outputs.begin(), m_outputs.end() ); + m_outputs.shrink_to_fit(); + m_dataSetters.erase( m_dataSetters.begin(), m_dataSetters.end() ); m_shouldBeSaved = true; } @@ -560,7 +549,18 @@ bool DataflowGraph::addSetter( PortBase* in ) { return false; } -inline bool DataflowGraph::addGetter( PortBase* out ) { +bool DataflowGraph::removeSetter( const std::string& setterName ) { + auto itS = m_dataSetters.find( setterName ); + if ( itS != m_dataSetters.end() ) { + auto& [desPort, in] = itS->second; + removeInput( in ); + m_dataSetters.erase( itS ); + return true; + } + return false; +} + +bool DataflowGraph::addGetter( PortBase* out ) { if ( out->is_input() ) { return false; } // This is very similar to addOutput, except the data can't be set, they will be in the init // of any Sink @@ -573,6 +573,17 @@ inline bool DataflowGraph::addGetter( PortBase* out ) { return !found; } +bool DataflowGraph::removeGetter( const std::string& getterName ) { + auto getterP = std::find_if( m_outputs.begin(), m_outputs.end(), [getterName]( const auto& p ) { + return p->getName() == getterName; + } ); + if ( getterP != m_outputs.end() ) { + m_outputs.erase( getterP ); + return true; + } + return false; +} + bool DataflowGraph::releaseDataSetter( const std::string& portName ) { auto setter = m_dataSetters.find( portName ); if ( setter != m_dataSetters.end() ) { @@ -600,7 +611,7 @@ std::shared_ptr DataflowGraph::getDataSetter( const std::string& portN return nullptr; } -std::vector DataflowGraph::getAllDataSetters() { +std::vector DataflowGraph::getAllDataSetters() const { std::vector r; r.reserve( m_dataSetters.size() ); for ( auto& s : m_dataSetters ) { @@ -617,7 +628,7 @@ PortBase* DataflowGraph::getDataGetter( const std::string& portName ) { return nullptr; } -std::vector DataflowGraph::getAllDataGetters() { +std::vector DataflowGraph::getAllDataGetters() const { std::vector r; r.reserve( m_outputs.size() ); for ( auto& portOut : m_outputs ) { diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index d8d25fd3ec8..9119ffbaf44 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -66,16 +66,19 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \param newNode const naked pointer to the candidate node /// \return true if ownership could be transferred to the graph. virtual bool canAdd( const Node* newNode ) const; + /// Adds a node to the render graph. Adds interface ports to the node newNode and the /// corresponding input and output ports to the graph. /// \param newNode The node to add to the render graph. /// ownership of the node is transferred to the graph virtual std::pair addNode( std::unique_ptr newNode ); + /// Removes a node from the render graph. Removes input and output ports of the graph /// corresponding to interface ports of the node. /// \param node The node to remove from the render graph. /// \return true if the node was removed and the given pointer is set to nullptr, false else virtual bool removeNode( Node*& node ); + /// Connects two nodes of the render graph. /// The two nodes must already be in the render graph (with the addNode(Node* newNode) /// function), the first node's in port must be free and the connected in port and out port must @@ -88,6 +91,7 @@ class RA_DATAFLOW_API DataflowGraph : public Node const std::string& nodeFromOutputName, Node* nodeTo, const std::string& nodeToInputName ); + /// Removes the link connected to this node's input port bool removeLink( Node* node, const std::string& nodeInputName ); @@ -114,9 +118,8 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Deletes all nodes from the render graph. virtual void clearNodes(); - /// Flag set after successful compilation indicating graph is ready to be executed - /// This flag is reset as soon as the graph is modified. - bool m_ready { false }; + /// Test if the graph is compiled + bool isCompiled() const; /// Flag that indicates if the graph should be saved to a file /// This flag is useless outside an load/edit/save scenario @@ -159,11 +162,11 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \note If called multiple times for the same port, only the last returned result is /// usable. /// TODO : Verify why, when listing the data setters, they are connected ... - std::vector getAllDataSetters(); + std::vector getAllDataSetters() const; /// Creates a vector that stores all the DataGetters (\see getDataGetter) of the graph. /// A tuple is composed of an output port belonging to the graph, its name its type. - std::vector getAllDataGetters(); + std::vector getAllDataGetters() const; protected: /** Allow derived class to construct the graph with their own static type @@ -174,6 +177,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node void toJsonInternal( nlohmann::json& ) const override; private: + /// Flag set after successful compilation indicating graph is ready to be executed + /// This flag is reset as soon as the graph is modified. + bool m_ready { false }; + /// The node factory to use for loading std::shared_ptr m_factories; /// The unordered list of nodes. @@ -208,12 +215,18 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// activateDataSetter/releaseDataSetter using DataSetter = std::pair; std::map m_dataSetters; + /// \brief Adds an input port to the graph and associate it with a dataSetter. /// This port is aliased as an interface port in a source node of the graph. /// This function checks if there is no input port with the same name already /// associated with the graph. bool addSetter( PortBase* in ); + /// \brief Remove the given setter from the graph + /// \param setterName + /// \return true if the setter was removed, false else. + bool removeSetter( const std::string& setterName ); + /// \brief Adds an out port for a Graph and register it as a dataGetter. /// This port is aliased as an interface port in a sink node of the graph. /// This function checks if there is no out port with the same name already @@ -221,6 +234,11 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \param out The port to add. bool addGetter( PortBase* out ); + /// \brief Remove the given getter from the graph + /// \param getterName + /// \return true if the getter was removed, false else. + bool removeGetter( const std::string& getterName ); + public: static const std::string& getTypename(); @@ -251,10 +269,14 @@ class RA_DATAFLOW_API DataflowGraph : public Node // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- +inline bool DataflowGraph::isCompiled() const { + return m_ready; +} inline void DataflowGraph::setNodeFactories( std::shared_ptr factories ) { m_factories = factories; } + inline std::shared_ptr DataflowGraph::getNodeFactories() const { return m_factories; } From 920991da46bdf90e1ba02ebf635a6f1cfeb9fbd0 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 8 Feb 2023 23:30:34 +0100 Subject: [PATCH 133/239] [dataflow-core] specialize some sources --- .../Core/Nodes/Sources/CoreDataSources.hpp | 73 ++++++++++++++++++- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp index 361d4e2f52d..34593706eba 100644 --- a/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp +++ b/src/Dataflow/Core/Nodes/Sources/CoreDataSources.hpp @@ -81,6 +81,8 @@ SPECIALIZE_EDITABLE_SOURCE( double, number ) SPECIALIZE_EDITABLE_SOURCE( int, value ) SPECIALIZE_EDITABLE_SOURCE( unsigned int, value ) +#undef SPECIALIZE_EDITABLE_SOURCE + // Color specialization need different implementation (as well as any Ra::Vectorxx) template <> inline SingleDataSourceNode::SingleDataSourceNode( @@ -92,7 +94,7 @@ inline SingleDataSourceNode::SingleDataSourceNode( template <> inline void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { - data["color"] = *getData(); + data["color"] = Ra::Core::Utils::Color::linearRGBTosRGB( *getData() ); } template <> @@ -107,7 +109,74 @@ SingleDataSourceNode::fromJsonInternal( const nlohmann:: return true; } -#undef SPECIALIZE_EDITABLE_SOURCE +// Ra::Core::Vector4 specialization +template <> +inline SingleDataSourceNode::SingleDataSourceNode( const std::string& name ) : + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { + setEditable( "vector" ); +} + +template <> +inline void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["vector"] = *getData(); +} + +template <> +inline bool +SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "vector" ) ) { + std::array v = data["vector"]; + auto vec = Ra::Core::Vector4( v[0], v[1], v[2], v[3] ); + setData( vec ); + } + return true; +} + +// Ra::Core::Vector3 specialization +template <> +inline SingleDataSourceNode::SingleDataSourceNode( const std::string& name ) : + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { + setEditable( "vector" ); +} + +template <> +inline void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["vector"] = *getData(); +} + +template <> +inline bool +SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "vector" ) ) { + std::array v = data["vector"]; + auto vec = Ra::Core::Vector3( v[0], v[1], v[2] ); + setData( vec ); + } + return true; +} + +// Ra::Core::Vector2 specialization +template <> +inline SingleDataSourceNode::SingleDataSourceNode( const std::string& name ) : + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { + setEditable( "vector" ); +} + +template <> +inline void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + data["vector"] = *getData(); +} + +template <> +inline bool +SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "vector" ) ) { + std::array v = data["vector"]; + auto vec = Ra::Core::Vector2( v[0], v[1] ); + setData( vec ); + } + return true; +} } // namespace Sources } // namespace Core From 14e41dcb56e4da1902ce07d641df6d689222b628 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 8 Feb 2023 23:31:00 +0100 Subject: [PATCH 134/239] [unittests] improve coverage --- tests/unittest/Dataflow/graph.cpp | 190 ++++++++++++-------- tests/unittest/Dataflow/nodes.cpp | 4 +- tests/unittest/Dataflow/sourcesandsinks.cpp | 29 +++ 3 files changed, 144 insertions(+), 79 deletions(-) diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp index b38a54d1c15..a5442e44fd4 100644 --- a/tests/unittest/Dataflow/graph.cpp +++ b/tests/unittest/Dataflow/graph.cpp @@ -8,6 +8,77 @@ using namespace Ra::Dataflow::Core; +void inspectGraph( const DataflowGraph& g ) { + // Factories used by the graph + auto factories = g.getNodeFactories(); + std::cout << "Used factories by the graph \"" << g.getInstanceName() << "\" with type \"" + << g.getTypeName() << "\" :\n"; + for ( const auto& f : *( factories.get() ) ) { + std::cout << "\t" << f.first << "\n"; + } + + // Nodes of the graph + auto nodes = g.getNodes(); + std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() << ") :\n"; + for ( const auto& n : *( nodes ) ) { + std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() + << "\"\n"; + // Inspect input, output and interfaces of the node + std::cout << "\t\tInput ports :\n"; + for ( const auto& p : n->getInputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() << "\n"; + } + std::cout << "\t\tOutput ports :\n"; + for ( const auto& p : n->getOutputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() << "\n"; + } + std::cout << "\t\tInterface ports :\n"; + for ( const auto& p : n->getInterfaces() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() << "\n"; + } + } + + // Nodes by level after the compilation + if ( g.isCompiled() ) { + auto cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level after compiling the graph :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto n : ( *cn )[i] ) { + std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + } + } + } + + // describe the graph interface : inputs and outputs port of the whole graph (not of the + // nodes) + std::cout << "Inputs and output ports of the graph " << g.getInstanceName() << " :\n"; + const auto& inputs = g.getInputs(); + std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; + for ( const auto& inp : inputs ) { + std::cout << "\t\t\"" << inp->getName() << "\" accepting type \"" << inp->getTypeName() + << "\"\n"; + } + const auto& outputs = g.getOutputs(); + std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; + for ( const auto& outp : outputs ) { + std::cout << "\t\t\"" << outp->getName() << "\" accepting type \"" << outp->getTypeName() + << "\"\n"; + } + + std::cout << "DataSetters and DataGetters port of the graph " << g.getInstanceName() << " :\n"; + auto setters = g.getAllDataSetters(); + std::cout << "\tSetters ports (" << setters.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : setters ) { + std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + } + auto getters = g.getAllDataGetters(); + std::cout << "\tGetters ports (" << getters.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : getters ) { + std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + } +} + TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { SECTION( "Creation of a graph" ) { DataflowGraph g( "Test Graph" ); @@ -235,6 +306,14 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { result = g.addLink( sourceIntNode, "to", sinkFloatNode, "from" ); REQUIRE( !result ); + // protect the graph to prevent link removal + g.setNodesAndLinksProtection( true ); + REQUIRE( g.getNodesAndLinksProtection() ); + // unable to remove links from protected graph ... + result = g.removeLink( sinkIntNode, "from" ); + REQUIRE( !result ); + g.setNodesAndLinksProtection( false ); + REQUIRE( !g.getNodesAndLinksProtection() ); // remove link OK result = g.removeLink( sinkIntNode, "from" ); REQUIRE( result ); @@ -246,6 +325,7 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { // compile the graph result = g.compile(); REQUIRE( result ); + REQUIRE( g.isCompiled() ); // clear the graph g.clearNodes(); @@ -266,95 +346,51 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { } SECTION( "Inspection of a graph" ) { - DataflowGraph g( "" ); - g.loadFromJson( "data/Dataflow/ExampleGraph.json" ); + auto g = DataflowGraph::loadGraphFromJsonFile( "data/Dataflow/ExampleGraph.json" ); // Factories used by the graph - auto factories = g.getNodeFactories(); + auto factories = g->getNodeFactories(); REQUIRE( factories != nullptr ); - std::cout << "Used factories by the graph \"" << g.getInstanceName() << "\" with type \"" - << g.getTypeName() << "\" :\n"; - for ( const auto& f : *( factories.get() ) ) { - std::cout << "\t" << f.first << "\n"; - } - - // Nodes of the graph - auto nodes = g.getNodes(); + auto nodes = g->getNodes(); REQUIRE( nodes != nullptr ); - std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() - << ") :\n"; - REQUIRE( nodes->size() == g.getNodesCount() ); - for ( const auto& n : *( nodes ) ) { - std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() - << "\"\n"; - // Inspect input, output and interfaces of the node - std::cout << "\t\tInput ports :\n"; - for ( const auto& p : n->getInputs() ) { - std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() - << "\n"; - } - std::cout << "\t\tOutput ports :\n"; - for ( const auto& p : n->getOutputs() ) { - std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() - << "\n"; - } - std::cout << "\t\tInterface ports :\n"; - for ( const auto& p : n->getInterfaces() ) { - std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() - << "\n"; - } - } - - // Nodes by level after the compilation - auto c = g.compile(); + REQUIRE( nodes->size() == g->getNodesCount() ); + auto c = g->compile(); REQUIRE( c == true ); - auto cn = g.getNodesByLevel(); - std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { - std::cout << "\tLevel " << i << " :\n"; - for ( const auto n : ( *cn )[i] ) { - std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; - } - } - - // describe the graph interface : inputs and outputs port of the whole graph (not of the - // nodes) - std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; - auto inputs = g.getAllDataSetters(); - std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : inputs ) { - std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; - } - auto outputs = g.getAllDataGetters(); - std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : outputs ) { - std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; - } + // Prints the graph content + inspectGraph( *g ); // removing the boolean sink from the graph - auto n = g.getNode( "validation value" ); + auto n = g->getNode( "validation value" ); REQUIRE( n->getInstanceName() == "validation value" ); - c = g.removeNode( n ); + c = g->removeNode( n ); REQUIRE( c == true ); REQUIRE( n == nullptr ); - c = g.compile(); + c = g->compile(); REQUIRE( c == true ); // Simplified graph after compilation - cn = g.getNodesByLevel(); - std::cout << "Nodes of the graph, sorted by level, after sink deletion :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { - std::cout << "\tLevel " << i << " :\n"; - for ( const auto nn : ( *cn )[i] ) { - std::cout << "\t\t\"" << nn->getInstanceName() << "\"\n"; - } - } - - // the source "Validator" is no more in level 0 as it is non reachable from a sink in the + auto cn = g->getNodesByLevel(); + // the source "Validator" is no more in level 0 as it is not reachable from a sink in the // graph. - bool found = false; - for ( const auto nn : ( *cn )[0] ) { - if ( nn->getInstanceName() == "Validator" ) { found = true; } - } - REQUIRE( found == false ); + auto found = std::find_if( ( *cn )[0].begin(), ( *cn )[0].end(), []( const auto& nn ) { + return nn->getInstanceName() == "Validator"; + } ); + REQUIRE( found == ( *cn )[0].end() ); + + // removing the source "Validator" + n = g->getNode( "Validator" ); + REQUIRE( n->getInstanceName() == "Validator" ); + // protect the graph to prevent node removal + g->setNodesAndLinksProtection( true ); + c = g->removeNode( n ); + REQUIRE( !c ); + g->setNodesAndLinksProtection( false ); + c = g->removeNode( n ); + REQUIRE( c ); + REQUIRE( n == nullptr ); + + std::cout << "####### Graph after sink and source removal\n"; + inspectGraph( *g ); + + delete g; } } diff --git a/tests/unittest/Dataflow/nodes.cpp b/tests/unittest/Dataflow/nodes.cpp index 25940aabc0f..38aa33c694e 100644 --- a/tests/unittest/Dataflow/nodes.cpp +++ b/tests/unittest/Dataflow/nodes.cpp @@ -43,9 +43,9 @@ createGraph( REQUIRE( g->addLink( source_a, "to", op, "a" ) ); REQUIRE( g->addLink( op, "r", sink, "from" ) ); REQUIRE( !g->compile() ); - // this will not execute the graph as it do not compiles + // this will not execute the graph as it does not compile g->execute(); - REQUIRE( !g->m_ready ); + REQUIRE( !g->isCompiled() ); // add missing link REQUIRE( g->addLink( source_b, "to", op, "b" ) ); diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp index b71bc6412b7..24e1c50ecb5 100644 --- a/tests/unittest/Dataflow/sourcesandsinks.cpp +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -40,10 +40,19 @@ void testGraph( const std::string& name, T in, T& out ) { T r = output->getData(); out = r; + g->releaseDataSetter( "in_to" ); + source->setData( &in ); + nlohmann::json graphData; g->toJson( graphData ); g->destroy(); delete g; + + g = new DataflowGraph { name }; + g->fromJson( graphData ); + auto ok = g->execute(); + REQUIRE( ok ); + delete g; } //! [Create a source to sink graph for type T] TEST_CASE( "Dataflow/Core/Sources and Sinks", "[Dataflow][Core][Sources and Sinks]" ) { @@ -108,6 +117,16 @@ TEST_CASE( "Dataflow/Core/Sources and Sinks", "[Dataflow][Core][Sources and Sink REQUIRE( x == y ); std::cout << " ... DONE!\n"; } + SECTION( "Operations on base type : Vector2" ) { + using DataType = Ra::Core::Vector2; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { 1_ra, 2_ra }; + DataType y; + testGraph( "Test on Vector2", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } SECTION( "Operations on base type : Vector3" ) { using DataType = Ra::Core::Vector3; std::cout << "Test on " << simplifiedDemangledType() << " ... "; @@ -118,6 +137,16 @@ TEST_CASE( "Dataflow/Core/Sources and Sinks", "[Dataflow][Core][Sources and Sink REQUIRE( x == y ); std::cout << " ... DONE!\n"; } + SECTION( "Operations on base type : Vector4" ) { + using DataType = Ra::Core::Vector4; + std::cout << "Test on " << simplifiedDemangledType() << " ... "; + DataType x { 1_ra, 2_ra, 3_ra, 4_ra }; + DataType y; + testGraph( "Test on Vector4", x, y ); + + REQUIRE( x == y ); + std::cout << " ... DONE!\n"; + } SECTION( "Operations on base type : Color" ) { using DataType = Ra::Core::Utils::Color; std::cout << "Test on " << simplifiedDemangledType() << " ... "; From 606317f1e08f66f3c2c1c5b4deb90344d3dfeb01 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 17:29:55 +0100 Subject: [PATCH 135/239] [dataflow-core] allows to access to a port through its name or index --- src/Dataflow/Core/Node.cpp | 65 +++++++++++++++++++++++++++----------- src/Dataflow/Core/Node.hpp | 13 ++++++++ 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index d28d13c9ebe..a236276ed2b 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -44,50 +44,62 @@ bool Node::fromJson( const nlohmann::json& data ) { // This is to avoid wrong error message when creating node from the editor return true; } - if ( data.contains( "model" ) ) { - if ( data["model"].contains( "instance" ) ) { m_instanceName = data["model"]["instance"]; } - } - else { - LOG( logERROR ) << "Missing required model when loading a Dataflow::Node"; - return false; + bool hasIdOrName = false; + if ( data.contains( "instance" ) ) { + hasIdOrName = true; + m_instanceName = data["instance"]; } // get the common content of the Node from the json data if ( data.contains( "id" ) ) { + hasIdOrName = true; std::string struuid = data["id"]; m_uuid = uuids::uuid::from_string( struuid ).value(); } - else { - LOG( logERROR ) << "Missing required uuid when loading node " << m_instanceName; + if ( !hasIdOrName ) { + LOG( logERROR ) << "Missing required uuid or instance name when loading node " + << m_instanceName; return false; } - // get the specific concrete node information - const auto& datamodel = data["model"]; - auto loaded = fromJsonInternal( datamodel ); - - // get the supplemental informations related to application/gui/... + bool loaded = false; + if ( data.contains( "model" ) ) { + // get the specific concrete node information + const auto& datamodel = data["model"]; + loaded = fromJsonInternal( datamodel ); + } + else { + LOG( logERROR ) << "Missing required model when loading a Dataflow::Node"; + loaded = false; + } + // get the supplemental information related to application/gui/... for ( auto& [key, value] : data.items() ) { - if ( key != "id" && key != "model" ) { m_extraJsonData.emplace( key, value ); } + if ( key != "id" && key != "instance" && key != "model" ) { + m_extraJsonData.emplace( key, value ); + } } return loaded; } void Node::toJson( nlohmann::json& data ) const { + // write the common content of the Node to the json data +#if 0 + // id is only needed for QtNodeEditor, do not save it, it will be regenerated when needed std::string struuid = "{" + uuids::to_string( m_uuid ) + "}"; data["id"] = struuid; +#endif + data["instance"] = m_instanceName; nlohmann::json model; - model["instance"] = m_instanceName; - model["name"] = m_typeName; + model["name"] = m_typeName; - // Fill the specific concrete node informations + // Fill the specific concrete node information (model instance) toJsonInternal( model ); data.emplace( "model", model ); - // store the supplemental informations related to application/gui/... + // store the supplemental information related to application/gui/... for ( auto& [key, value] : m_extraJsonData.items() ) { - if ( key != "id" && key != "model" ) { data.emplace( key, value ); } + if ( key != "id" && key != "instance" && key != "model" ) { data.emplace( key, value ); } } } @@ -97,6 +109,21 @@ void Node::addJsonMetaData( const nlohmann::json& data ) { } } +PortBase* Node::getPortByName( const std::string& type, const std::string& name ) const { + const auto& ports = ( type == "in" ) ? m_inputs : m_outputs; + auto itp = std::find_if( + ports.begin(), ports.end(), [n = name]( const auto& p ) { return p->getName() == n; } ); + PortBase* fprt { nullptr }; + if ( itp != ports.cend() ) { fprt = itp->get(); } + return fprt; +} + +PortBase* Node::getPortByIndex( const std::string& type, int idx ) const { + const auto& ports = ( type == "in" ) ? m_inputs : m_outputs; + if ( 0 <= idx && size_t( idx ) < ports.size() ) { return ports[idx].get(); } + return nullptr; +} + } // namespace Core } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 78d5179af9e..57318d6195e 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -77,6 +77,19 @@ class RA_DATAFLOW_API Node /// \name Control the interfaces of the nodes (inputs, outputs, internal data, ...) /// @{ + + /// \brief Get an input port by its name + /// \param type either "in" or "out", the directional type of the port + /// \param name + /// \return an alias pointer on the requested port if it exists, nullptr else + PortBase* getPortByName( const std::string& type, const std::string& name ) const; + + /// \brief Get an input port by its index + /// \param type either "in" or "out", the directional type of the port + /// \param idx + /// \return an alias pointer on the requested port if it exists, nullptr else + PortBase* getPortByIndex( const std::string& type, int idx ) const; + /// \brief Gets the in ports of the node. /// Input ports are own to the node. const std::vector>& getInputs() const; From b9a6b7a6e04ed0d4e3cbfe71072abddc5f638fb0 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 17:31:32 +0100 Subject: [PATCH 136/239] [dataflow-core] simplify loading and json representation --- src/Dataflow/Core/DataflowGraph.cpp | 216 ++++++++++++++++------------ 1 file changed, 127 insertions(+), 89 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index e250b9b24e8..d29cdcae13a 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -89,31 +89,24 @@ void DataflowGraph::toJsonInternal( nlohmann::json& data ) const { nlohmann::json nodeData; n->toJson( nodeData ); nodes.push_back( nodeData ); - int numPort = 0; for ( const auto& input : n->getInputs() ) { if ( input->isLinked() ) { nlohmann::json link = nlohmann::json::object(); - link["in_id"] = n->getUuid(); - link["in_index"] = numPort; auto portOut = input->getLink(); auto nodeOut = portOut->getNode(); - int outPortIndex = 0; - for ( const auto& p : nodeOut->getOutputs() ) { - if ( p.get() == portOut ) { break; } - outPortIndex++; - } - link["out_id"] = nodeOut->getUuid(); - link["out_index"] = outPortIndex; + link["out_node"] = nodeOut->getInstanceName(); + link["out_port"] = portOut->getName(); + link["in_node"] = n->getInstanceName(); + link["in_port"] = input->getName(); connections.push_back( link ); } - numPort++; } } // write the common content of the Node to the json data graph["nodes"] = nodes; graph["connections"] = connections; - // Fill the specific concrete node informations + // Fill the specific concrete node information data.emplace( "graph", graph ); } @@ -131,6 +124,52 @@ bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { return fromJson( j ); } +std::pair getLinkInfo( const std::string& which, + const nlohmann::json& linkData, + const std::unordered_map& nodeById, + const std::map& nodeByName ) { + std::string field = which + "_node"; + Node* node { nullptr }; + if ( linkData.contains( field ) ) { + auto itNode = nodeByName.find( linkData[field] ); + if ( itNode != nodeByName.end() ) { node = itNode->second; } + } + else { + // try to find the node by id + field = which + "_id"; + if ( linkData.contains( field ) ) { + auto itNode = nodeById.find( linkData[field] ); + if ( itNode != nodeById.end() ) { node = itNode->second; } + } + } + if ( node == nullptr ) { + // Error, could not find the node + std::string msg = + std::string { "Node " } + which + " not found in cache " + " : " + linkData.dump(); + return { nullptr, msg }; + } + + std::string port; + field = which + "_port"; + if ( linkData.contains( field ) ) { + auto p = node->getPortByName( which, linkData[field] ); + if ( p != nullptr ) { port = p->getName(); } + } + else { + field = which + "_index"; + if ( linkData.contains( field ) ) { + auto p = node->getPortByIndex( which, linkData[field] ); + if ( p != nullptr ) { port = p->getName(); } + } + } + if ( port.empty() ) { + std::string msg = std::string { "Port " } + which + " not found in node " + + node->getInstanceName() + " : " + linkData.dump(); + return { nullptr, msg }; + } + return { node, port }; +} + bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "graph" ) ) { // indicate that the graph must be recompiled after loading @@ -154,76 +193,70 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } } std::unordered_map nodeById; - + std::map nodeByName; auto nodes = data["graph"]["nodes"]; for ( auto& n : nodes ) { - std::string name = n["model"]["name"]; - std::string id = n["id"]; - - auto newNode = m_factories->createNode( name, n, this ); - if ( newNode ) { nodeById.emplace( id, newNode ); } - else { - LOG( logERROR ) << "Unable to create the node " << name; + if ( !n["model"].contains( "name" ) ) { + LOG( logERROR ) << "Found a node without model description." << n.dump() + << "Unable to build an instance."; return false; } - } - - auto links = data["graph"]["connections"]; - for ( auto& l : links ) { - Node* nodeFrom { nullptr }; - std::string fromOutput { "" }; - Node* nodeTo { nullptr }; - std::string toInput { "" }; - - auto nodeId = l["out_id"]; - auto itNode = nodeById.find( nodeId ); - if ( itNode != nodeById.end() ) { - nodeFrom = itNode->second; - int fromIndex = l["out_index"]; - if ( fromIndex >= 0 && fromIndex < int( nodeFrom->getOutputs().size() ) ) { - fromOutput = nodeFrom->getOutputs()[fromIndex]->getName(); - } - else { - LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" - << ": Output index " << fromIndex << " for node \"" - << nodeFrom->getInstanceName() << " (" - << nodeFrom->getTypeName() << ")\" must be between 0 and " - << nodeFrom->getOutputs().size() - 1 << ". Link not added."; - return false; - } + std::string nodeTypeName = n["model"]["name"]; + std::string instanceName, id; + bool nodeIsIdentified = false; + if ( n.contains( "instance" ) ) { + instanceName = n["instance"]; + nodeIsIdentified = true; } - else { - LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" - << ": Could not find a node associated with id " << nodeId - << ". Link not added."; + if ( n.contains( "id" ) ) { + id = n["id"]; + nodeIsIdentified = true; + } + if ( !nodeIsIdentified ) { + LOG( logERROR ) << "Found a node of type " << nodeTypeName + << " without identification "; return false; } - - nodeId = l["in_id"]; - itNode = nodeById.find( nodeId ); - if ( itNode != nodeById.end() ) { - nodeTo = itNode->second; - int toIndex = l["in_index"]; - - if ( toIndex >= 0 && toIndex < int( nodeTo->getInputs().size() ) ) { - toInput = nodeTo->getInputs()[toIndex]->getName(); + auto newNode = m_factories->createNode( nodeTypeName, n, this ); + if ( newNode ) { + if ( !instanceName.empty() ) { + auto [it, inserted] = nodeByName.insert( { instanceName, newNode } ); + if ( !inserted ) { + LOG( logERROR ) << "DataflowGraph::loadFromJson : duplicated node name " + << nodeTypeName; + return false; + } } - else { - LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" - << ": Input index " << toIndex << " for node \"" - << nodeTo->getInstanceName() << " (" << nodeTo->getTypeName() - << ")\" must be between 0 and " - << nodeTo->getInputs().size() - 1 << ". Link not added."; - return false; + if ( !id.empty() ) { + auto [it, inserted] = nodeById.insert( { id, newNode } ); + if ( !inserted ) { + LOG( logERROR ) + << "DataflowGraph::loadFromJson : duplicated node uuid " << id; + return false; + } } } else { - LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" - << ": Could not find a node associated with id " << nodeId - << ". Link not added."; + LOG( logERROR ) << "Unable to create the node " << nodeTypeName; + return false; + } + } + auto links = data["graph"]["connections"]; + for ( auto& l : links ) { + auto [nodeFrom, fromOutput] = getLinkInfo( "out", l, nodeById, nodeByName ); + if ( nodeFrom == nullptr ) { + LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON." + << " Could not find the link source (" << fromOutput + << "). Link not added."; + return false; + } + auto [nodeTo, toInput] = getLinkInfo( "in", l, nodeById, nodeByName ); + if ( nodeTo == nullptr ) { + LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON." + << " Could not find the link source (" << toInput + << "). Link not added."; return false; } - if ( !addLink( nodeFrom, fromOutput, nodeTo, toInput ) ) { LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON" @@ -537,27 +570,29 @@ int DataflowGraph::goThroughGraph( } bool DataflowGraph::addSetter( PortBase* in ) { - addInput( in ); + bool found = false; if ( m_dataSetters.find( in->getName() ) == m_dataSetters.end() ) { + addInput( in ); auto portOut = std::shared_ptr( in->reflect( this, in->getName() ) ); m_dataSetters.emplace( std::make_pair( in->getName(), DataSetter { DataSetterDesc { portOut, portOut->getName(), portOut->getTypeName() }, in } ) ); - return true; + found = true; } - return false; + return found; } bool DataflowGraph::removeSetter( const std::string& setterName ) { - auto itS = m_dataSetters.find( setterName ); + bool removed = false; + auto itS = m_dataSetters.find( setterName ); if ( itS != m_dataSetters.end() ) { auto& [desPort, in] = itS->second; removeInput( in ); m_dataSetters.erase( itS ); - return true; + removed = true; } - return false; + return removed; } bool DataflowGraph::addGetter( PortBase* out ) { @@ -577,21 +612,23 @@ bool DataflowGraph::removeGetter( const std::string& getterName ) { auto getterP = std::find_if( m_outputs.begin(), m_outputs.end(), [getterName]( const auto& p ) { return p->getName() == getterName; } ); + bool found = false; if ( getterP != m_outputs.end() ) { m_outputs.erase( getterP ); - return true; + found = true; } - return false; + return found; } bool DataflowGraph::releaseDataSetter( const std::string& portName ) { auto setter = m_dataSetters.find( portName ); + bool found = false; if ( setter != m_dataSetters.end() ) { auto [desc, in] = setter->second; in->disconnect(); - return true; + found = true; } - return false; + return found; } // Why is this method useful if it is the same than getDataSetter ? @@ -601,14 +638,14 @@ bool DataflowGraph::activateDataSetter( const std::string& portName ) { std::shared_ptr DataflowGraph::getDataSetter( const std::string& portName ) { auto setter = m_dataSetters.find( portName ); + std::shared_ptr p { nullptr }; if ( setter != m_dataSetters.end() ) { auto [desc, in] = setter->second; - auto p = std::get<0>( desc ); + p = std::get<0>( desc ); in->disconnect(); p->connect( in ); - return p; } - return nullptr; + return p; } std::vector DataflowGraph::getAllDataSetters() const { @@ -624,8 +661,9 @@ PortBase* DataflowGraph::getDataGetter( const std::string& portName ) { auto portIt = std::find_if( m_outputs.begin(), m_outputs.end(), [portName]( const auto& p ) { return p->getName() == portName; } ); - if ( portIt != m_outputs.end() ) { return portIt->get(); } - return nullptr; + PortBase* g { nullptr }; + if ( portIt != m_outputs.end() ) { g = portIt->get(); } + return g; } std::vector DataflowGraph::getAllDataGetters() const { @@ -643,8 +681,8 @@ Node* DataflowGraph::getNode( const std::string& instanceNameNode ) const { return n->getInstanceName() == instanceNameNode; } ); if ( nodeIt != m_nodes.end() ) { return nodeIt->get(); } - LOG( logERROR ) << "DataflowGraph::getNode : The node with the instance name " - << instanceNameNode << " has not been found"; + LOG( logERROR ) << "DataflowGraph::getNode : The node with the instance name \"" + << instanceNameNode << "\" has not been found"; return nullptr; } @@ -659,15 +697,15 @@ DataflowGraph* DataflowGraph::loadGraphFromJsonFile( const std::string& filename jsonFile >> j; bool valid = false; - if ( j.contains( "model" ) ) { - if ( j["model"].contains( "name" ) && j["model"].contains( "instance" ) ) { valid = true; } + if ( j.contains( "instance" ) && j.contains( "model" ) ) { + valid = j["model"].contains( "name" ); } if ( !valid ) { LOG( logERROR ) << "loadGraphFromJsonFile :" << filename << " does not contain a valid json NodeGraph\n"; return nullptr; } - std::string instanceName = j["model"]["instance"]; + std::string instanceName = j["instance"]; std::string graphType = j["model"]["name"]; LOG( logINFO ) << "Loading the graph " << instanceName << ", with type " << graphType << "\n"; From 45b39cdde4c6a1e46d5b235eac39be76fb3a18ef Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 17:32:40 +0100 Subject: [PATCH 137/239] [dataflow-core] modify default generated node creator functor --- src/Dataflow/Core/NodeFactory.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index 64d244ca8aa..6d0c18ec4e8 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -259,7 +259,12 @@ bool NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, return registerNodeCreator( T::getTypename(), [this, instanceNamePrefix]( const nlohmann::json& data ) { - auto node = new T( instanceNamePrefix + std::to_string( this->nextNodeId() ) ); + std::string instanceName; + if ( data.contains( "instance" ) ) { instanceName = data["instance"]; } + else { + instanceName = instanceNamePrefix + std::to_string( this->nextNodeId() ); + } + auto node = new T( instanceName ); node->fromJson( data ); return node; }, From 2c76058bd3fa4cdebc45b59b0e9549cb6e6ded29 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 17:33:23 +0100 Subject: [PATCH 138/239] [dataflow-qtgui] fix crash when ending graph edition session --- .../QtGui/GraphEditor/GraphEditorWindow.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index dc66418c81d..9d2d4031edf 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -8,13 +8,18 @@ namespace GraphEditor { using namespace Ra::Dataflow::Core; GraphEditorWindow::~GraphEditorWindow() { - // Prevent graph destruction if the editor is used to work with active graphs. - auto graphProtection = m_graph->getNodesAndLinksProtection(); - if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( true ); } - delete m_graphEdit; - if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( graphProtection ); } + if ( m_graph ) { + // Prevent graph destruction if the editor is used to work with active graphs. + auto graphProtection = m_graph->getNodesAndLinksProtection(); + if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( true ); } + delete m_graphEdit; + if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( graphProtection ); } + else { + delete m_graph; + } + } else { - delete m_graph; + delete m_graphEdit; } } @@ -101,6 +106,7 @@ bool GraphEditorWindow::saveAs() { QFileDialog dialog( this ); dialog.setWindowModality( Qt::WindowModal ); dialog.setAcceptMode( QFileDialog::AcceptSave ); + dialog.setDefaultSuffix( "json" ); if ( dialog.exec() != QDialog::Accepted ) return false; return saveFile( dialog.selectedFiles().first() ); } From 73c5af43fafea02d2d2e42a3bed6071878d527e6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 17:35:02 +0100 Subject: [PATCH 139/239] [unittests] improve coverage for graph building and serialization --- tests/unittest/Dataflow/graph.cpp | 230 ++++++++++-------- tests/unittest/Dataflow/serialization.cpp | 15 +- .../unittest/data/Dataflow/ExampleGraph.json | 143 +++++------ 3 files changed, 206 insertions(+), 182 deletions(-) diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp index a5442e44fd4..c259e019b2c 100644 --- a/tests/unittest/Dataflow/graph.cpp +++ b/tests/unittest/Dataflow/graph.cpp @@ -22,7 +22,7 @@ void inspectGraph( const DataflowGraph& g ) { std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() << ") :\n"; for ( const auto& n : *( nodes ) ) { std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() - << "\"\n"; + << "\" with uuid \"" << n->getUuid() << "\n"; // Inspect input, output and interfaces of the node std::cout << "\t\tInput ports :\n"; for ( const auto& p : n->getInputs() ) { @@ -82,174 +82,211 @@ void inspectGraph( const DataflowGraph& g ) { TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { SECTION( "Creation of a graph" ) { DataflowGraph g( "Test Graph" ); + // Test not a json error detection auto result = g.loadFromJson( "data/Dataflow/NotAJsonFile.json" ); REQUIRE( !result ); + // Test loading empty graph nlohmann::json emptyJson = {}; result = g.fromJson( emptyJson ); REQUIRE( result ); + + // missing identification of the graph (either id or instance) nlohmann::json noId = { { "model", { "name", "Core DataflowGraph" } } }; result = g.fromJson( noId ); REQUIRE( !result ); g.destroy(); - nlohmann::json noModel = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" } }; + // missing model of the graph + nlohmann::json noModel = { { "instance", "No model in this node" } }; result = g.fromJson( noModel ); REQUIRE( !result ); g.destroy(); - nlohmann::json noGraph = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + // missing instance data --> loads an empty graph + nlohmann::json noGraph = { { "instance", "Missing instance data for model" }, { "model", { "name", "Core DataflowGraph" } } }; result = g.fromJson( noGraph ); REQUIRE( result ); g.destroy(); + // Requesting unknown factory nlohmann::json wrongFactory = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "instance", "unknown factory" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", { "NotAFactory" } } } } } } }; result = g.fromJson( wrongFactory ); REQUIRE( !result ); g.destroy(); - nlohmann::json NotANode = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, - { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, - { "graph", - { { "factories", {} }, - { "nodes", - { { { "id", "tototo" }, - { "model", - { { "instance", "NotANode" }, - { "name", "NotANode" } } } } } } } } } } }; - result = g.fromJson( NotANode ); + // trying to instance an unknown node type + nlohmann::json NotANode = { + { "instance", "graph with unknown node" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "instance", "NotANode" }, + { "model", { { "name", "NotANode" } } } } } } } } } } }; + result = g.fromJson( NotANode ); REQUIRE( !result ); g.destroy(); - nlohmann::json wrongConnection = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + // trying to instance an unknown node type + nlohmann::json NoModelName = { + { "instance", "graph with missing node model information" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SourceFloat" }, { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, - { "connections", { { { "out_id", "wrongId" } } } } } } } } }; - result = g.fromJson( wrongConnection ); + { { { "instance", "Unknown model" }, + { "model", { { "extra", "NotaTypeName" } } } } } } } } } } }; + result = g.fromJson( NoModelName ); REQUIRE( !result ); g.destroy(); - wrongConnection = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + // trying to instance an unknown node type + nlohmann::json noInstanceIdentification = { + { "instance", "graph with missing node model information" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, - { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SourceFloat" }, { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 2 } } } } } } } } }; - result = g.fromJson( wrongConnection ); + { "nodes", { { { "model", { { "name", "Source" } } } } } } } } } } }; + result = g.fromJson( noInstanceIdentification ); REQUIRE( !result ); g.destroy(); - wrongConnection = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + // errors in the connection description + nlohmann::json reusingNodeIdentification = { + { "instance", "graph with wrong connection" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SourceFloat" }, { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 0 }, - { "in_id", "wrongId" }, - { "in_index", 2 } } } } } } } } }; - result = g.fromJson( wrongConnection ); + { { { "instance", "Source" }, { "model", { { "name", "Source" } } } }, + { { "instance", "Source" }, { "model", { { "name", "Sink" } } } } } }, + { "connections", { { { "out_node", "wrongId" } } } } } } } } }; + result = g.fromJson( reusingNodeIdentification ); REQUIRE( !result ); g.destroy(); - - wrongConnection = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + reusingNodeIdentification = { + { "instance", "graph with wrong connection" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SourceFloat" }, { "name", "Source" } } } }, + { { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "model", { { "name", "Source" } } } }, { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 0 }, - { "in_id", "{1111111b-2222-3333-4444-555555555555}" }, - { "in_index", 2 } } } } } } } } }; - result = g.fromJson( wrongConnection ); + { "model", { { "name", "Sink" } } } } } }, + { "connections", { { { "out_node", "wrongId" } } } } } } } } }; + result = g.fromJson( reusingNodeIdentification ); REQUIRE( !result ); g.destroy(); - wrongConnection = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + // errors in the connection description + nlohmann::json wrongConnection = { + { "instance", "graph with wrong connection" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SourceFloat" }, { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "instance", "SinkInt" }, { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 0 }, - { "in_id", "{1111111b-2222-3333-4444-555555555555}" }, - { "in_index", 0 } } } } } } } } }; + { { { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "instance", "SinkInt" }, { "model", { { "name", "Sink" } } } } } }, + { "connections", { { { "out_node", "wrongId" } } } } } } } } }; result = g.fromJson( wrongConnection ); REQUIRE( !result ); g.destroy(); + wrongConnection = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "instance", "Test Graph Inline" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "instance", "SinkInt" }, + { "model", { { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 2 } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + wrongConnection = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "instance", "Test Graph Inline" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "instance", "SinkInt" }, + { "model", { { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 0 }, + { "in_node", "Sink" }, + { "in_port", "from" } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + wrongConnection = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "instance", "Test Graph Inline" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, + { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "id", "{1111111b-2222-3333-4444-555555555555}" }, + { "instance", "SinkInt" }, + { "model", { { "name", "Sink" } } } } } }, + { "connections", + { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { "out_index", 0 }, + { "in_node", "SinkInt" }, + { "in_port", "from" } } } } } } } } }; + result = g.fromJson( wrongConnection ); + REQUIRE( !result ); + g.destroy(); + + // Constructed a correct graph nlohmann::json goodSimpleGraph = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, + { "instance", "Test Graph Inline" }, { "model", - { { "instance", "Test Graph Inline" }, - { "name", "Core DataflowGraph" }, + { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, { "nodes", { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SourceFloat" }, { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", - { { "instance", "SinkFloat" }, { "name", "Sink" } } } } } }, + { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "instance", "SinkFloat" }, + { "model", { { "name", "Sink" } } } } } }, { "connections", { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 0 }, - { "in_id", "{1111111b-2222-3333-4444-555555555555}" }, + { "out_port", "to" }, + { "in_node", "SinkFloat" }, { "in_index", 0 } } } } } } } } }; result = g.fromJson( goodSimpleGraph ); REQUIRE( result ); @@ -346,6 +383,7 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { } SECTION( "Inspection of a graph" ) { + std::cout << "Loading graph data/Dataflow/ExampleGraph.json\n"; auto g = DataflowGraph::loadGraphFromJsonFile( "data/Dataflow/ExampleGraph.json" ); // Factories used by the graph diff --git a/tests/unittest/Dataflow/serialization.cpp b/tests/unittest/Dataflow/serialization.cpp index b01e2e54193..21d8e180afb 100644 --- a/tests/unittest/Dataflow/serialization.cpp +++ b/tests/unittest/Dataflow/serialization.cpp @@ -13,12 +13,12 @@ #include TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { - SECTION( "Serialization of a graph" ) { - + SECTION( "Execution and modification of a graph" ) { using namespace Ra::Dataflow::Core; using DataType = Scalar; DataflowGraph g { "original graph" }; - + g.addJsonMetaData( + { { "extra", { { "info", "missing operators on functional node" } } } } ); auto source_a = new Sources::SingleDataSourceNode( "a" ); g.addNode( std::unique_ptr( source_a ) ); auto a = g.getDataSetter( "a_to" ); @@ -33,9 +33,10 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { TestNode::Arg2_type b ) -> TestNode::Res_type { return a + b; }; - auto op = new TestNode( "addition" ); - op->setOperator( add ); - g.addNode( std::unique_ptr( op ) ); + auto op_unique = std::make_unique( "addition" ); + op_unique->setOperator( add ); + auto [added, op] = g.addNode( std::move( op_unique ) ); + REQUIRE( added ); g.addLink( source_a, "to", op, "a" ); g.addLink( op, "r", sink, "from" ); g.addLink( source_b, "to", op, "b" ); @@ -52,8 +53,6 @@ TEST_CASE( "Dataflow/Core/DataflowGraph", "[Dataflow][Core][DataflowGraph]" ) { // Save the graph std::string tmpdir { "tmpDir4Tests" }; - - std::cout << "Graph tmp dir : " << tmpdir << "\n"; std::filesystem::create_directories( tmpdir ); g.saveToJson( tmpdir + "/GraphSerializationTest.json" ); g.destroy(); diff --git a/tests/unittest/data/Dataflow/ExampleGraph.json b/tests/unittest/data/Dataflow/ExampleGraph.json index 6c9f70b6aae..96025e0cb30 100644 --- a/tests/unittest/data/Dataflow/ExampleGraph.json +++ b/tests/unittest/data/Dataflow/ExampleGraph.json @@ -1,94 +1,93 @@ { - "id": "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}", + "instance": "Example graph", "model": { "graph": { "connections": [ { - "in_id": "{016829fe-65fc-4b09-aaaf-04b76813e9bd}", - "in_index": 0, - "out_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", - "out_index": 0 + "in_node": "reduced vector", + "in_port": "from", + "out_node": "Collection reducer - original collection", + "out_port": "out" }, { - "in_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", - "in_index": 0, - "out_id": "{564cd37a-5d99-45cd-a642-8743a7b10adc}", - "out_index": 0 + "in_node": "Collection reducer - original collection", + "in_port": "in", + "out_node": "Vector", + "out_port": "to" }, { - "in_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", - "in_index": 1, - "out_id": "{5bd5b929-acf2-461b-a7da-73e662d61962}", - "out_index": 0 + "in_node": "Collection reducer - original collection", + "in_port": "f", + "out_node": "Reducer", + "out_port": "f" }, { - "in_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", - "in_index": 2, - "out_id": "{7a0c688e-c785-4849-ad34-89abd1473f4c}", - "out_index": 0 + "in_node": "Collection reducer - original collection", + "in_port": "init", + "out_node": "Neutral", + "out_port": "to" }, { - "in_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", - "in_index": 0, - "out_id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", - "out_index": 0 + "in_node": "Collection reducer - transformed collection", + "in_port": "in", + "out_node": "Collection transformer", + "out_port": "out" }, { - "in_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", - "in_index": 1, - "out_id": "{5bd5b929-acf2-461b-a7da-73e662d61962}", - "out_index": 0 + "in_node": "Collection reducer - transformed collection", + "in_port": "f", + "out_node": "Reducer", + "out_port": "f" }, { - "in_id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", - "in_index": 0, - "out_id": "{564cd37a-5d99-45cd-a642-8743a7b10adc}", - "out_index": 0 + "in_node": "Collection transformer", + "in_port": "in", + "out_node": "Vector", + "out_port": "to" }, { - "in_id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", - "in_index": 1, - "out_id": "{6d442201-7e25-44f4-81cc-4dcea78780b2}", - "out_index": 0 + "in_node": "Collection transformer", + "in_port": "f", + "out_node": "Doubler", + "out_port": "f" }, { - "in_id": "{465d2861-dcbe-4717-a70d-c75e1042a730}", - "in_index": 0, - "out_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", - "out_index": 0 + "in_node": "transformed/reduced vector", + "in_port": "from", + "out_node": "Collection reducer - transformed collection", + "out_port": "out" }, { - "in_id": "{0b74b010-2bc6-4b56-914d-ddbf30701946}", - "in_index": 0, - "out_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", - "out_index": 0 + "in_node": "validation value", + "in_port": "from", + "out_node": "Validator : evaluate the validation predicate", + "out_port": "r" }, { - "in_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", - "in_index": 0, - "out_id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", - "out_index": 0 + "in_node": "Validator : evaluate the validation predicate", + "in_port": "a", + "out_node": "Collection reducer - original collection", + "out_port": "out" }, { - "in_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", - "in_index": 1, - "out_id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", - "out_index": 0 + "in_node": "Validator : evaluate the validation predicate", + "in_port": "b", + "out_node": "Collection reducer - transformed collection", + "out_port": "out" }, { - "in_id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", - "in_index": 2, - "out_id": "{f920fe05-221d-4549-819a-f24dce92d9db}", - "out_index": 0 + "in_node": "Validator : evaluate the validation predicate", + "in_port": "f", + "out_node": "Validator", + "out_port": "f" } ], "factories": [], "nodes": [ { - "id": "{564cd37a-5d99-45cd-a642-8743a7b10adc}", + "instance": "Vector", "model": { "comment": "Unable to save data when serializing a SingleDataSourceNode>.", - "instance": "Vector", "name": "Source>" }, "position": { @@ -97,10 +96,9 @@ } }, { - "id": "{6d442201-7e25-44f4-81cc-4dcea78780b2}", + "instance": "Doubler", "model": { "comment": "Unable to save data when serializing a FunctionSourceNode>.", - "instance": "Doubler", "name": "Source>" }, "position": { @@ -109,9 +107,8 @@ } }, { - "id": "{7a0c688e-c785-4849-ad34-89abd1473f4c}", + "instance": "Neutral", "model": { - "instance": "Neutral", "name": "Source", "number": 0.0 }, @@ -121,10 +118,9 @@ } }, { - "id": "{5bd5b929-acf2-461b-a7da-73e662d61962}", + "instance": "Reducer", "model": { "comment": "Unable to save data when serializing a FunctionSourceNode>.", - "instance": "Reducer", "name": "Source>" }, "position": { @@ -133,9 +129,8 @@ } }, { - "id": "{016829fe-65fc-4b09-aaaf-04b76813e9bd}", + "instance": "reduced vector", "model": { - "instance": "reduced vector", "name": "Sink" }, "position": { @@ -144,10 +139,9 @@ } }, { - "id": "{d1ac47fc-a128-4dea-8542-6303acc77688}", + "instance": "Collection reducer - original collection", "model": { "comment": "Reduce operator could not be serialized for Reduce>", - "instance": "Collection reducer - original collection", "name": "Reduce>" }, "position": { @@ -156,10 +150,9 @@ } }, { - "id": "{22dfcafb-9581-40be-b269-a17cf1e948d6}", + "instance": "Collection reducer - transformed collection", "model": { "comment": "Reduce operator could not be serialized for Reduce>", - "instance": "Collection reducer - transformed collection", "name": "Reduce>" }, "position": { @@ -168,10 +161,9 @@ } }, { - "id": "{b9a33fe7-4ada-4a8e-a104-81341912f6ef}", + "instance": "Collection transformer", "model": { "comment": "Transform operator could not be serialized for Transform>", - "instance": "Collection transformer", "name": "Transform>" }, "position": { @@ -180,9 +172,8 @@ } }, { - "id": "{465d2861-dcbe-4717-a70d-c75e1042a730}", + "instance": "transformed/reduced vector", "model": { - "instance": "transformed/reduced vector", "name": "Sink" }, "position": { @@ -191,10 +182,9 @@ } }, { - "id": "{f920fe05-221d-4549-819a-f24dce92d9db}", + "instance": "Validator", "model": { "comment": "Unable to save data when serializing a FunctionSourceNode>.", - "instance": "Validator", "name": "Source>" }, "position": { @@ -203,9 +193,8 @@ } }, { - "id": "{0b74b010-2bc6-4b56-914d-ddbf30701946}", + "instance": "validation value", "model": { - "instance": "validation value", "name": "Sink" }, "position": { @@ -214,10 +203,9 @@ } }, { - "id": "{22a6cb27-a0c9-4ac4-8adb-5c4b70beb6d4}", + "instance": "Validator : evaluate the validation predicate", "model": { "comment": "Binary operator could not be serialized for BinaryOp bool>", - "instance": "Validator : evaluate the validation predicate", "name": "BinaryOp bool>" }, "position": { @@ -227,7 +215,6 @@ } ] }, - "instance": "Example graph", "name": "Core DataflowGraph" } } From 5464ed0c2d3e12e76827c9d4bb8fc2561fd1c54b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 19:02:17 +0100 Subject: [PATCH 140/239] [dataflow-core] allows to force recompilation of a graph --- src/Dataflow/Core/DataflowGraph.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 9119ffbaf44..2bdc3494b78 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -121,6 +121,9 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// Test if the graph is compiled bool isCompiled() const; + /// Mark the graph as needing recompilation (useful to force recompilation and resources update) + void needsRecompile(); + /// Flag that indicates if the graph should be saved to a file /// This flag is useless outside an load/edit/save scenario bool m_shouldBeSaved { false }; @@ -273,6 +276,10 @@ inline bool DataflowGraph::isCompiled() const { return m_ready; } +inline void DataflowGraph::needsRecompile() { + m_ready = false; +} + inline void DataflowGraph::setNodeFactories( std::shared_ptr factories ) { m_factories = factories; } From 40d026af849bca6a0beab16eb1c9b95df7442f29 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 19:02:47 +0100 Subject: [PATCH 141/239] [unittests] update tests --- tests/unittest/Dataflow/graph.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp index c259e019b2c..59ec6f8b09b 100644 --- a/tests/unittest/Dataflow/graph.cpp +++ b/tests/unittest/Dataflow/graph.cpp @@ -394,8 +394,11 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { REQUIRE( nodes->size() == g->getNodesCount() ); auto c = g->compile(); REQUIRE( c == true ); + REQUIRE( g->isCompiled() ); // Prints the graph content inspectGraph( *g ); + g->needsRecompile(); + REQUIRE( !g->isCompiled() ); // removing the boolean sink from the graph auto n = g->getNode( "validation value" ); @@ -405,6 +408,7 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { REQUIRE( n == nullptr ); c = g->compile(); REQUIRE( c == true ); + // Simplified graph after compilation auto cn = g->getNodesByLevel(); // the source "Validator" is no more in level 0 as it is not reachable from a sink in the From e650df386ca15f93bfa87812586767675951cb52 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 21:25:09 +0100 Subject: [PATCH 142/239] [dataflow-qtGui] use Radium log --- .../QtGui/GraphEditor/NodeAdapterModel.cpp | 4 ++-- .../QtGui/GraphEditor/WidgetFactory.cpp | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index 1c1d20bddb7..013325bc145 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -218,7 +217,8 @@ void updateWidget( Node* node, QWidget* widget ) { for ( size_t i = 0; i < node->getEditableParameters().size(); i++ ) { auto edtParam = node->getEditableParameters()[i].get(); if ( !WidgetFactory::updateWidget( widget, edtParam ) ) { - std::cerr << "Unable to update parameter " << edtParam->getName() << "\n"; + LOG( Ra::Core::Utils::logWARNING ) + << "NodeAdapterModel : unable to update parameter " << edtParam->getName(); } } } diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index b46b39e4f3f..3a786b1099e 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -38,9 +37,9 @@ void registerWidgetInternal( std::type_index typeIdx, widgetsfunctions[typeIdx] = { std::move( widgetCreator ), std::move( widgetUpdater ) }; } else { - // TODO, when PR #1027 will be merged, demangle the type name - std::cerr << "WidgetFactory: trying to add an already existing widget builder for type " - << typeIdx.name() << "." << std::endl; + LOG( Ra::Core::Utils::logWARNING ) + << "WidgetFactory: trying to add an already existing widget builder for type " + << simplifiedDemangledType( typeIdx ) << "."; } } @@ -50,8 +49,9 @@ QWidget* createWidget( EditableParameterBase* editableParameter ) { } else { // TODO, when PR #1027 will be merged, demangle the type name - std::cerr << "WidgetFactory: no defined widget builder for hashed type " - << editableParameter->getType().name() << "." << std::endl; + LOG( Ra::Core::Utils::logWARNING ) + << "WidgetFactory : no defined widget builder for hashed type " + << editableParameter->getType().name() << "."; } return nullptr; } @@ -61,8 +61,9 @@ bool updateWidget( QWidget* widget, EditableParameterBase* editableParameter ) { return widgetsfunctions[editableParameter->getType()].second( widget, editableParameter ); } else { - std::cerr << "WidgetFactory: no defined widget updater for hashed type " - << editableParameter->getType().name() << "." << std::endl; + LOG( Ra::Core::Utils::logWARNING ) + << "WidgetFactory: no defined widget updater for hashed type " + << editableParameter->getType().name() << "."; } return false; } From 4d4b14a6c1e826fba8ecfff22e7b201318f753e2 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 13 Feb 2023 16:57:06 +0100 Subject: [PATCH 143/239] [dataflow-core] try to fix random crash : sinkNode --- src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp index d2c2f0354cd..7b50fe9e006 100644 --- a/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp +++ b/src/Dataflow/Core/Nodes/Sinks/SinkNode.hpp @@ -67,17 +67,20 @@ SinkNode::SinkNode( const std::string& instanceName, const std::string& typeN template void SinkNode::init() { // this should be done only once (or when the address of local data changes) - // What if interfaces were not created before init (e.g. call of init on a node not added to a - // graph) Todo : assert on the existence of the interface - auto interfacePort = static_cast*>( m_interface[0] ); - interfacePort->setData( &m_data ); + if ( m_interface[0] != nullptr ) { + auto interfacePort = static_cast*>( m_interface[0] ); + interfacePort->setData( &m_data ); + } Node::init(); } template bool SinkNode::execute() { - m_data = m_portIn->getData(); - return true; + if ( m_portIn->hasData() ) { + m_data = m_portIn->getData(); + return true; + } + return false; } template From 80a8859384571e797d35e8d081e94d13009ccac8 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 13 Feb 2023 16:57:44 +0100 Subject: [PATCH 144/239] [dataflow-core] try to fix random crash : Port --- src/Dataflow/Core/Port.hpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index 9aba0598511..b3c7d3e1b9b 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -14,11 +14,6 @@ namespace Core { class Node; -template -class PortOut; -template -class PortIn; - /** * \brief Base class for nodes' ports * A port is a strongly typed extremity of connections between nodes. @@ -186,7 +181,10 @@ class PortIn : public PortBase, /// Gets the out port this port is connected to. PortBase* getLink() override; + /// Returns true if the port is linked to an output port that has data. + bool hasData() override; /// Gets a reference to the data pointed by the connected out port. + /// \note no verification is made about the availability of the data. T& getData(); /// Checks if there is not out port already connected and if the data types are the same. /// @param o The other port to test the connection @@ -358,6 +356,12 @@ T& PortIn::getData() { return m_from->getData(); } +template +inline bool PortIn::hasData() { + if ( isLinked() ) { return m_from->hasData(); } + return false; +} + template bool PortIn::accept( PortBase* other ) { if ( !m_from && ( other->getType() == getType() ) ) { return PortBase::accept( other ); } From 0875e21174059a75dfd1246a05cd6662480ad535 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 13 Feb 2023 16:57:59 +0100 Subject: [PATCH 145/239] [dataflow-core] try to fix random crash : Node --- src/Dataflow/Core/Node.hpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 57318d6195e..2a9f6d938ce 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -292,14 +292,10 @@ inline const std::vector>& Node::getOutputs() const { inline const std::vector& Node::buildInterfaces( Node* parent ) { m_interface.clear(); m_interface.shrink_to_fit(); - std::vector>* readFrom; - if ( m_inputs.empty() ) { readFrom = &m_outputs; } - else { - readFrom = &m_inputs; - } + const std::vector>* readFrom = + m_inputs.empty() ? &m_outputs : &m_inputs; m_interface.reserve( readFrom->size() ); for ( const auto& p : *readFrom ) { - // Todo, ensure thereis no twice the same port .... m_interface.emplace_back( p->reflect( parent, getInstanceName() + '_' + p->getName() ) ); } return m_interface; From d4b57dfc260f80889523a341688a4659b9f59c07 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 13 Feb 2023 16:58:18 +0100 Subject: [PATCH 146/239] [dataflow-core] try to fix random crash : Graph --- src/Dataflow/Core/DataflowGraph.cpp | 42 +++++++++++++---------------- src/Dataflow/Core/DataflowGraph.hpp | 23 ++++++++-------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index d29cdcae13a..a13c91a0129 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -274,21 +274,15 @@ bool DataflowGraph::canAdd( const Node* newNode ) const { } std::pair DataflowGraph::addNode( std::unique_ptr newNode ) { - std::map m_mapInputs; // Check if the new node already exists (= same name and type) if ( canAdd( newNode.get() ) ) { - if ( newNode->getInputs().empty() ) { - // it is a source node, add its interface port as input and data setter to the graph - auto& interfaces = newNode->buildInterfaces( this ); + if ( newNode->getInputs().empty() || newNode->getOutputs().empty() ) { + bool ( DataflowGraph::*addGraphIOPort )( PortBase* ) = newNode->getInputs().empty() + ? &DataflowGraph::addSetter + : &DataflowGraph::addGetter; + auto& interfaces = newNode->buildInterfaces( this ); for ( auto p : interfaces ) { - addSetter( p ); - } - } - if ( newNode->getOutputs().empty() ) { - // it is a sink node, add its interface port as output to the graph - auto& interfaces = newNode->buildInterfaces( this ); - for ( auto p : interfaces ) { - addGetter( p ); + ( this->*addGraphIOPort )( p ); } } auto addedNode = newNode.get(); @@ -310,14 +304,12 @@ bool DataflowGraph::removeNode( Node*& node ) { int index = -1; if ( ( index = findNode( node ) ) == -1 ) { return false; } else { - if ( node->getInputs().empty() ) { // Check if it is a source node - for ( auto& port : node->getInterfaces() ) { - removeSetter( port->getName() ); - } - } - if ( node->getOutputs().empty() ) { // Check if it is a sink node - for ( auto& port : node->getInterfaces() ) { - removeGetter( port->getName() ); + if ( node->getInputs().empty() || node->getOutputs().empty() ) { + bool ( DataflowGraph::*removeGraphIOPort )( const std::string& ) = + node->getInputs().empty() ? &DataflowGraph::removeSetter + : &DataflowGraph::removeGetter; + for ( auto& p : node->getInterfaces() ) { + ( this->*removeGraphIOPort )( p->getName() ); } } m_nodes.erase( m_nodes.begin() + index ); @@ -597,12 +589,16 @@ bool DataflowGraph::removeSetter( const std::string& setterName ) { bool DataflowGraph::addGetter( PortBase* out ) { if ( out->is_input() ) { return false; } - // This is very similar to addOutput, except the data can't be set, they will be in the init - // of any Sink + // This is very similar to addOutput, except the data can't be set. + // Data pointer must be set by any sink at compile time, in the init function to refer to the + // data fetched from the associated input link bool found = false; // TODO check if this verification is needed ? for ( auto& output : m_outputs ) { - if ( output->getName() == out->getName() ) { found = true; } + if ( output->getName() == out->getName() ) { + found = true; + break; + } } if ( !found ) { m_outputs.emplace_back( out ); } return !found; diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index 2bdc3494b78..f6661e10c01 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -128,9 +128,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// This flag is useless outside an load/edit/save scenario bool m_shouldBeSaved { false }; - /// \brief Creates an output port connected to the named input port of the graph. + /// \brief Gets an output port connected to the named input port of the graph. /// Return the connected output port if success, sharing the ownership with the caller. - /// Allows to set data to the graph from the caller . + /// This output port could then be used through setter->setData( ptr ) to set the graph input + /// from the data pointer owned by the caller. /// \note As ownership is shared with the caller, the graph must survive the returned /// pointer to be able to use the dataSetter.. /// \params portName The name of the input port of the graph @@ -141,10 +142,10 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \brief connect the data setting port from its inputs. bool activateDataSetter( const std::string& portName ); - /// Returns an alias to the named output port of the graph. + /// \brief Returns an alias to the named output port of the graph. /// Allows to get the data stored at this port after the execution of the graph. - /// \note ownership is left to the graph. The graph must survive the returned - /// pointer to be able to use the dataGetter.. + /// \note ownership is left to the graph, not shared. The graph must survive the returned + /// pointer to be able to use the dataGetter. /// \params portName the name of the output port PortBase* getDataGetter( const std::string& portName ); @@ -157,17 +158,17 @@ class RA_DATAFLOW_API DataflowGraph : public Node /// \brief Data getter descriptor. /// A Data getter descriptor is composed of an output port (belonging to any node of the graph), /// its name and its type. - /// Use getData on the output port to extract data from the graph + /// Use getData on the output port to extract data from the graph. + /// \note, a dataGetter is valid only after successful compilation of the graph. + /// \todo find a way to test the validity of the getter (invalid if no path exists from any + /// source port to the associated sink port) using DataGetterDesc = std::tuple; - /// Creates a vector that stores all the DataSetters (\see getDataSetter) of the graph. - - /// \note If called multiple times for the same port, only the last returned result is - /// usable. + /// Creates a vector that stores all the existing DataSetters (\see getDataSetter) of the graph. /// TODO : Verify why, when listing the data setters, they are connected ... std::vector getAllDataSetters() const; - /// Creates a vector that stores all the DataGetters (\see getDataGetter) of the graph. + /// Creates a vector that stores all the existing DataGetters (\see getDataGetter) of the graph. /// A tuple is composed of an output port belonging to the graph, its name its type. std::vector getAllDataGetters() const; From bbaa086def7f0cfb27cd9063c401289de72357a1 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 13 Feb 2023 16:58:59 +0100 Subject: [PATCH 147/239] [unittests] try to fix random crash --- tests/unittest/Dataflow/customnodes.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index c8d2325fb9d..66e3b90ce0b 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -177,10 +177,10 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { REQUIRE( inputThreshold != nullptr ); auto filteredCollection = g->getDataGetter( "rs_from" ); - auto generatedOperator = g->getDataGetter( "nm_from" ); + REQUIRE( filteredCollection != nullptr ); + auto generatedOperator = g->getDataGetter( "nm_from" ); + REQUIRE( generatedOperator != nullptr ); - auto r = g->compile(); - REQUIRE( r ); // parameterise the graph using CollectionType = Ra::Core::VectorArray; CollectionType testVector; @@ -205,10 +205,15 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { } std::cout << '\n'; + /// Getters are usable only after successful compilation/execution of the graph + auto r = g->compile(); + REQUIRE( r ); + // execute the graph that filter out nothing // execute r = g->execute(); REQUIRE( r ); + // Get results as references (no need to get them again later) auto& vres = filteredCollection->getData(); auto& vop = generatedOperator->getData(); From 20a704a055d4e5a2bcc0ddd187060e4601cd99bd Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 13 Feb 2023 22:27:03 +0100 Subject: [PATCH 148/239] [unittest] try to fix random crash --- tests/unittest/Dataflow/customnodes.cpp | 56 +++++++++++++++---------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 66e3b90ce0b..71b71f17f14 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -129,22 +129,38 @@ class FilterSelector final : public Node // Reusable function to create a graph template DataflowGraph* buildgraph( const std::string& name ) { - auto ds = new Sources::SingleDataSourceNode>( "ds" ); - auto rs = new Sinks::SinkNode>( "rs" ); - auto ts = new Sources::SingleDataSourceNode( "ts" ); - auto ss = new Customs::CustomStringSource( "ss" ); - auto nm = new Customs::CustomStringSink( "nm" ); - auto fs = new Customs::FilterSelector( "fs" ); - auto fl = new Functionals::FilterNode>( "fl" ); - auto g = new DataflowGraph( name ); - g->addNode( std::unique_ptr( ds ) ); - g->addNode( std::unique_ptr( rs ) ); - g->addNode( std::unique_ptr( ts ) ); - g->addNode( std::unique_ptr( ss ) ); - g->addNode( std::unique_ptr( nm ) ); - g->addNode( std::unique_ptr( fs ) ); - g->addNode( std::unique_ptr( fl ) ); + + auto addedNode = g->addNode( + std::make_unique>>( "ds" ) ); + REQUIRE( addedNode.first ); + auto ds = addedNode.second; + + addedNode = + g->addNode( std::make_unique>>( "rs" ) ); + REQUIRE( addedNode.first ); + auto rs = addedNode.second; + + addedNode = g->addNode( std::make_unique>( "ts" ) ); + REQUIRE( addedNode.first ); + auto ts = addedNode.second; + + addedNode = g->addNode( std::make_unique( "ss" ) ); + REQUIRE( addedNode.first ); + auto ss = addedNode.second; + + addedNode = g->addNode( std::make_unique( "nm" ) ); + REQUIRE( addedNode.first ); + auto nm = addedNode.second; + + addedNode = g->addNode( std::make_unique>( "fs" ) ); + REQUIRE( addedNode.first ); + auto fs = addedNode.second; + + addedNode = g->addNode( + std::make_unique>>( "fl" ) ); + REQUIRE( addedNode.first ); + auto fl = addedNode.second; bool ok; ok = g->addLink( ds, "to", fl, "in" ); @@ -205,16 +221,13 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { } std::cout << '\n'; - /// Getters are usable only after successful compilation/execution of the graph - auto r = g->compile(); - REQUIRE( r ); - // execute the graph that filter out nothing // execute - r = g->execute(); + auto r = g->execute(); REQUIRE( r ); - // Get results as references (no need to get them again later) + // Getters are usable only after successful compilation/execution of the graph + // Get results as references (no need to get them again later if the graph does not change) auto& vres = filteredCollection->getData(); auto& vop = generatedOperator->getData(); @@ -240,7 +253,6 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { std::cout << ord << ' '; } std::cout << '\n'; - // Change operator to keep element less than threshold op = "<"; r = g->execute(); From 81626a7630541cdce0e96e45129072354bb7e166 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 24 Feb 2023 18:08:56 +0100 Subject: [PATCH 149/239] [dataflow] remove dependency from stduuid external --- doc/basics/dependencies.md | 6 +- doc/concepts/dataflow/HelloGraph.json | 141 +++++++++--------- doc/concepts/nodesystem.md | 2 +- doc/images/HelloGraph.png | Bin 158928 -> 46186 bytes .../FunctionalsGraph/main.cpp | 2 + .../GraphSerialization/main.cpp | 2 + external/Dataflow/CMakeLists.txt | 21 --- external/Dataflow/patches/stduuid.patch | 24 --- src/Dataflow/Core/CMakeLists.txt | 4 +- src/Dataflow/Core/Config.cmake.in | 3 - src/Dataflow/Core/DataflowGraph.cpp | 44 ++---- src/Dataflow/Core/Node.cpp | 61 +------- src/Dataflow/Core/Node.hpp | 17 --- .../QtGui/GraphEditor/GraphEditorView.cpp | 8 +- .../QtGui/GraphEditor/NodeAdapterModel.cpp | 1 + .../QtGui/GraphEditor/NodeAdapterModel.hpp | 4 +- tests/unittest/Dataflow/graph.cpp | 114 +++++++------- 17 files changed, 152 insertions(+), 302 deletions(-) delete mode 100644 external/Dataflow/patches/stduuid.patch diff --git a/doc/basics/dependencies.md b/doc/basics/dependencies.md index 5b52ab7d3d9..ee134f70a7c 100644 --- a/doc/basics/dependencies.md +++ b/doc/basics/dependencies.md @@ -7,7 +7,7 @@ Radium relies on several external libraries to load files or to represent some d * [Engine] glm, globjects, glbindings, tinyEXR * [IO] Assimp * [Gui] Qt Core, Qt Widgets and Qt OpenGL v5.5+ (5.14 at least, Qt6 support is experimental), PowerSlider -* [Dataflow] stduuid, RadiumNodeEditor +* [Dataflow] RadiumNodeEditor * [doc] Doxygen-awesome-css * stb_image @@ -101,7 +101,6 @@ set(tinyEXR_DIR "/path/to/external/install/share/tinyEXR/cmake/" CACHE PATH "My set(assimp_DIR "/path/to/external/install/lib/cmake/assimp-5.0/" CACHE PATH "My assimp location") set(tinyply_DIR "/path/to/external/install/lib/cmake/tinyply/" CACHE PATH "My tinyply location") set(PowerSlider_DIR "/path/to/external/install/lib/cmake/PowerSlider/" CACHE PATH "My PowerSlider location") -set(stduuid_DIR "/path/to/external/install/lib/cmake/stduuid/" CACHE PATH "My stduuid") set(RadiumNodeEditor_DIR "/path/to/external/install/lib/cmake/RadiumNodeEditor/" CACHE PATH "My NodeEditor") set(RADIUM_IO_ASSIMP ON CACHE BOOL "Radium uses assimp io") set(RADIUM_IO_TINYPLY ON CACHE BOOL "Radium uses tinyply io") @@ -125,7 +124,6 @@ To this end, just provide the corresponding '*_DIR' to cmake at configuration ti Currently supported (note that these paths must refer to the installation directory of the corresponding library): -* `stduuid_DIR` * `RadiumNodeEditor_DIR` * `assimp_DIR` * `tinyply_DIR` @@ -142,8 +140,6 @@ Currently supported (note that these paths must refer to the installation direct Radium is compiled and tested with specific version of dependencies, as given in the external's folder CMakeLists.txt and state here for the record -* stduuid: https://github.com/mariusbancila/stduuid, [3afe7193facd5d674de709fccc44d5055e144d7a], - * with options `-DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON` * RadiumNodeEditor: https://github.com/MathiasPaulin/RadiumQtNodeEditor.git, [main], * with options `None` * assimp: https://github.com/assimp/assimp.git, [tags/v5.0.1], diff --git a/doc/concepts/dataflow/HelloGraph.json b/doc/concepts/dataflow/HelloGraph.json index 9290ca65ffd..ec1e743560f 100644 --- a/doc/concepts/dataflow/HelloGraph.json +++ b/doc/concepts/dataflow/HelloGraph.json @@ -1,76 +1,71 @@ { - "id": "{c1c08892-9064-4484-b1f6-1c7da3bb56f8}", - "model": { - "graph": { - "connections": [ - { - "in_id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", - "in_index": 0, - "out_id": "{eef7a427-c725-42d1-adb3-29d5112597d6}", - "out_index": 0 - }, - { - "in_id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", - "in_index": 1, - "out_id": "{ef56ef16-569c-48e7-ae42-91e130088737}", - "out_index": 0 - }, - { - "in_id": "{41d4e5df-fbfe-482f-ab5e-10c7941ffd60}", - "in_index": 0, - "out_id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", - "out_index": 0 - } - ], - "factories": [], - "nodes": [ - { - "id": "{eef7a427-c725-42d1-adb3-29d5112597d6}", - "model": { - "instance": "Source", - "name": "Source>" - }, - "position": { - "x": -23.0, - "y": 88.0 - } - }, - { - "id": "{ef56ef16-569c-48e7-ae42-91e130088737}", - "model": { - "instance": "Selector", - "name": "Source>" - }, - "position": { - "x": -112.0, - "y": 239.0 - } - }, - { - "id": "{daa522ef-9242-436d-a57d-487bc8d850f1}", - "model": { - "instance": "Filter", - "name": "Filter>" - }, - "position": { - "x": 310.0, - "y": 158.0 - } - }, - { - "id": "{41d4e5df-fbfe-482f-ab5e-10c7941ffd60}", - "model": { - "instance": "Sink", - "name": "Sink>" - }, - "position": { - "x": 609.0, - "y": 238.0 - } - } - ] + "instance": "helloGraph", + "model": { + "graph": { + "connections": [ + { + "in_node": "Filter", + "in_port": "in", + "out_node": "Source", + "out_port": "to" }, - "instance": "helloGraph", - "name": "Core DataflowGraph" - } + { + "in_node": "Filter", + "in_port": "f", + "out_node": "Selector", + "out_port": "f" + }, + { + "in_node": "Sink", + "in_port": "from", + "out_node": "Filter", + "out_port": "out" + } + ], + "factories": [], + "nodes": [ + { + "instance": "Source", + "model": { + "name": "Source>" + }, + "position": { + "x": -195.0, + "y": -82.0 + } + }, + { + "instance": "Selector", + "model": { + "name": "Source>" + }, + "position": { + "x": -279.0, + "y": 35.0 + } + }, + { + "instance": "Filter", + "model": { + "name": "Filter>" + }, + "position": { + "x": 44.0, + "y": -45.0 + } + }, + { + "instance": "Sink", + "model": { + "name": "Sink>" + }, + "position": { + "x": 267.0, + "y": 12.0 + } + } + ] + }, + "name": "Core DataflowGraph" + } } diff --git a/doc/concepts/nodesystem.md b/doc/concepts/nodesystem.md index 54c05739422..e484a849cf0 100644 --- a/doc/concepts/nodesystem.md +++ b/doc/concepts/nodesystem.md @@ -160,7 +160,7 @@ Output ports (1) are : ### 2. Compiling the graph and getting input/output accessors -In order to use the graph as a function actiing on its input, it should be first compiled by +In order to use the graph as a function acting on its input, it should be first compiled by \snippet examples/DataflowExamples/HelloGraph/main.cpp Verifying the graph can be compiled If the compilation success the accessors for the input data and the output result might be fetched from the graph diff --git a/doc/images/HelloGraph.png b/doc/images/HelloGraph.png index e0df369af27ae5ceff32124d3d06151976b82a1b..f7dfe06218d5f387849c43ca2a7b9df60ab6d66e 100644 GIT binary patch literal 46186 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ʄH6w@e0jgS)lU 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 literal 158928 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

      HK0a(r8P^GIQc=_tar(om}VTtK6#*pg;y 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 diff --git a/examples/DataflowExamples/FunctionalsGraph/main.cpp b/examples/DataflowExamples/FunctionalsGraph/main.cpp index 136cd27ed9c..a0e2d80642b 100644 --- a/examples/DataflowExamples/FunctionalsGraph/main.cpp +++ b/examples/DataflowExamples/FunctionalsGraph/main.cpp @@ -5,6 +5,8 @@ #include #include +#include + using namespace Ra::Dataflow::Core; /* ----------------------------------------------------------------------------------- */ diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp index 2cb9c77e7e0..f57a6626ec1 100644 --- a/examples/DataflowExamples/GraphSerialization/main.cpp +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -5,6 +5,8 @@ #include +#include + using namespace Ra::Dataflow::Core; /* ----------------------------------------------------------------------------------- */ diff --git a/external/Dataflow/CMakeLists.txt b/external/Dataflow/CMakeLists.txt index 08d14491398..b991d7c59d2 100644 --- a/external/Dataflow/CMakeLists.txt +++ b/external/Dataflow/CMakeLists.txt @@ -17,27 +17,6 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) add_custom_target(DataflowExternals ALL) -if(NOT DEFINED stduuid_DIR) - check_externals_prerequisite() - status_message("" "stduuid" "remote git") - ExternalProject_Add( - stduuid - GIT_TAG 3afe7193facd5d674de709fccc44d5055e144d7a - GIT_REPOSITORY https://github.com/mariusbancila/stduuid - GIT_SHALLOW TRUE - GIT_PROGRESS TRUE - PATCH_COMMAND git reset --hard && git apply -v --ignore-whitespace - "${CMAKE_CURRENT_LIST_DIR}/patches/stduuid.patch" - INSTALL_DIR "${CMAKE_INSTALL_PREFIX}" - CMAKE_ARGS ${RADIUM_EXTERNAL_CMAKE_OPTIONS} -DUUID_BUILD_TESTS=OFF -DUUID_ENABLE_INSTALL=ON - -DCMAKE_INSTALL_PREFIX= "-DCMAKE_MESSAGE_INDENT=${indent_string}\;" - ) - set_external_dir(stduuid "lib/cmake/stduuid/") - add_dependencies(DataflowExternals stduuid) -else() - status_message("" "stduuid" ${stduuid_DIR}) -endif() - if(NOT DEFINED RadiumNodeEditor_DIR) check_externals_prerequisite() status_message("" "RadiumNodeEditor" "remote git") diff --git a/external/Dataflow/patches/stduuid.patch b/external/Dataflow/patches/stduuid.patch deleted file mode 100644 index 4022183e398..00000000000 --- a/external/Dataflow/patches/stduuid.patch +++ /dev/null @@ -1,24 +0,0 @@ -diff --git a/cmake/Config.cmake.in b/cmake/Config.cmake.in -index 7217b72..6b099bc 100644 ---- a/cmake/Config.cmake.in -+++ b/cmake/Config.cmake.in -@@ -1,3 +1,7 @@ -+include(FindPackageHandleStandardArgs) -+set(${CMAKE_FIND_PACKAGE_NAME}_CONFIG ${CMAKE_CURRENT_LIST_FILE}) -+find_package_handle_standard_args(@PROJECT_NAME@ CONFIG_MODE) -+ - @PACKAGE_INIT@ - - include(CMakeFindDependencyMacro) -@@ -9,6 +13,7 @@ if (@UUID_SYSTEM_GENERATOR@) - endif () - endif () - --include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") -- --check_required_components(@PROJECT_NAME@) -\ No newline at end of file -+if(NOT TARGET @PROJECT_NAME@) -+ include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@-targets.cmake") -+endif() -+check_required_components(@PROJECT_NAME@) diff --git a/src/Dataflow/Core/CMakeLists.txt b/src/Dataflow/Core/CMakeLists.txt index 36699772aad..4db4eb2428c 100644 --- a/src/Dataflow/Core/CMakeLists.txt +++ b/src/Dataflow/Core/CMakeLists.txt @@ -10,8 +10,6 @@ add_library( ${dataflow_core_private} ) -find_package(stduuid REQUIRED NO_DEFAULT_PATH) - # This one should be extracted directly from parent project properties. target_compile_definitions(${ra_dataflowcore_target} PRIVATE RA_DATAFLOW_EXPORTS) @@ -21,7 +19,7 @@ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "MSVC") endif() add_dependencies(${ra_dataflowcore_target} Core) -target_link_libraries(${ra_dataflowcore_target} PUBLIC Core stduuid) +target_link_libraries(${ra_dataflowcore_target} PUBLIC Core) message(STATUS "Configuring library ${ra_dataflowcore_target} with standard settings") configure_radium_target(${ra_dataflowcore_target}) diff --git a/src/Dataflow/Core/Config.cmake.in b/src/Dataflow/Core/Config.cmake.in index c196aa2280b..200aa5af5f9 100644 --- a/src/Dataflow/Core/Config.cmake.in +++ b/src/Dataflow/Core/Config.cmake.in @@ -17,8 +17,5 @@ if (DataflowCore_FOUND AND NOT TARGET DataflowCore) endif() if(Configure_DataflowCore) - # Theses paths reflect the paths founds in RadiumEngine/external/Dataflow/package - set(stduuid_DIR "@stduuid_DIR@") - find_dependency(stduuid REQUIRED NO_DEFAULT_PATH) include("${CMAKE_CURRENT_LIST_DIR}/../Dataflow/Core/DataflowCoreTargets.cmake") endif() diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index a13c91a0129..cfec19c4932 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -126,23 +126,13 @@ bool DataflowGraph::loadFromJson( const std::string& jsonFilePath ) { std::pair getLinkInfo( const std::string& which, const nlohmann::json& linkData, - const std::unordered_map& nodeById, const std::map& nodeByName ) { std::string field = which + "_node"; Node* node { nullptr }; - if ( linkData.contains( field ) ) { - auto itNode = nodeByName.find( linkData[field] ); - if ( itNode != nodeByName.end() ) { node = itNode->second; } - } + + auto itNode = nodeByName.find( linkData[field] ); + if ( itNode != nodeByName.end() ) { node = itNode->second; } else { - // try to find the node by id - field = which + "_id"; - if ( linkData.contains( field ) ) { - auto itNode = nodeById.find( linkData[field] ); - if ( itNode != nodeById.end() ) { node = itNode->second; } - } - } - if ( node == nullptr ) { // Error, could not find the node std::string msg = std::string { "Node " } + which + " not found in cache " + " : " + linkData.dump(); @@ -192,7 +182,6 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } } } - std::unordered_map nodeById; std::map nodeByName; auto nodes = data["graph"]["nodes"]; for ( auto& n : nodes ) { @@ -202,17 +191,10 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { return false; } std::string nodeTypeName = n["model"]["name"]; - std::string instanceName, id; - bool nodeIsIdentified = false; - if ( n.contains( "instance" ) ) { - instanceName = n["instance"]; - nodeIsIdentified = true; - } - if ( n.contains( "id" ) ) { - id = n["id"]; - nodeIsIdentified = true; - } - if ( !nodeIsIdentified ) { + std::string instanceName; + + if ( n.contains( "instance" ) ) { instanceName = n["instance"]; } + else { LOG( logERROR ) << "Found a node of type " << nodeTypeName << " without identification "; return false; @@ -227,14 +209,6 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { return false; } } - if ( !id.empty() ) { - auto [it, inserted] = nodeById.insert( { id, newNode } ); - if ( !inserted ) { - LOG( logERROR ) - << "DataflowGraph::loadFromJson : duplicated node uuid " << id; - return false; - } - } } else { LOG( logERROR ) << "Unable to create the node " << nodeTypeName; @@ -243,14 +217,14 @@ bool DataflowGraph::fromJsonInternal( const nlohmann::json& data ) { } auto links = data["graph"]["connections"]; for ( auto& l : links ) { - auto [nodeFrom, fromOutput] = getLinkInfo( "out", l, nodeById, nodeByName ); + auto [nodeFrom, fromOutput] = getLinkInfo( "out", l, nodeByName ); if ( nodeFrom == nullptr ) { LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON." << " Could not find the link source (" << fromOutput << "). Link not added."; return false; } - auto [nodeTo, toInput] = getLinkInfo( "in", l, nodeById, nodeByName ); + auto [nodeTo, toInput] = getLinkInfo( "in", l, nodeByName ); if ( nodeTo == nullptr ) { LOG( logERROR ) << "DataflowGraph::loadFromJson: error when parsing JSON." << " Could not find the link source (" << toInput diff --git a/src/Dataflow/Core/Node.cpp b/src/Dataflow/Core/Node.cpp index a236276ed2b..4d741b1f453 100644 --- a/src/Dataflow/Core/Node.cpp +++ b/src/Dataflow/Core/Node.cpp @@ -8,59 +8,21 @@ namespace Core { using namespace Ra::Core::Utils; -bool Node::s_uuidGeneratorInitialized { false }; -std::unique_ptr Node::s_uidGenerator { nullptr }; -std::unique_ptr Node::s_uuidSeeds { nullptr }; - -void Node::createUuidGenerator() { - if ( s_uuidGeneratorInitialized ) { return; } - std::random_device rd; - auto seed_data = std::array {}; - std::generate( std::begin( seed_data ), std::end( seed_data ), std::ref( rd ) ); - std::seed_seq seq( std::begin( seed_data ), std::end( seed_data ) ); - s_uuidSeeds = std::make_unique( seq ); - s_uidGenerator = std::make_unique( s_uuidSeeds.get() ); - // delete generator; - s_uuidGeneratorInitialized = true; -} - -/// Generates the uuid of the node -void Node::generateUuid() { - if ( !s_uuidGeneratorInitialized ) { createUuidGenerator(); } - m_uuid = ( *s_uidGenerator )(); -} -/// Gets the UUID of the node as a string -std::string Node::getUuid() const { - return std::string { "{" } + uuids::to_string( m_uuid ) + "}"; -} - Node::Node( const std::string& instanceName, const std::string& typeName ) : - m_typeName { typeName }, m_instanceName { instanceName } { - generateUuid(); -} + m_typeName { typeName }, m_instanceName { instanceName } {} bool Node::fromJson( const nlohmann::json& data ) { if ( data.empty() ) { // This is to avoid wrong error message when creating node from the editor return true; } - bool hasIdOrName = false; - if ( data.contains( "instance" ) ) { - hasIdOrName = true; - m_instanceName = data["instance"]; - } - // get the common content of the Node from the json data - if ( data.contains( "id" ) ) { - hasIdOrName = true; - std::string struuid = data["id"]; - m_uuid = uuids::uuid::from_string( struuid ).value(); - } - if ( !hasIdOrName ) { - LOG( logERROR ) << "Missing required uuid or instance name when loading node " - << m_instanceName; + + if ( data.contains( "instance" ) ) { m_instanceName = data["instance"]; } + else { + LOG( logERROR ) << "Missing required instance name when loading node " << m_instanceName; return false; } - + // get the common content of the Node from the json data bool loaded = false; if ( data.contains( "model" ) ) { // get the specific concrete node information @@ -73,9 +35,7 @@ bool Node::fromJson( const nlohmann::json& data ) { } // get the supplemental information related to application/gui/... for ( auto& [key, value] : data.items() ) { - if ( key != "id" && key != "instance" && key != "model" ) { - m_extraJsonData.emplace( key, value ); - } + if ( key != "instance" && key != "model" ) { m_extraJsonData.emplace( key, value ); } } return loaded; } @@ -83,11 +43,6 @@ bool Node::fromJson( const nlohmann::json& data ) { void Node::toJson( nlohmann::json& data ) const { // write the common content of the Node to the json data -#if 0 - // id is only needed for QtNodeEditor, do not save it, it will be regenerated when needed - std::string struuid = "{" + uuids::to_string( m_uuid ) + "}"; - data["id"] = struuid; -#endif data["instance"] = m_instanceName; nlohmann::json model; @@ -99,7 +54,7 @@ void Node::toJson( nlohmann::json& data ) const { // store the supplemental information related to application/gui/... for ( auto& [key, value] : m_extraJsonData.items() ) { - if ( key != "id" && key != "instance" && key != "model" ) { data.emplace( key, value ); } + if ( key != "instance" && key != "model" ) { data.emplace( key, value ); } } } diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 2a9f6d938ce..66903b60a9d 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -7,8 +7,6 @@ #include -#include - #include #include #include @@ -123,9 +121,6 @@ class RA_DATAFLOW_API Node /// \brief Sets the instance name (rename) the node void setInstanceName( const std::string& newName ); - /// \brief Gets the UUID of the node as a string - std::string getUuid() const; - /// @} /// \name Serialization of a node @@ -232,18 +227,6 @@ class RA_DATAFLOW_API Node /// Additional data on the node, added by application or gui or ... nlohmann::json m_extraJsonData; - /// \brief Generates the uuid of the node - void generateUuid(); - /// The uuid of the node - uuids::uuid m_uuid; - - private: - /// generator for uuid - static bool s_uuidGeneratorInitialized; - static std::unique_ptr s_uidGenerator; - static std::unique_ptr s_uuidSeeds; - static void createUuidGenerator(); - public: /// \brief Returns the demangled type name of the node or any human readable representation of /// the type name. diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 5b688950752..1ff2c73db85 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -113,9 +113,13 @@ void GraphEditorView::editGraph( DataflowGraph* g ) { if ( m_dataflowGraph ) { buildAdapterRegistry( NodeFactoriesManager::getFactoryManager() ); const auto& nodes = *( m_dataflowGraph->getNodes() ); + // NodeToUuid mapping + std::map nodeToUuid; // inserting nodes for ( const auto& n : nodes ) { - scene->importNode( std::make_unique( m_dataflowGraph, n.get() ) ); + auto nodeAdapter = std::make_unique( m_dataflowGraph, n.get() ); + nodeToUuid.insert( { n.get(), nodeAdapter->uuid() } ); + scene->importNode( std::move( nodeAdapter ) ); } // inserting connections for ( const auto& n : nodes ) { @@ -130,7 +134,7 @@ void GraphEditorView::editGraph( DataflowGraph* g ) { outPortIndex++; } scene->importConnection( - nodeOut->getUuid().c_str(), outPortIndex, n->getUuid().c_str(), numPort ); + nodeToUuid[nodeOut], outPortIndex, nodeToUuid[n.get()], numPort ); } numPort++; } diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index 013325bc145..86d46097f0a 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -46,6 +46,7 @@ using namespace Ra::Gui::Widgets; NodeAdapterModel::NodeAdapterModel( DataflowGraph* graph, Node* n ) : m_node { n }, m_dataflowGraph { graph } { + m_uuid = QUuid::createUuid(); m_inputsConnected.resize( m_node->getInputs().size() ); m_widget = NodeDataModelTools::getWidget( m_node ); NodeDataModelTools::updateWidget( m_node, m_widget ); diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp index b7ae7633d50..b25c3a50a80 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp @@ -34,7 +34,7 @@ class RA_DATAFLOW_API NodeAdapterModel : public QtNodes::NodeDataModel QString name() const override { return m_node->getTypeName().c_str(); } - QString uuid() const override { return m_node->getUuid().c_str(); } + QString uuid() const override { return m_uuid.toString(); } bool isDeletable() override { return true; } // Assume all nodes belong to the graph @@ -74,6 +74,8 @@ class RA_DATAFLOW_API NodeAdapterModel : public QtNodes::NodeDataModel mutable QtNodes::NodeValidationState m_validationState = QtNodes::NodeValidationState::Valid; mutable QString m_validationError = QString( "" ); + QUuid m_uuid; + public: QJsonObject save() const override; void restore( QJsonObject const& p ) override; diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp index 59ec6f8b09b..594942affa1 100644 --- a/tests/unittest/Dataflow/graph.cpp +++ b/tests/unittest/Dataflow/graph.cpp @@ -22,7 +22,7 @@ void inspectGraph( const DataflowGraph& g ) { std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() << ") :\n"; for ( const auto& n : *( nodes ) ) { std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() - << "\" with uuid \"" << n->getUuid() << "\n"; + << "\"\n"; // Inspect input, output and interfaces of the node std::cout << "\t\tInput ports :\n"; for ( const auto& p : n->getInputs() ) { @@ -180,10 +180,8 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { { "graph", { { "factories", {} }, { "nodes", - { { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "model", { { "name", "Sink" } } } } } }, + { { { "model", { { "name", "Source" } } } }, + { { "model", { { "name", "Sink" } } } } } }, { "connections", { { { "out_node", "wrongId" } } } } } } } } }; result = g.fromJson( reusingNodeIdentification ); REQUIRE( !result ); @@ -205,86 +203,74 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { REQUIRE( !result ); g.destroy(); - wrongConnection = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, - { "instance", "Test Graph Inline" }, - { "model", - { { "name", "Core DataflowGraph" }, - { "graph", - { { "factories", {} }, - { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "instance", "SourceFloat" }, - { "model", { { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "instance", "SinkInt" }, - { "model", { { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 2 } } } } } } } } }; - result = g.fromJson( wrongConnection ); + wrongConnection = { + { "instance", "Test Graph Inline" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "instance", "SinkInt" }, { "model", { { "name", "Sink" } } } } } }, + { "connections", + { { { "out_node", "SourceFloat" }, { "out_index", 2 } } } } } } } } }; + result = g.fromJson( wrongConnection ); REQUIRE( !result ); g.destroy(); - wrongConnection = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, - { "instance", "Test Graph Inline" }, - { "model", - { { "name", "Core DataflowGraph" }, - { "graph", - { { "factories", {} }, - { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "instance", "SourceFloat" }, - { "model", { { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "instance", "SinkInt" }, - { "model", { { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 0 }, - { "in_node", "Sink" }, - { "in_port", "from" } } } } } } } } }; - result = g.fromJson( wrongConnection ); + wrongConnection = { + { "instance", "Test Graph Inline" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "instance", "SinkInt" }, { "model", { { "name", "Sink" } } } } } }, + { "connections", + { { { "out_node", "SourceFloat" }, + { "out_index", 0 }, + { "in_node", "Sink" }, + { "in_port", "from" } } } } } } } } }; + result = g.fromJson( wrongConnection ); REQUIRE( !result ); g.destroy(); - wrongConnection = { { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, - { "instance", "Test Graph Inline" }, - { "model", - { { "name", "Core DataflowGraph" }, - { "graph", - { { "factories", {} }, - { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "instance", "SourceFloat" }, - { "model", { { "name", "Source" } } } }, - { { "id", "{1111111b-2222-3333-4444-555555555555}" }, - { "instance", "SinkInt" }, - { "model", { { "name", "Sink" } } } } } }, - { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, - { "out_index", 0 }, - { "in_node", "SinkInt" }, - { "in_port", "from" } } } } } } } } }; - result = g.fromJson( wrongConnection ); + wrongConnection = { + { "instance", "Test Graph Inline" }, + { "model", + { { "name", "Core DataflowGraph" }, + { "graph", + { { "factories", {} }, + { "nodes", + { { { "instance", "SourceFloat" }, + { "model", { { "name", "Source" } } } }, + { { "instance", "SinkInt" }, { "model", { { "name", "Sink" } } } } } }, + { "connections", + { { { "out_node", "SourceFloat" }, + { "out_index", 0 }, + { "in_node", "SinkInt" }, + { "in_port", "from" } } } } } } } } }; + result = g.fromJson( wrongConnection ); REQUIRE( !result ); g.destroy(); // Constructed a correct graph nlohmann::json goodSimpleGraph = { - { "id", "{d3c4d86a-49d9-496e-b01b-1c142538d8ef}" }, { "instance", "Test Graph Inline" }, { "model", { { "name", "Core DataflowGraph" }, { "graph", { { "factories", {} }, { "nodes", - { { { "id", "{1111111a-2222-3333-4444-555555555555}" }, - { "instance", "SourceFloat" }, + { { { "instance", "SourceFloat" }, { "model", { { "name", "Source" } } } }, { { "instance", "SinkFloat" }, { "model", { { "name", "Sink" } } } } } }, { "connections", - { { { "out_id", "{1111111a-2222-3333-4444-555555555555}" }, + { { { "out_node", "SourceFloat" }, { "out_port", "to" }, { "in_node", "SinkFloat" }, { "in_index", 0 } } } } } } } } }; From 8102056bb44ab5860a8751516a9badec26736ac9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 20 Mar 2023 16:15:09 +0100 Subject: [PATCH 150/239] [dataflow-core][tests] improve Radium types demangler --- src/Dataflow/Core/TypeDemangler.cpp | 2 +- src/Dataflow/Core/TypeDemangler.hpp | 25 ++++++++++++--------- tests/unittest/Dataflow/sourcesandsinks.cpp | 1 + 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/Dataflow/Core/TypeDemangler.cpp b/src/Dataflow/Core/TypeDemangler.cpp index 96ba64a3fac..686564ee629 100644 --- a/src/Dataflow/Core/TypeDemangler.cpp +++ b/src/Dataflow/Core/TypeDemangler.cpp @@ -13,7 +13,7 @@ namespace TypeInternal { /** \todo verify windows specific type demangling needs. * */ -RA_DATAFLOW_API std::string makeTypeReadable( const std::string& fullType ) { +RA_DATAFLOW_API auto makeTypeReadable( const std::string& fullType ) -> std::string { static std::map knownTypes { { "std::", "" }, { ", std::allocator", "" }, diff --git a/src/Dataflow/Core/TypeDemangler.hpp b/src/Dataflow/Core/TypeDemangler.hpp index 84999e397ce..eb337a39d66 100644 --- a/src/Dataflow/Core/TypeDemangler.hpp +++ b/src/Dataflow/Core/TypeDemangler.hpp @@ -7,39 +7,44 @@ namespace Ra { namespace Dataflow { namespace Core { -/// \brief Return the human readable version of the type name T with simplified radium types +/// \brief Return the human readable version of the type name T with simplified radium type names template -const char* simplifiedDemangledType() noexcept; +auto simplifiedDemangledType() noexcept -> std::string; -/// \brief Return the human readable version of the type name T with simplified radium types +/// \brief Return the human readable version of the type name T with simplified radium type names template -const char* simplifiedDemangledType( const T& ) noexcept; +auto simplifiedDemangledType( const T& ) noexcept -> std::string; -RA_DATAFLOW_API std::string simplifiedDemangledType( const std::type_index& typeName ) noexcept; +/// \brief Return the human readable version of the type name whose index is known, with simplified +/// radium type names +/// \param typeName The typeIndex whose simplified named is requested +/// \return The Radium-simplified type name +RA_DATAFLOW_API auto simplifiedDemangledType( const std::type_index& typeName ) noexcept + -> std::string; // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- namespace TypeInternal { -RA_DATAFLOW_API std::string makeTypeReadable( const std::string& ); +RA_DATAFLOW_API auto makeTypeReadable( const std::string& ) -> std::string; } template -const char* simplifiedDemangledType() noexcept { +auto simplifiedDemangledType() noexcept -> std::string { static auto demangled_name = []() { std::string demangledType = TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType() ); return demangledType; }(); - return demangled_name.data(); + return demangled_name; } template -const char* simplifiedDemangledType( const T& ) noexcept { +auto simplifiedDemangledType( const T& ) noexcept -> std::string { return simplifiedDemangledType(); } -inline std::string simplifiedDemangledType( const std::type_index& typeName ) noexcept { +inline auto simplifiedDemangledType( const std::type_index& typeName ) noexcept -> std::string { return TypeInternal::makeTypeReadable( Ra::Core::Utils::demangleType( typeName ) ); } diff --git a/tests/unittest/Dataflow/sourcesandsinks.cpp b/tests/unittest/Dataflow/sourcesandsinks.cpp index 24e1c50ecb5..7992c705409 100644 --- a/tests/unittest/Dataflow/sourcesandsinks.cpp +++ b/tests/unittest/Dataflow/sourcesandsinks.cpp @@ -38,6 +38,7 @@ void testGraph( const std::string& name, T in, T& out ) { g->execute(); T r = output->getData(); + std::cout << "Getting a " << simplifiedDemangledType( r ) << " from interface port ... "; out = r; g->releaseDataSetter( "in_to" ); From 736e44f245e551086a7370239204ffe8331ad298 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 20 Mar 2023 17:17:16 +0100 Subject: [PATCH 151/239] [dataflow-core][tests][examples] Node factory improvements --- .../GraphSerialization/main.cpp | 5 +- src/Dataflow/Core/NodeFactory.cpp | 81 ++++----- src/Dataflow/Core/NodeFactory.hpp | 159 +++++++++--------- src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp | 3 +- tests/unittest/Dataflow/customnodes.cpp | 21 ++- 5 files changed, 137 insertions(+), 132 deletions(-) diff --git a/examples/DataflowExamples/GraphSerialization/main.cpp b/examples/DataflowExamples/GraphSerialization/main.cpp index f57a6626ec1..4d49e77f8f1 100644 --- a/examples/DataflowExamples/GraphSerialization/main.cpp +++ b/examples/DataflowExamples/GraphSerialization/main.cpp @@ -30,16 +30,13 @@ int main( int argc, char* argv[] ) { customFactory->registerNodeCreator>( Sinks::SinkNode::getTypename() + "_", "Custom" ); - // register the factory into the system to enable loading any graph that use these nodes - NodeFactoriesManager::registerFactory( customFactory ); //! [Creating the factory for the custom node types and add it to the node system] { //! [Creating an empty graph using the custom nodes factory] DataflowGraph g { "Serialization example" }; // Add to the graph the custom factory (built-in nodes are automatically managed) - g.addFactory( "ExampleCustomFactory", - NodeFactoriesManager::getFactory( "ExampleCustomFactory" ) ); + g.addFactory( NodeFactoriesManager::getFactory( "ExampleCustomFactory" ) ); //! [Creating an empty graph using the custom nodes factory] //! [Creating Nodes] diff --git a/src/Dataflow/Core/NodeFactory.cpp b/src/Dataflow/Core/NodeFactory.cpp index f2f1fbfdb5a..dbe230b7bf7 100644 --- a/src/Dataflow/Core/NodeFactory.cpp +++ b/src/Dataflow/Core/NodeFactory.cpp @@ -2,8 +2,6 @@ #include -#include - namespace Ra { namespace Dataflow { namespace Core { @@ -14,16 +12,15 @@ const std::string dataFlowBuiltInsFactoryName { "DataFlowBuiltIns" }; NodeFactory::NodeFactory( std::string name ) : m_name( std::move( name ) ) {} -std::string NodeFactory::getName() const { +auto NodeFactory::getName() const -> std::string { return m_name; } -Node* NodeFactory::createNode( std::string& nodeType, - const nlohmann::json& data, - DataflowGraph* owningGraph ) { - auto it = m_nodesCreators.find( nodeType ); - if ( it != m_nodesCreators.end() ) { - auto node = it->second.first( data ); +auto NodeFactory::createNode( const std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph ) -> Node* { + if ( auto itr = m_nodesCreators.find( nodeType ); itr != m_nodesCreators.end() ) { + auto* node = itr->second.first( data ); if ( owningGraph != nullptr ) { auto [added, n] = owningGraph->addNode( std::unique_ptr( node ) ); return n; @@ -33,11 +30,11 @@ Node* NodeFactory::createNode( std::string& nodeType, return nullptr; } -bool NodeFactory::registerNodeCreator( std::string nodeType, +auto NodeFactory::registerNodeCreator( const std::string& nodeType, NodeCreatorFunctor nodeCreator, - const std::string& nodeCategory ) { - auto it = m_nodesCreators.find( nodeType ); - if ( it == m_nodesCreators.end() ) { + const std::string& nodeCategory ) -> bool { + + if ( auto itr = m_nodesCreators.find( nodeType ); itr == m_nodesCreators.end() ) { m_nodesCreators[nodeType] = { std::move( nodeCreator ), nodeCategory }; return true; } @@ -47,16 +44,17 @@ bool NodeFactory::registerNodeCreator( std::string nodeType, return false; } -size_t NodeFactory::nextNodeId() { +auto NodeFactory::nextNodeId() -> size_t { return ++m_nodesCreated; } -Node* NodeFactorySet::createNode( std::string& nodeType, - const nlohmann::json& data, - DataflowGraph* owningGraph ) { - for ( const auto& it : m_factories ) { - auto node = it.second->createNode( nodeType, data, owningGraph ); - if ( node ) { return node; } +auto NodeFactorySet::createNode( const std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph ) -> Node* { + for ( const auto& itr : m_factories ) { + if ( auto* node = itr.second->createNode( nodeType, data, owningGraph ); node ) { + return node; + } } LOG( Ra::Core::Utils::logERROR ) << "NodeFactorySet: unable to find constructor for " << nodeType << " in any managed factory."; @@ -66,44 +64,39 @@ Node* NodeFactorySet::createNode( std::string& nodeType, namespace NodeFactoriesManager { /** - * \brief Allow static intialization without init order problems + * \brief Allow static initialization without init order problems * \return The manager singleton */ -NodeFactorySet& getFactoryManager() { +auto getFactoryManager() -> NodeFactorySet& { static NodeFactorySet s_factoryManager {}; return s_factoryManager; } -bool registerFactory( NodeFactorySet::mapped_type factory ) { - auto& fctMngr = getFactoryManager(); - auto factoryName = factory->getName(); - return fctMngr.addFactory( std::move( factoryName ), std::move( factory ) ); +auto registerFactory( NodeFactorySet::mapped_type factory ) -> bool { + return getFactoryManager().addFactory( std::move( factory ) ); } -NodeFactorySet::mapped_type createFactory( const std::string& name ) { - return NodeFactorySet::mapped_type { new NodeFactorySet::mapped_type::element_type( name ) }; +auto createFactory( const NodeFactorySet::key_type& name ) -> NodeFactorySet::mapped_type { + auto factory = getFactory( name ); + if ( factory == nullptr ) { + factory = std::make_shared( name ); + registerFactory( factory ); + } + return factory; } -NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ) { - auto& fctMngr = getFactoryManager(); - auto factory = fctMngr.find( factoryName ); - if ( factory == fctMngr.end() ) { return nullptr; } - return factory->second; +auto getFactory( const NodeFactorySet::key_type& name ) -> NodeFactorySet::mapped_type { + auto& fctMgr = getFactoryManager(); + if ( auto factory = fctMgr.find( name ); factory != fctMgr.end() ) { return factory->second; } + return nullptr; } -bool unregisterFactory( NodeFactorySet::key_type factoryName ) { - auto& fctMngr = getFactoryManager(); - return fctMngr.removeFactory( factoryName ); +auto unregisterFactory( const NodeFactorySet::key_type& name ) -> bool { + return getFactoryManager().removeFactory( name ); } -NodeFactorySet::mapped_type getDataFlowBuiltInsFactory() { - auto& fctMngr = getFactoryManager(); - auto i = fctMngr.find( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); - if ( i != fctMngr.end() ) { return i->second; } - - // Should never be there - std::cerr << "@&$&$@&$@&$ ERROR !!!!!! Core factory not registered\n"; - std::abort(); +auto getDataFlowBuiltInsFactory() -> NodeFactorySet::mapped_type { + return getFactory( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); } } // namespace NodeFactoriesManager diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index 6d0c18ec4e8..3d4d979fdc7 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -16,9 +16,9 @@ namespace Core { class DataflowGraph; /** - * NodeFactory store a set of functions allowing to dynmically create dataflow nodes. - * A NodeFactory is used when loading a nodegraph from a json representation of the graph to - * instanciate all the loaded nodes. + * NodeFactory store a set of functions allowing to dynamically create dataflow nodes. + * A NodeFactory is used when loading a node graph from a json representation of the graph to + * instantiate all the loaded nodes. * Each DataflowGraph must have a reference to the nodeFactory to be used to create all the node he * has. * @@ -29,7 +29,7 @@ class RA_DATAFLOW_API NodeFactory /** Creates an empty factory with the given name */ explicit NodeFactory( std::string name ); - std::string getName() const; + [[nodiscard]] auto getName() const -> std::string; /** Function that creates and initialize a node. * Typical implementation of such a function should do the following : @@ -50,8 +50,8 @@ class RA_DATAFLOW_API NodeFactory * collision). */ template - bool registerNodeCreator( NodeCreatorFunctor nodeCreator, - const std::string& nodeCategory = "RadiumNodes" ); + auto registerNodeCreator( NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory = "RadiumNodes" ) -> bool; /** * Associate, for a given concrete node type, a generic NodeCreatorFunctor @@ -63,8 +63,8 @@ class RA_DATAFLOW_API NodeFactory * collision). */ template - bool registerNodeCreator( const std::string& instanceNamePrefix, - const std::string& nodeCategory = "RadiumNodes" ); + auto registerNodeCreator( const std::string& instanceNamePrefix, + const std::string& nodeCategory = "RadiumNodes" ) -> bool; /** * Associate, for a given concrete node type name, a NodeCreatorFunctor * \param nodeType the name of the concrete type @@ -73,15 +73,15 @@ class RA_DATAFLOW_API NodeFactory * \return true if the node creator is successfully added, false if not (e.g. due to a name * collision). */ - bool registerNodeCreator( std::string nodeType, + auto registerNodeCreator( const std::string& nodeType, NodeCreatorFunctor nodeCreator, - const std::string& nodeCategory = "RadiumNodes" ); + const std::string& nodeCategory = "RadiumNodes" ) -> bool; /** * Get an unique, increasing node id. * \return */ - size_t nextNodeId(); + auto nextNodeId() -> size_t; /** Create a node of the requested type. * The node is filled with the given json content. @@ -91,14 +91,14 @@ class RA_DATAFLOW_API NodeFactory * \param owningGraph if non null, the node is added to ths graph * \return the new node. Ownership of the returned pointer is left to the caller. */ - [[nodiscard]] Node* createNode( std::string& nodeType, - const nlohmann::json& data, - DataflowGraph* owningGraph = nullptr ); + [[nodiscard]] auto createNode( const std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph = nullptr ) -> Node*; /** * The type of the associative container used to store the factory * this container associate a concrete node type name to a pair - * + * * The name of the node category is helpful for graphical NodeGraph editor. * By default this is set to be "RadiumNodes" */ @@ -108,7 +108,7 @@ class RA_DATAFLOW_API NodeFactory * Get a const reference on the associative map * \return */ - [[nodiscard]] const ContainerType& getFactoryMap() const; + [[nodiscard]] auto getFactoryMap() const -> const ContainerType&; private: ContainerType m_nodesCreators; @@ -122,66 +122,70 @@ class RA_DATAFLOW_API NodeFactory class RA_DATAFLOW_API NodeFactorySet { public: - using key_type = std::string; - using mapped_type = std::shared_ptr; - using value_type = std::pair; + using container_type = std::map>; - using container_type = std::map; + using key_type = container_type::key_type; + using mapped_type = container_type::mapped_type; + using value_type = container_type::value_type; using const_iterator = container_type::const_iterator; using iterator = container_type::iterator; /** * Add a factory to the set of factories available - * \param factoryname the name of the factory * \param factory the factory * \return true if the factory was inserted, false if the insertion was prevented by an * already existing factory with the same name. */ - bool addFactory( key_type factoryname, mapped_type factory ); + auto addFactory( mapped_type factory ) -> bool; /** * \brief Test if a factory exists in the set with the given name - * \param factoryname The name of the factory to search for - * \return an optional that is empty (evaluates to false) if no factory exeist with the given + * \param name The name of the factory to search for + * \return an optional that is empty (evaluates to false) if no factory exists with the given * name or that contains the existing factory. */ - Ra::Core::Utils::optional - hasFactory( const NodeFactorySet::key_type& factoryname ); + auto hasFactory( const key_type& name ) -> Ra::Core::Utils::optional; /** - * Remove the identified factory from the set - * \param factoryname the name of the factory to remove + * \brief Remove the identified factory from the set + * \param name the name of the factory to remove * \return true if the factory was removed, false if the factory does not exist in the set. */ - bool removeFactory( const key_type& factoryname ); + auto removeFactory( const key_type& name ) -> bool; /** - * Create a node using one of the functor (if it exists) registered in one factory for the given - * type name. + * * \return the created node, nullptr if there is no construction functor registered for the * type. */ - [[nodiscard]] Node* createNode( std::string& nodeType, - const nlohmann::json& data, - DataflowGraph* owningGraph = nullptr ); + /** + * \brief Create a node using one of the functor (if it exists) registered in one factory for + * the given type name. \param nodeType name of the node type (as simplified by Radium + * demangler) to create \param data json data to fill the created node \param owningGraph Graph + * in which the node should be added, if not nullptr. \return The created node, nullptr in case + * of failure + */ + [[nodiscard]] auto createNode( const std::string& nodeType, + const nlohmann::json& data, + DataflowGraph* owningGraph = nullptr ) -> Node*; /* Wrappers to the interface of the underlying container * see https://en.cppreference.com/w/cpp/container/map */ - const_iterator begin() const; - const_iterator end() const; - const_iterator cbegin() const; - const_iterator cend() const; - const_iterator find( const key_type& key ) const; - std::pair insert( value_type value ); - size_t erase( const key_type& key ); + auto begin() const -> const_iterator; + auto end() const -> const_iterator; + auto cbegin() const -> const_iterator; + auto cend() const -> const_iterator; + auto find( const key_type& key ) const -> const_iterator; + auto insert( value_type value ) -> std::pair; + auto erase( const key_type& key ) -> size_t; private: container_type m_factories; }; -/** TODO Make this a class similar to all the managers of Radium. +/** * Implement a NodeFactoryManager that stores a set of factories available to the system. * Such a manager will be populated with Core::Dataflow node factories (Specialized sources, * specialized sink, ...) and will allow users to register its own factories. @@ -196,13 +200,13 @@ class RA_DATAFLOW_API NodeFactorySet * * @note: the factory name "DataFlowBuiltIns" is reserved and correspond to the base nodes available * for each dataflow graph (Specialized sources, specialized sink, ...). This factory will be - * automatically added to all created factoryset. + * automatically added to all created factory set. */ namespace NodeFactoriesManager { /** Names of the system Builtins factories (automatically added to each graph) */ extern const std::string dataFlowBuiltInsFactoryName; -RA_DATAFLOW_API NodeFactorySet& getFactoryManager(); +RA_DATAFLOW_API auto getFactoryManager() -> NodeFactorySet&; /** Register a factory into the manager. * The key will be fetched from the factory (its name) @@ -213,49 +217,50 @@ RA_DATAFLOW_API NodeFactorySet& getFactoryManager(); * \param factory * \return true if the factory was registered, false if not (e.g. due to name collision). */ -RA_DATAFLOW_API bool registerFactory( NodeFactorySet::mapped_type factory ); +RA_DATAFLOW_API auto registerFactory( NodeFactorySet::mapped_type factory ) -> bool; /** - * \brief Create a factory to be customized and later added to the manager + * \brief Create and register a factory to the manager. * \param name The name of the factory to create * \return a configurable factory. - * \note The created factory is not registered into the manager. */ -RA_DATAFLOW_API NodeFactorySet::mapped_type createFactory( const std::string& name ); +RA_DATAFLOW_API auto createFactory( const NodeFactorySet::key_type& name ) + -> NodeFactorySet::mapped_type; /** * \brief Gets the given factory from the manager - * \param factoryName + * \param name The name of the factory to get * \return a shared_ptr to the requested factory, nullptr if the factory does not exist. */ -RA_DATAFLOW_API NodeFactorySet::mapped_type getFactory( NodeFactorySet::key_type factoryName ); +RA_DATAFLOW_API auto getFactory( const NodeFactorySet::key_type& name ) + -> NodeFactorySet::mapped_type; /** * \brief Unregister the factory from the manager - * \param factoryName + * \param name The name of the factory to unregister * \return true if the factory was unregistered, false if not (e.g. for names not being managed). */ -RA_DATAFLOW_API bool unregisterFactory( NodeFactorySet::key_type factoryName ); +RA_DATAFLOW_API auto unregisterFactory( const NodeFactorySet::key_type& name ) -> bool; /** * \brief Gets the factory for nodes exported by the Core dataflow library. * \return */ -RA_DATAFLOW_API NodeFactorySet::mapped_type getDataFlowBuiltInsFactory(); +RA_DATAFLOW_API auto getDataFlowBuiltInsFactory() -> NodeFactorySet::mapped_type; } // namespace NodeFactoriesManager // ----------------------------------------------------------------- // ---------------------- inline methods --------------------------- template -bool NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, - const std::string& nodeCategory ) { +auto NodeFactory::registerNodeCreator( NodeCreatorFunctor nodeCreator, + const std::string& nodeCategory ) -> bool { return registerNodeCreator( T::getTypename(), std::move( nodeCreator ), nodeCategory ); } template -bool NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, - const std::string& nodeCategory ) { +auto NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, + const std::string& nodeCategory ) -> bool { return registerNodeCreator( T::getTypename(), [this, instanceNamePrefix]( const nlohmann::json& data ) { @@ -271,49 +276,45 @@ bool NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, nodeCategory ); } -inline const NodeFactory::ContainerType& NodeFactory::getFactoryMap() const { +inline auto NodeFactory::getFactoryMap() const -> const NodeFactory::ContainerType& { return m_nodesCreators; } -inline bool NodeFactorySet::addFactory( NodeFactorySet::key_type factoryname, - NodeFactorySet::mapped_type factory ) { - const auto [loc, inserted] = insert( { std::move( factoryname ), std::move( factory ) } ); +inline auto NodeFactorySet::addFactory( NodeFactorySet::mapped_type factory ) -> bool { + const auto [loc, inserted] = insert( { factory->getName(), std::move( factory ) } ); return inserted; } -inline Ra::Core::Utils::optional -NodeFactorySet::hasFactory( const NodeFactorySet::key_type& factoryname ) { - auto f = m_factories.find( factoryname ); - if ( f != m_factories.end() ) { return f->second; } - else { - return {}; - } +inline auto NodeFactorySet::hasFactory( const NodeFactorySet::key_type& name ) + -> Ra::Core::Utils::optional { + if ( auto fct = m_factories.find( name ); fct != m_factories.end() ) { return fct->second; } + return {}; } -inline bool NodeFactorySet::removeFactory( const NodeFactorySet::key_type& factoryname ) { - return erase( factoryname ); +inline auto NodeFactorySet::removeFactory( const NodeFactorySet::key_type& name ) -> bool { + return erase( name ); } -inline NodeFactorySet::const_iterator NodeFactorySet::begin() const { +inline auto NodeFactorySet::begin() const -> NodeFactorySet::const_iterator { return m_factories.begin(); } -inline NodeFactorySet::const_iterator NodeFactorySet::end() const { +inline auto NodeFactorySet::end() const -> NodeFactorySet::const_iterator { return m_factories.end(); } -inline NodeFactorySet::const_iterator NodeFactorySet::cbegin() const { +inline auto NodeFactorySet::cbegin() const -> NodeFactorySet::const_iterator { return m_factories.cbegin(); } -inline NodeFactorySet::const_iterator NodeFactorySet::cend() const { +inline auto NodeFactorySet::cend() const -> NodeFactorySet::const_iterator { return m_factories.cend(); } -inline NodeFactorySet::const_iterator -NodeFactorySet::find( const NodeFactorySet::key_type& key ) const { +inline auto NodeFactorySet::find( const NodeFactorySet::key_type& key ) const + -> NodeFactorySet::const_iterator { return m_factories.find( key ); } -inline std::pair -NodeFactorySet::insert( NodeFactorySet::value_type value ) { +inline auto NodeFactorySet::insert( NodeFactorySet::value_type value ) + -> std::pair { return m_factories.insert( std::move( value ) ); } -inline size_t NodeFactorySet::erase( const NodeFactorySet::key_type& key ) { +inline auto NodeFactorySet::erase( const NodeFactorySet::key_type& key ) -> size_t { return m_factories.erase( key ); } diff --git a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp index 7994962f111..cd7a666cc71 100644 --- a/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp +++ b/src/Dataflow/Core/Nodes/CoreBuiltInsNodes.cpp @@ -12,6 +12,7 @@ namespace Core { namespace NodeFactoriesManager { void registerStandardFactories() { + if ( getFactory( NodeFactoriesManager::dataFlowBuiltInsFactoryName ) ) { return; } auto coreFactory = createFactory( NodeFactoriesManager::dataFlowBuiltInsFactoryName ); /* --- Sources --- */ Private::registerSourcesFactories( coreFactory ); @@ -24,8 +25,6 @@ void registerStandardFactories() { /* --- Graphs --- */ coreFactory->registerNodeCreator( DataflowGraph::getTypename() + "_", "Graph" ); - - registerFactory( coreFactory ); } } // namespace NodeFactoriesManager } // namespace Core diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 71b71f17f14..ba10b04d954 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -298,12 +298,27 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { registered = customFactory->registerNodeCreator>( Customs::FilterSelector::getTypename() + "_", "Custom" ); REQUIRE( registered == false ); - // register the factory into the system to enable loading any graph that use these nodes - NodeFactoriesManager::registerFactory( customFactory ); + + std::cout << "Building the following custom nodes with the factory " + << customFactory->getName() << "\n"; + for ( auto [name, functor] : customFactory->getFactoryMap() ) { + std::cout << name << ", "; + } + std::cout << "\n"; + + nlohmann::json emptyData; + auto customSource = customFactory->createNode( + "Source, allocator>>", emptyData, nullptr ); + REQUIRE( customSource != nullptr ); + + std::cout << "Created node " << customSource->getInstanceName() << " with type " + << customSource->getTypeName() << "\n"; + + //"Source, allocator>>" // build a graph auto g = buildgraph( "testCustomNodes" ); - g->addFactory( customFactory->getName(), customFactory ); + g->addFactory( customFactory ); std::string tmpdir { "customGraphExport/" }; std::filesystem::create_directories( tmpdir ); From 95643d08070a692bf26cffbdee9c9180e1918af2 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 20 Mar 2023 17:48:39 +0100 Subject: [PATCH 152/239] [dataflow-core][dataflow-qtgui][tests] fix typos and profile for DataflowGraph --- src/Dataflow/Core/DataflowGraph.hpp | 88 +++++++++++-------- .../QtGui/GraphEditor/GraphEditorView.cpp | 2 +- tests/unittest/Dataflow/graph.cpp | 23 +++-- 3 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/Dataflow/Core/DataflowGraph.hpp b/src/Dataflow/Core/DataflowGraph.hpp index f6661e10c01..4c99b7c5030 100644 --- a/src/Dataflow/Core/DataflowGraph.hpp +++ b/src/Dataflow/Core/DataflowGraph.hpp @@ -16,7 +16,7 @@ namespace Core { */ /** - * \brief Represent a set of connected nodes that define a computational graph + * \brief Represent a set of connected nodes that define a Direct Acyclic Computational Graph * Ownership of nodes is given to the graph at construction time. * \todo make a "graph embedding node" that allow to seemlesly integrate a graph as a node in * another graph @@ -38,45 +38,46 @@ class RA_DATAFLOW_API DataflowGraph : public Node bool execute() override; void destroy() override; - /// Set the factory set to use when loading a graph - /// This function replace the existing factoryset if any + /// \brief Set the factories to use when loading a graph. + /// \param factories new factory set that will replace the existing factory set if any. void setNodeFactories( std::shared_ptr factories ); - /// get the node factory set associated with from the graph. - /// returns nullptr if no factoryset is associated with the graph + /// \brief Get the node factories associated with the graph. + /// \return returns nullptr if no factory set is associated with the graph. std::shared_ptr getNodeFactories() const; - /// Add a factory to the factoryset of the graph. - /// Creates the factoryset if it does not exists - void addFactory( const std::string& name, std::shared_ptr f ); - - /// Add a factory to the factoryset of the graph. - /// Creates the factoryset if it does not exists + /// \brief Add a factory to the factory set of the graph. + /// Creates the factory set if it does not exists + /// \param f a shared pointer to the factory to be added. The name of this factory void addFactory( std::shared_ptr f ); - /// Loads nodes and links from a JSON file. + /// \brief Remove a factory from the factory set of the graph. + /// \param name the name of the factory to remove + /// \return true if the factory was found and removed + bool removeFactory( const std::string& name ); + + /// \brief Loads nodes and links from a JSON file. /// \param jsonFilePath The path to the JSON file. + /// \return true if the file was loaded, false if an error occurs. bool loadFromJson( const std::string& jsonFilePath ); - /// Saves nodes and links to a JSON file. + /// \brief Saves nodes and links to a JSON file. /// \param jsonFilePath The path to the JSON file. void saveToJson( const std::string& jsonFilePath ); - /// \brief Test if a node can be added to a graph - /// \param newNode const naked pointer to the candidate node - /// \return true if ownership could be transferred to the graph. - virtual bool canAdd( const Node* newNode ) const; - - /// Adds a node to the render graph. Adds interface ports to the node newNode and the - /// corresponding input and output ports to the graph. - /// \param newNode The node to add to the render graph. - /// ownership of the node is transferred to the graph + /// \brief Adds a node to the render graph. + /// Adds interface ports to the node newNode and the corresponding input and output ports to + /// the graph. + /// \param newNode The node to add to the graph. + /// \return a pair with a bool and a raw pointer to the Node. If the bool is true, the raw + /// pointer is owned by the graph. If the bool is false, the raw pointer ownership is left to + /// the caller. virtual std::pair addNode( std::unique_ptr newNode ); - /// Removes a node from the render graph. Removes input and output ports of the graph - /// corresponding to interface ports of the node. - /// \param node The node to remove from the render graph. - /// \return true if the node was removed and the given pointer is set to nullptr, false else + /// \brief Removes a node from the render graph. + /// Removes input and output ports, corresponding to interface ports of the node, from the + /// graph. \param node The node to remove from the graph. \return true if the node was removed + /// and the given pointer is set to nullptr, false else virtual bool removeNode( Node*& node ); /// Connects two nodes of the render graph. @@ -92,18 +93,23 @@ class RA_DATAFLOW_API DataflowGraph : public Node Node* nodeTo, const std::string& nodeToInputName ); - /// Removes the link connected to this node's input port + /// + /// \brief Removes the link connected to a node's input port + /// \param node the node to unlink + /// \param nodeInputName the name of the port to unlink + /// \return true if link is removed, false if not. bool removeLink( Node* node, const std::string& nodeInputName ); - /// Gets the nodes - const std::vector>* getNodes() const; + /// \brief Get the vector of all the nodes on the graph + /// \return + const std::vector>& getNodes() const; /// Gets a specific node according to its instance name. /// \param instanceNameNode The instance name of the node. Node* getNode( const std::string& instanceNameNode ) const; /// Gets the nodes ordered by level (after compilation) - const std::vector>* getNodesByLevel() const; + const std::vector>& getNodesByLevel() const; /// Compile the render graph to check its validity and simplify it. /// The compilation has multiple goals: @@ -180,6 +186,11 @@ class RA_DATAFLOW_API DataflowGraph : public Node bool fromJsonInternal( const nlohmann::json& data ) override; void toJsonInternal( nlohmann::json& ) const override; + /// \brief Test if a node can be added to a graph + /// \param newNode const naked pointer to the candidate node + /// \return true if ownership could be transferred to the graph. + virtual bool canAdd( const Node* newNode ) const; + private: /// Flag set after successful compilation indicating graph is ready to be executed /// This flag is reset as soon as the graph is modified. @@ -289,21 +300,20 @@ inline std::shared_ptr DataflowGraph::getNodeFactories() const { return m_factories; } -inline void DataflowGraph::addFactory( const std::string& name, std::shared_ptr f ) { +inline void DataflowGraph::addFactory( std::shared_ptr f ) { if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } - m_factories->addFactory( name, f ); + m_factories->addFactory( f ); } -inline void DataflowGraph::addFactory( std::shared_ptr f ) { - if ( !m_factories ) { m_factories.reset( new NodeFactorySet ); } - m_factories->addFactory( f->getName(), f ); +inline bool DataflowGraph::removeFactory( const std::string& name ) { + return m_factories->removeFactory( name ); } -inline const std::vector>* DataflowGraph::getNodes() const { - return &m_nodes; +inline const std::vector>& DataflowGraph::getNodes() const { + return m_nodes; } -inline const std::vector>* DataflowGraph::getNodesByLevel() const { - return &m_nodesByLevel; +inline const std::vector>& DataflowGraph::getNodesByLevel() const { + return m_nodesByLevel; } inline size_t DataflowGraph::getNodesCount() const { return m_nodes.size(); diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 1ff2c73db85..84f4526d9b5 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -112,7 +112,7 @@ void GraphEditorView::editGraph( DataflowGraph* g ) { m_dataflowGraph = g; if ( m_dataflowGraph ) { buildAdapterRegistry( NodeFactoriesManager::getFactoryManager() ); - const auto& nodes = *( m_dataflowGraph->getNodes() ); + auto& nodes = m_dataflowGraph->getNodes(); // NodeToUuid mapping std::map nodeToUuid; // inserting nodes diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp index 594942affa1..01f8ebfd0ce 100644 --- a/tests/unittest/Dataflow/graph.cpp +++ b/tests/unittest/Dataflow/graph.cpp @@ -18,9 +18,9 @@ void inspectGraph( const DataflowGraph& g ) { } // Nodes of the graph - auto nodes = g.getNodes(); - std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() << ") :\n"; - for ( const auto& n : *( nodes ) ) { + auto& nodes = g.getNodes(); + std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes.size() << ") :\n"; + for ( const auto& n : nodes ) { std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() << "\"\n"; // Inspect input, output and interfaces of the node @@ -40,11 +40,11 @@ void inspectGraph( const DataflowGraph& g ) { // Nodes by level after the compilation if ( g.isCompiled() ) { - auto cn = g.getNodesByLevel(); + auto& cn = g.getNodesByLevel(); std::cout << "Nodes of the graph, sorted by level after compiling the graph :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { + for ( size_t i = 0; i < cn.size(); ++i ) { std::cout << "\tLevel " << i << " :\n"; - for ( const auto n : ( *cn )[i] ) { + for ( const auto n : cn[i] ) { std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; } } @@ -375,9 +375,8 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { // Factories used by the graph auto factories = g->getNodeFactories(); REQUIRE( factories != nullptr ); - auto nodes = g->getNodes(); - REQUIRE( nodes != nullptr ); - REQUIRE( nodes->size() == g->getNodesCount() ); + auto& nodes = g->getNodes(); + REQUIRE( nodes.size() == g->getNodesCount() ); auto c = g->compile(); REQUIRE( c == true ); REQUIRE( g->isCompiled() ); @@ -396,13 +395,13 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { REQUIRE( c == true ); // Simplified graph after compilation - auto cn = g->getNodesByLevel(); + auto& cn = g->getNodesByLevel(); // the source "Validator" is no more in level 0 as it is not reachable from a sink in the // graph. - auto found = std::find_if( ( *cn )[0].begin(), ( *cn )[0].end(), []( const auto& nn ) { + auto found = std::find_if( cn[0].begin(), cn[0].end(), []( const auto& nn ) { return nn->getInstanceName() == "Validator"; } ); - REQUIRE( found == ( *cn )[0].end() ); + REQUIRE( found == cn[0].end() ); // removing the source "Validator" n = g->getNode( "Validator" ); From b4bb1018df7f9f835a61cfe4e2999b7807edd84e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 20 Mar 2023 18:04:47 +0100 Subject: [PATCH 153/239] [tests] fix missing const in dataflow graph test --- tests/unittest/Dataflow/graph.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unittest/Dataflow/graph.cpp b/tests/unittest/Dataflow/graph.cpp index 01f8ebfd0ce..c6d32147583 100644 --- a/tests/unittest/Dataflow/graph.cpp +++ b/tests/unittest/Dataflow/graph.cpp @@ -18,7 +18,7 @@ void inspectGraph( const DataflowGraph& g ) { } // Nodes of the graph - auto& nodes = g.getNodes(); + const auto& nodes = g.getNodes(); std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes.size() << ") :\n"; for ( const auto& n : nodes ) { std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() @@ -375,7 +375,7 @@ TEST_CASE( "Dataflow/Core/Graph", "[Dataflow][Core][Graph]" ) { // Factories used by the graph auto factories = g->getNodeFactories(); REQUIRE( factories != nullptr ); - auto& nodes = g->getNodes(); + const auto& nodes = g->getNodes(); REQUIRE( nodes.size() == g->getNodesCount() ); auto c = g->compile(); REQUIRE( c == true ); From db189f7674f2757c4e500699161a8951ca262aa0 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 20 Mar 2023 18:05:12 +0100 Subject: [PATCH 154/239] [dataflow-qtgui] fix missing const in dataflow graph node access --- src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 84f4526d9b5..6ae75c23be2 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -112,7 +112,7 @@ void GraphEditorView::editGraph( DataflowGraph* g ) { m_dataflowGraph = g; if ( m_dataflowGraph ) { buildAdapterRegistry( NodeFactoriesManager::getFactoryManager() ); - auto& nodes = m_dataflowGraph->getNodes(); + const auto& nodes = m_dataflowGraph->getNodes(); // NodeToUuid mapping std::map nodeToUuid; // inserting nodes From a7a98790da693dc6c1120e2aaf35821d677c50aa Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 21 Mar 2023 09:05:26 +0100 Subject: [PATCH 155/239] [dataflow-core][tests] fix type demangler for __cxx11::basic_string on Linux --- src/Dataflow/Core/TypeDemangler.cpp | 1 + tests/unittest/Dataflow/customnodes.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/Core/TypeDemangler.cpp b/src/Dataflow/Core/TypeDemangler.cpp index 686564ee629..1f6105d6f17 100644 --- a/src/Dataflow/Core/TypeDemangler.cpp +++ b/src/Dataflow/Core/TypeDemangler.cpp @@ -16,6 +16,7 @@ namespace TypeInternal { RA_DATAFLOW_API auto makeTypeReadable( const std::string& fullType ) -> std::string { static std::map knownTypes { { "std::", "" }, + { "__cxx11::", "" }, { ", std::allocator", "" }, { ", std::allocator", "" }, { ", std::allocator", "" }, diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index ba10b04d954..a8d3a0e7e8f 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -308,13 +308,12 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { nlohmann::json emptyData; auto customSource = customFactory->createNode( - "Source, allocator>>", emptyData, nullptr ); + Customs::CustomStringSource::getTypename(), emptyData, nullptr ); REQUIRE( customSource != nullptr ); std::cout << "Created node " << customSource->getInstanceName() << " with type " - << customSource->getTypeName() << "\n"; - - //"Source, allocator>>" + << customSource->getTypeName() << " // " + << Customs::CustomStringSource::getTypename() << "\n"; // build a graph auto g = buildgraph( "testCustomNodes" ); @@ -338,13 +337,14 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { delete g; /// try to load the graph without custom factory - NodeFactoriesManager::unregisterFactory( customFactory->getName() ); + auto unregistered = NodeFactoriesManager::unregisterFactory( customFactory->getName() ); + REQUIRE( unregistered == true ); g = new DataflowGraph( "" ); loaded = g->loadFromJson( tmpdir + "customGraph.json" ); REQUIRE( loaded == false ); - delete g; + std::filesystem::remove_all( tmpdir ); } } From e8b56872395e630a6549cc1f391a569d6d377db4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 24 Mar 2023 08:27:50 +0100 Subject: [PATCH 156/239] [gui] fix missing widget name --- src/Gui/Widgets/ControlPanel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Gui/Widgets/ControlPanel.cpp b/src/Gui/Widgets/ControlPanel.cpp index cca0783f5fd..63fd127068e 100644 --- a/src/Gui/Widgets/ControlPanel.cpp +++ b/src/Gui/Widgets/ControlPanel.cpp @@ -158,7 +158,8 @@ void ControlPanel::addColorInput( Ra::Core::Utils::Color color, bool withAlpha, const std::string& tooltip ) { - auto button = new QPushButton( name.c_str(), this ); + auto button = new QPushButton( name.c_str(), this ); + button->setObjectName( name.c_str() ); auto srgbColor = Ra::Core::Utils::Color::linearRGBTosRGB( color ); auto clrBttn = QColor::fromRgbF( srgbColor[0], srgbColor[1], srgbColor[2], srgbColor[3] ); auto clrDlg = [callback, clrBttn, withAlpha, button, name]() mutable { From c3bd198d414e7c7c5449ed0d830750006f3a8b74 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 24 Mar 2023 08:29:10 +0100 Subject: [PATCH 157/239] [dataflow-core] use find_if instead of custom loop --- src/Dataflow/Core/Node.hpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 66903b60a9d..86fe7e63150 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -341,12 +341,14 @@ inline bool Node::removeOutput( PortBase*& out ) { } inline bool Node::addEditableParameter( EditableParameterBase* editableParameter ) { - bool found = false; - for ( auto& edit : m_editableParameters ) { - if ( edit.get()->getName() == editableParameter->getName() ) { found = true; } + auto edIt = std::find_if( + m_editableParameters.begin(), + m_editableParameters.end(), + [name = editableParameter->getName()]( const auto& p ) { return p->getName() == name; } ); + if ( edIt == m_editableParameters.end() ) { + m_editableParameters.emplace_back( editableParameter ); } - if ( !found ) { m_editableParameters.emplace_back( editableParameter ); } - return !found; + return edIt == m_editableParameters.end(); } inline bool Node::removeEditableParameter( const std::string& name ) { From 269dc91546b9ec2c9a2c9ef8f5194066855c72b9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 24 Mar 2023 08:29:55 +0100 Subject: [PATCH 158/239] [dataflow-qtgui] add widget --- .../QtGui/GraphEditor/NodeAdapterModel.cpp | 3 +- .../QtGui/GraphEditor/WidgetFactory.cpp | 65 ++++++++++++++++--- 2 files changed, 59 insertions(+), 9 deletions(-) diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index 86d46097f0a..098b0020977 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -219,7 +219,8 @@ void updateWidget( Node* node, QWidget* widget ) { auto edtParam = node->getEditableParameters()[i].get(); if ( !WidgetFactory::updateWidget( widget, edtParam ) ) { LOG( Ra::Core::Utils::logWARNING ) - << "NodeAdapterModel : unable to update parameter " << edtParam->getName(); + << "NodeAdapterModel : unable to update parameter " << edtParam->getName() + << " on node " << node->getInstanceName() << " (" << node->getTypeName() << ")"; } } } diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index 3a786b1099e..a59e4007d01 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -50,8 +50,8 @@ QWidget* createWidget( EditableParameterBase* editableParameter ) { else { // TODO, when PR #1027 will be merged, demangle the type name LOG( Ra::Core::Utils::logWARNING ) - << "WidgetFactory : no defined widget builder for hashed type " - << editableParameter->getType().name() << "."; + << "WidgetFactory : no defined widget builder for type " + << simplifiedDemangledType( editableParameter->getType() ) << "."; } return nullptr; } @@ -62,8 +62,8 @@ bool updateWidget( QWidget* widget, EditableParameterBase* editableParameter ) { } else { LOG( Ra::Core::Utils::logWARNING ) - << "WidgetFactory: no defined widget updater for hashed type " - << editableParameter->getType().name() << "."; + << "WidgetFactory: no defined widget updater for type " + << simplifiedDemangledType( editableParameter->getType() ) << "."; } return false; } @@ -124,7 +124,7 @@ void initializeWidgetFactory() { auto editable = dynamic_cast*>( editableParameter ); auto powerSlider = new PowerSlider(); powerSlider->setObjectName( editable->getName().c_str() ); - editable->m_data = 0.0_ra; + // editable->m_data = 0.0_ra; powerSlider->setValue( editable->m_data ); const auto& constraints = editable->getConstraints(); Scalar minValue = 0_ra; @@ -148,7 +148,40 @@ void initializeWidgetFactory() { return false; } } ); + /* + * int edition + */ + WidgetFactory::registerWidget( + []( EditableParameterBase* editableParameter ) { + auto editable = dynamic_cast*>( editableParameter ); + const auto& constraints = editable->getConstraints(); + Scalar minValue = 0_ra; + Scalar maxValue = 1000_ra; + if ( constraints.contains( "min" ) ) { minValue = Scalar( constraints["min"] ); } + if ( constraints.contains( "max" ) ) { maxValue = Scalar( constraints["max"] ); } + auto powerSlider = new PowerSlider(); + powerSlider->setObjectName( editable->getName().c_str() ); + // editable->m_data = 0.0_ra; + powerSlider->setValue( editable->m_data ); + powerSlider->setRange( minValue, maxValue ); + powerSlider->setSingleStep( 1 ); + PowerSlider::connect( powerSlider, + &PowerSlider::valueChanged, + [editable]( Scalar value ) { editable->m_data = int( value ); } ); + return powerSlider; + }, + []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { + auto editable = dynamic_cast*>( editableParameter ); + auto slider = widget->findChild( editableParameter->getName().c_str() ); + if ( slider ) { + slider->setValue( editable->m_data ); + return true; + } + else { + return false; + } + } ); /* * Boolean edition */ @@ -190,19 +223,35 @@ void initializeWidgetFactory() { auto clrCbk = [editable]( const Ra::Core::Utils::Color& clr ) { editable->m_data = clr; }; - controlPanel->addColorInput( "Choose color", clrCbk, editable->m_data, true ); + controlPanel->addColorInput( editable->getName(), clrCbk, editable->m_data, true ); controlPanel->setVisible( true ); return controlPanel; }, []( QWidget* widget, EditableParameterBase* editableParameter ) -> bool { auto editable = dynamic_cast*>( editableParameter ); - auto button = widget->findChild( "Choose color" ); + auto button = widget->findChild( editable->getName().c_str() ); if ( button ) { // todo, update the color on the button and make the dialog to take its // initial color from the button. Once dont, return true ... - return false; + auto srgbColor = Ra::Core::Utils::Color::linearRGBTosRGB( editable->m_data ); + auto clrBttn = + QColor::fromRgbF( srgbColor[0], srgbColor[1], srgbColor[2], srgbColor[3] ); + + auto lum = 0.2126_ra * Scalar( clrBttn.redF() ) + + 0.7151_ra * Scalar( clrBttn.greenF() ) + + 0.0721_ra * Scalar( clrBttn.blueF() ); + QString qss = QString( "background-color: %1" ).arg( clrBttn.name() ); + if ( lum > 1_ra / 3_ra ) { qss += QString( "; color: #000000" ); } + else { + qss += QString( "; color: #FFFFFF" ); + } + button->setStyleSheet( qss ); + return true; } + LOG( Ra::Core::Utils::logWARNING ) + << " Unable to find the button \"Choose color\" for \"" << editable->getName() + << "\" "; return false; } ); From a872237f82c4f42097c0eff3c1e4781b012bec91 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 3 Apr 2023 10:09:48 +0200 Subject: [PATCH 159/239] [dataflow-core] add a todo in Node.hpp --- src/Dataflow/Core/Node.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 86fe7e63150..867644886c5 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -222,6 +222,7 @@ class RA_DATAFLOW_API Node std::vector m_interface; /// The editable parameters of the node + /// \todo replace this by a Ra::Core::VariableSet std::vector> m_editableParameters; /// Additional data on the node, added by application or gui or ... From 27d2e63ab00425b1f81d8b90208159f0bfab09be Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 06:57:36 +0200 Subject: [PATCH 160/239] [dataflow-rendering] remove .inl in the Rendering package --- src/Dataflow/Rendering/CMakeLists.txt | 6 +-- .../Rendering/Renderer/RenderingGraph.hpp | 36 +++++++++++++- .../Rendering/Renderer/RenderingGraph.inl | 47 ------------------- src/Dataflow/Rendering/filelist.cmake | 2 - 4 files changed, 37 insertions(+), 54 deletions(-) delete mode 100644 src/Dataflow/Rendering/Renderer/RenderingGraph.inl diff --git a/src/Dataflow/Rendering/CMakeLists.txt b/src/Dataflow/Rendering/CMakeLists.txt index ee2a6f5d739..c5e2b13f0e1 100644 --- a/src/Dataflow/Rendering/CMakeLists.txt +++ b/src/Dataflow/Rendering/CMakeLists.txt @@ -7,8 +7,8 @@ include(filelist.cmake) # configure library add_library( - ${ra_dataflowrendering_target} SHARED - ${dataflow_rendering_sources} ${dataflow_rendering_headers} ${dataflow_rendering_inlines} + ${ra_dataflowrendering_target} SHARED ${dataflow_rendering_sources} + ${dataflow_rendering_headers} ) # This one should be extracted directly from parent project properties. @@ -24,7 +24,7 @@ configure_radium_target(${ra_dataflowrendering_target}) # configure the library only. The package is a sub-package and should be configured independently configure_radium_library( TARGET ${ra_dataflowrendering_target} COMPONENT TARGET_DIR "Dataflow/Rendering" - FILES "${dataflow_rendering_headers};${dataflow_rendering_inlines}" + FILES "${dataflow_rendering_headers}" ) # Generate cmake package configure_radium_package( diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index 9a0afe064fe..96824f12348 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -73,9 +73,41 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph std::vector m_outputTextures; }; +inline RenderingGraph::RenderingGraph( const std::string& name ) : + DataflowGraph( name, getTypename() ) {} + +inline const std::string& RenderingGraph::getTypename() { + static std::string demangledTypeName { "Rendering Graph" }; + return demangledTypeName; +} + +inline void RenderingGraph::resize( uint32_t width, uint32_t height ) { + for ( auto rn : m_renderingNodes ) { + rn->resize( width, height ); + } +} + +inline void RenderingGraph::setDataSources( std::vector* ros, + std::vector* lights ) { +#if 0 + for(auto sn : m_dataProviders) { + sn->setScene(ros, lights); + } +#endif +} + +inline void RenderingGraph::setCameras( std::vector* cameras ) { +#if 0 + for(auto sn : m_dataProviders) { + sn->setCameras(cameras); + } +#endif +} + +inline void RenderingGraph::fromJsonInternal( const nlohmann::json& ) {} +inline void RenderingGraph::toJsonInternal( nlohmann::json& ) const {} + } // namespace Renderer } // namespace Rendering } // namespace Dataflow } // namespace Ra - -#include diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl b/src/Dataflow/Rendering/Renderer/RenderingGraph.inl deleted file mode 100644 index d9229568b38..00000000000 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include - -namespace Ra { -namespace Dataflow { -namespace Rendering { -namespace Renderer { - -inline RenderingGraph::RenderingGraph( const std::string& name ) : - DataflowGraph( name, getTypename() ) {} - -inline const std::string& RenderingGraph::getTypename() { - static std::string demangledTypeName { "Rendering Graph" }; - return demangledTypeName; -} - -inline void RenderingGraph::resize( uint32_t width, uint32_t height ) { - for ( auto rn : m_renderingNodes ) { - rn->resize( width, height ); - } -} - -inline void RenderingGraph::setDataSources( std::vector* ros, - std::vector* lights ) { -#if 0 - for(auto sn : m_dataProviders) { - sn->setScene(ros, lights); - } -#endif -} - -inline void RenderingGraph::setCameras( std::vector* cameras ) { -#if 0 - for(auto sn : m_dataProviders) { - sn->setCameras(cameras); - } -#endif -} - -inline void RenderingGraph::fromJsonInternal( const nlohmann::json& ) {} -inline void RenderingGraph::toJsonInternal( nlohmann::json& ) const {} - -} // namespace Renderer -} // namespace Rendering -} // namespace Dataflow -} // namespace Ra diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 79e584a43d0..7c05dd525ed 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -7,5 +7,3 @@ set(dataflow_rendering_sources Renderer/DataflowRenderer.cpp Renderer/RenderingG set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/DataflowRenderer.hpp Renderer/RenderingGraph.hpp ) - -set(dataflow_rendering_inlines Renderer/RenderingGraph.inl) From 924377b3a9546b512947e427cb7cd981cd41bab8 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 10:35:03 +0200 Subject: [PATCH 161/239] [core] fix merge of Radium PR #1037 --- src/Core/filelist.cmake | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/src/Core/filelist.cmake b/src/Core/filelist.cmake index 7397b4bafc8..4760bf220f9 100644 --- a/src/Core/filelist.cmake +++ b/src/Core/filelist.cmake @@ -138,38 +138,3 @@ set(core_headers Utils/TypesUtils.hpp Utils/Version.hpp ) - -set(core_inlines - Animation/HandleArray.inl - Animation/Sequence.inl - Asset/AnimationData.inl - Asset/BlinnPhongMaterialData.inl - Asset/Camera.inl - Asset/FileData.inl - Asset/GeometryData.inl - Asset/HandleData.inl - Asset/LightData.inl - Asset/MaterialData.inl - Containers/AdjacencyList.inl - Containers/Grid.inl - Containers/Tex.inl - Geometry/Curve2D.inl - Geometry/DistanceQueries.inl - Geometry/IndexedGeometry.inl - Geometry/MeshPrimitives.inl - Geometry/PolyLine.inl - Geometry/Spline.inl - Geometry/TopologicalMesh.inl - Geometry/TriangleMesh.inl - Geometry/deprecated/TopologicalMesh.inl - Math/DualQuaternion.inl - Math/LinearAlgebra.inl - Math/Math.inl - Math/Quadric.inl - Random/RandomPointSet.inl - Utils/Attribs.inl - Utils/BijectiveAssociation.inl - Utils/CircularIndex.inl - Utils/Index.inl - Utils/IndexMap.inl -) From c7f9167187b4b6f4106a3d859af608b9cd12c93f Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 12:50:12 +0200 Subject: [PATCH 162/239] [engine] fix envmap computation when Scalar = double --- src/Engine/Data/EnvironmentTexture.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Engine/Data/EnvironmentTexture.cpp b/src/Engine/Data/EnvironmentTexture.cpp index 3fda194fe83..d542b2ceb6a 100644 --- a/src/Engine/Data/EnvironmentTexture.cpp +++ b/src/Engine/Data/EnvironmentTexture.cpp @@ -425,13 +425,14 @@ void EnvironmentTexture::setupTexturesFromSphericalEquiRectangular() { Scalar v = -1 + j * duv; Vector3 d = bases[imgIdx][0] + u * bases[imgIdx][1] + v * bases[imgIdx][2]; d = d.normalized(); - Vector2 st { w * sphericalPhi( d ) / ( 2 * M_PI ), h * sphericalTheta( d ) / M_PI }; + Vector2 st { w * sphericalPhi( d ) / ( 2_ra * M_PI ), + h * sphericalTheta( d ) / M_PI }; // TODO : use st to access and filter the original envmap // for now, no filtering is done. (eq to GL_NEAREST) - int s = int( st.x() ); - int t = int( st.y() ); - int cu = int( ( u / 2 + 0.5 ) * textureSize ); - int cv = int( ( v / 2 + 0.5 ) * textureSize ); + int s = std::min( int( st.x() ), w - 1 ); + int t = std::min( int( st.y() ), h - 1 ); + int cu = int( ( u / 2_ra + 0.5_ra ) * textureSize ); + int cv = int( ( v / 2_ra + 0.5_ra ) * textureSize ); m_skyData[imgIdx][4 * ( cv * textureSize + cu ) + 0] = latlonPix[4 * ( t * w + s ) + 0]; From 8cfb0309cb106d0eb478f765f6e40e7ca3c0868f Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 12:54:40 +0200 Subject: [PATCH 163/239] [core][tests] fix Random sequences when Scalar = double --- src/Core/Random/RandomPointSet.cpp | 4 +-- src/Core/Random/RandomPointSet.hpp | 2 +- tests/unittest/Core/random.cpp | 51 ++++++++++++++++++------------ 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/src/Core/Random/RandomPointSet.cpp b/src/Core/Random/RandomPointSet.cpp index c375138d9e4..1529c8de6d9 100644 --- a/src/Core/Random/RandomPointSet.cpp +++ b/src/Core/Random/RandomPointSet.cpp @@ -19,7 +19,7 @@ Scalar VanDerCorputSequence::operator()( unsigned int bits ) { bits = ( ( bits & 0x33333333u ) << 2u ) | ( ( bits & 0xCCCCCCCCu ) >> 2u ); bits = ( ( bits & 0x0F0F0F0Fu ) << 4u ) | ( ( bits & 0xF0F0F0F0u ) >> 4u ); bits = ( ( bits & 0x00FF00FFu ) << 8u ) | ( ( bits & 0xFF00FF00u ) >> 8u ); - return Scalar( float( bits ) * 2.3283064365386963e-10 ); // / 0x100000000 + return Scalar( float( bits ) * 2.3283064365386963e-10_ra ); // / 0x100000000 } FibonacciPointSet::FibonacciPointSet( size_t n ) : seq( n ) {} @@ -42,7 +42,7 @@ Ra::Core::Vector2 HammersleyPointSet::operator()( size_t i ) { } MersenneTwisterPointSet::MersenneTwisterPointSet( size_t number ) : - gen( 0 ), seq( 0., 1. ), n( number ) {} + gen( 0 ), seq( 0._ra, 1._ra ), n( number ) {} size_t MersenneTwisterPointSet::range() { return n; diff --git a/src/Core/Random/RandomPointSet.hpp b/src/Core/Random/RandomPointSet.hpp index 6d4c39a8a44..7f26f89f31b 100644 --- a/src/Core/Random/RandomPointSet.hpp +++ b/src/Core/Random/RandomPointSet.hpp @@ -108,7 +108,7 @@ class RA_CORE_API HammersleyPointSet class RA_CORE_API MersenneTwisterPointSet { std::mt19937 gen; - std::uniform_real_distribution<> seq; + std::uniform_real_distribution seq; size_t n; public: diff --git a/tests/unittest/Core/random.cpp b/tests/unittest/Core/random.cpp index 27383ea05b3..2c302457f8e 100644 --- a/tests/unittest/Core/random.cpp +++ b/tests/unittest/Core/random.cpp @@ -10,10 +10,10 @@ using namespace Ra::Core::Random; TEST_CASE( "Core/Random/RandomPointSet", "[Core][Core/Random][PointSet]" ) { SECTION( "Fibonacci sequence" ) { std::array fib_verif { 0_ra, - 0.61803400516510009765625_ra, - 1.2360680103302001953125_ra, - 1.8541018962860107421875_ra, - 2.472136020660400390625_ra }; + 0.61803398874989479150343640867504_ra, + 1.2360679774997895830068728173501_ra, + 1.8541019662496844855326116885408_ra, + 2.4721359549995791660137456347002_ra }; FibonacciSequence fib { 2 }; // Our fibonacci sequence is only defined for more than 5 points REQUIRE( fib.range() == 5 ); @@ -33,10 +33,10 @@ TEST_CASE( "Core/Random/RandomPointSet", "[Core][Core/Random][PointSet]" ) { SECTION( "Fibonacci point set" ) { std::array, 5> fibseq_verif { std::pair { 0_ra, 0_ra / 5_ra }, - { 0.61803400516510009765625_ra, 1_ra / 5_ra }, - { 1.2360680103302001953125_ra, 2_ra / 5_ra }, - { 1.8541018962860107421875_ra, 3_ra / 5_ra }, - { 2.472136020660400390625_ra, 4_ra / 5_ra } }; + { 0.61803398874989479150343640867504_ra, 1_ra / 5_ra }, + { 1.2360679774997895830068728173501_ra, 2_ra / 5_ra }, + { 1.8541019662496844855326116885408_ra, 3_ra / 5_ra }, + { 2.4721359549995791660137456347002_ra, 4_ra / 5_ra } }; FibonacciPointSet fibs { 5 }; for ( size_t i = 0; i < 5; ++i ) { auto v = fibs( i ); @@ -62,15 +62,26 @@ TEST_CASE( "Core/Random/RandomPointSet", "[Core][Core/Random][PointSet]" ) { // todo, verify if the sequence is always the same (it should) on any systems/run SECTION( "MersenneTwister point set" ) { +#ifdef CORE_USE_DOUBLE + // Sequence valid only when Scalar == double std::array, 5> seq_verif { - std::pair { 0.59284460544586181640625_ra, - 0.844265758991241455078125_ra }, - { 0.857945621013641357421875_ra, 0.847251713275909423828125_ra }, - { 0.623563706874847412109375_ra, 0.384381711483001708984375_ra }, - { 0.2975346148014068603515625_ra, 0.056712977588176727294921875_ra }, - { 0.2726562917232513427734375_ra, 0.477665126323699951171875_ra } }; + std::pair { 0.59284461651668263204584263803554_ra, + 0.84426574425659828282419994138763_ra }, + { 0.85794561998982987738315841852454_ra, 0.84725173738433123826752080276492_ra }, + { 0.62356369649610832173181051985011_ra, 0.38438170837375662536317122430773_ra }, + { 0.29753460535723419422282631785492_ra, 0.056712975933163663200264892338964_ra }, + { 0.27265629474158931122573790162278_ra, 0.47766511174464632016878340436961_ra } }; +#else + // Sequence valid only when Scalar == float + std::array, 5> seq_verif { + std::pair { 0.548813521862030029296875_ra, + 0.59284460544586181640625_ra }, + { 0.71518933773040771484375_ra, 0.844265758991241455078125_ra }, + { 0.602763354778289794921875_ra, 0.857945621013641357421875_ra }, + { 0.544883191585540771484375_ra, 0.847251713275909423828125_ra }, + { 0.4236547946929931640625_ra, 0.623563706874847412109375_ra } }; +#endif MersenneTwisterPointSet seq { 5 }; - std::cout << std::setprecision( 32 ); for ( size_t i = 0; i < 5; ++i ) { auto v = seq( i ); REQUIRE( isApprox( v[0], seq_verif[i].first ) ); @@ -80,11 +91,11 @@ TEST_CASE( "Core/Random/RandomPointSet", "[Core][Core/Random][PointSet]" ) { SECTION( "SphericalPointSet point set (Hammersley)" ) { std::array, 5> seq_verif { - std::pair { 1.5099580252808664226904511451721e-07_ra, 0_ra }, - { 0.3090169727802276611328125_ra, 0.951056540012359619140625_ra }, - { -0.700629293918609619140625_ra, 0.50903689861297607421875_ra }, - { -0.700629055500030517578125_ra, -0.509037196636199951171875_ra }, - { 0.204395592212677001953125_ra, -0.62906467914581298828125_ra } }; + std::pair { 1.2246467991473532071737640294584e-16_ra, 0_ra }, + { 0.30901699437494745126286943559535_ra, 0.95105651629515353118193843329209_ra }, + { -0.70062926922203661028731858095853_ra, 0.50903696045512725198989301134134_ra }, + { -0.70062926922203672130962104347418_ra, -0.50903696045512702994528808631003_ra }, + { 0.20439552950218897731105016646325_ra, -0.62906475622110624712490789534058_ra } }; SphericalPointSet seq { 5 }; for ( size_t i = 0; i < 5; ++i ) { auto v = seq( i ); From ba58eb6b3181c7592b968744b428f1bc0e5d20d8 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 12:55:31 +0200 Subject: [PATCH 164/239] [dataflow-rendering] fix codacy for unused component --- src/Dataflow/Rendering/Renderer/RenderingGraph.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index 96824f12348..06fbab96038 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -55,7 +55,7 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph private: /// The renderer's shader program manager - Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; + Ra::Engine::Data::ShaderProgramManager* m_shaderMngr { nullptr }; /// List of nodes that requires some particular processing std::vector m_renderingNodes; // to resize std::vector m_rtIndexedNodes; // associate an index and buildRenderTechnique From 94e6b98d2302340649f2d2d537297562467db9d3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 12:56:38 +0200 Subject: [PATCH 165/239] [tests] fix random sequence when Scalar = double --- tests/unittest/Dataflow/customnodes.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index a8d3a0e7e8f..606405a123b 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -197,12 +197,12 @@ TEST_CASE( "Dataflow/Core/Custom nodes", "[Dataflow][Core][Custom nodes]" ) { auto generatedOperator = g->getDataGetter( "nm_from" ); REQUIRE( generatedOperator != nullptr ); - // parameterise the graph + // parameterize the graph using CollectionType = Ra::Core::VectorArray; CollectionType testVector; testVector.reserve( 10 ); std::mt19937 gen( 0 ); - std::uniform_real_distribution<> dis( 0.0, 1.0 ); + std::uniform_real_distribution dis( 0.0_ra, 1.0_ra ); // Fill the vector with random numbers between 0 and 1 for ( size_t n = 0; n < testVector.capacity(); ++n ) { testVector.push_back( dis( gen ) ); From 26941c140452c733f504c3c3ee32896746b0bdbd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 16:15:17 +0000 Subject: [PATCH 166/239] [general] format with new guidelines --- .../FunctionalsGraph/main.cpp | 8 ++--- src/Core/Random/RandomPointSet.hpp | 20 +++++------ src/Dataflow/Core/DataflowGraph.cpp | 8 ++--- src/Dataflow/Core/EditableParameter.hpp | 8 ++--- src/Dataflow/Core/Enumerator.hpp | 4 +-- src/Dataflow/Core/Node.hpp | 4 +-- src/Dataflow/Core/NodeFactory.hpp | 4 +-- .../Core/Nodes/Sources/FunctionSource.hpp | 4 +-- src/Dataflow/Core/Port.hpp | 12 +++---- .../QtGui/GraphEditor/GraphEditorView.cpp | 4 +-- .../QtGui/GraphEditor/GraphEditorWindow.cpp | 32 +++++------------ .../QtGui/GraphEditor/NodeAdapterModel.cpp | 12 ++----- .../QtGui/GraphEditor/NodeAdapterModel.hpp | 8 ++--- .../QtGui/GraphEditor/WidgetFactory.cpp | 36 +++++-------------- .../Rendering/Renderer/DataflowRenderer.cpp | 8 ++--- .../Rendering/Renderer/DataflowRenderer.hpp | 8 ++--- tests/unittest/Dataflow/customnodes.cpp | 8 ++--- 17 files changed, 62 insertions(+), 126 deletions(-) diff --git a/examples/DataflowExamples/FunctionalsGraph/main.cpp b/examples/DataflowExamples/FunctionalsGraph/main.cpp index a0e2d80642b..9d26bd23f0e 100644 --- a/examples/DataflowExamples/FunctionalsGraph/main.cpp +++ b/examples/DataflowExamples/FunctionalsGraph/main.cpp @@ -123,9 +123,7 @@ int main( int argc, char* argv[] ) { } } if ( execOK ) { std::cout << "OK\n"; } - else { - std::cout << "NOK\n"; - } + else { std::cout << "NOK\n"; } //! [Verify the result] //! [Modifying the graph to add link to map operator] @@ -155,9 +153,7 @@ int main( int argc, char* argv[] ) { } } if ( execOK ) { std::cout << "OK\n"; } - else { - std::cout << "NOK\n"; - } + else { std::cout << "NOK\n"; } //! [Execute and test the result] return 0; diff --git a/src/Core/Random/RandomPointSet.hpp b/src/Core/Random/RandomPointSet.hpp index 7f26f89f31b..d79f620f98c 100644 --- a/src/Core/Random/RandomPointSet.hpp +++ b/src/Core/Random/RandomPointSet.hpp @@ -41,10 +41,10 @@ class RA_CORE_API FibonacciSequence public: explicit FibonacciSequence( size_t number ); // copyable - FibonacciSequence( const FibonacciSequence& ) = default; + FibonacciSequence( const FibonacciSequence& ) = default; FibonacciSequence& operator=( const FibonacciSequence& ) = default; // movable - FibonacciSequence( FibonacciSequence&& ) = default; + FibonacciSequence( FibonacciSequence&& ) = default; FibonacciSequence& operator=( FibonacciSequence&& ) = default; virtual ~FibonacciSequence() = default; @@ -70,10 +70,10 @@ class RA_CORE_API FibonacciPointSet public: explicit FibonacciPointSet( size_t n ); // copyable - FibonacciPointSet( const FibonacciPointSet& ) = default; + FibonacciPointSet( const FibonacciPointSet& ) = default; FibonacciPointSet& operator=( const FibonacciPointSet& ) = default; // movable - FibonacciPointSet( FibonacciPointSet&& ) = default; + FibonacciPointSet( FibonacciPointSet&& ) = default; FibonacciPointSet& operator=( FibonacciPointSet&& ) = default; virtual ~FibonacciPointSet() = default; @@ -92,10 +92,10 @@ class RA_CORE_API HammersleyPointSet public: explicit HammersleyPointSet( size_t number ); // copyable - HammersleyPointSet( const HammersleyPointSet& ) = default; + HammersleyPointSet( const HammersleyPointSet& ) = default; HammersleyPointSet& operator=( const HammersleyPointSet& ) = default; // movable - HammersleyPointSet( HammersleyPointSet&& ) = default; + HammersleyPointSet( HammersleyPointSet&& ) = default; HammersleyPointSet& operator=( HammersleyPointSet&& ) = default; virtual ~HammersleyPointSet() = default; @@ -114,10 +114,10 @@ class RA_CORE_API MersenneTwisterPointSet public: explicit MersenneTwisterPointSet( size_t number ); // copyable - MersenneTwisterPointSet( const MersenneTwisterPointSet& ) = default; + MersenneTwisterPointSet( const MersenneTwisterPointSet& ) = default; MersenneTwisterPointSet& operator=( const MersenneTwisterPointSet& ) = default; // movable - MersenneTwisterPointSet( MersenneTwisterPointSet&& ) = default; + MersenneTwisterPointSet( MersenneTwisterPointSet&& ) = default; MersenneTwisterPointSet& operator=( MersenneTwisterPointSet&& ) = default; virtual ~MersenneTwisterPointSet() = default; @@ -139,10 +139,10 @@ class SphericalPointSet public: explicit SphericalPointSet( size_t n ); // copyable - SphericalPointSet( const SphericalPointSet& ) = default; + SphericalPointSet( const SphericalPointSet& ) = default; SphericalPointSet& operator=( const SphericalPointSet& ) = default; // movable - SphericalPointSet( SphericalPointSet&& ) = default; + SphericalPointSet( SphericalPointSet&& ) = default; SphericalPointSet& operator=( SphericalPointSet&& ) = default; virtual ~SphericalPointSet() = default; diff --git a/src/Dataflow/Core/DataflowGraph.cpp b/src/Dataflow/Core/DataflowGraph.cpp index cfec19c4932..5a39fe1185c 100644 --- a/src/Dataflow/Core/DataflowGraph.cpp +++ b/src/Dataflow/Core/DataflowGraph.cpp @@ -265,9 +265,7 @@ std::pair DataflowGraph::addNode( std::unique_ptr newNode ) { m_shouldBeSaved = true; return { true, addedNode }; } - else { - return { false, newNode.release() }; - } + else { return { false, newNode.release() }; } } bool DataflowGraph::removeNode( Node*& node ) { @@ -405,9 +403,7 @@ int DataflowGraph::findNode( const Node* node ) const { auto foundIt = std::find_if( m_nodes.begin(), m_nodes.end(), [node]( const auto& p ) { return *p == *node; } ); if ( foundIt != m_nodes.end() ) { return std::distance( m_nodes.begin(), foundIt ); } - else { - return -1; - } + else { return -1; } } bool DataflowGraph::compile() { diff --git a/src/Dataflow/Core/EditableParameter.hpp b/src/Dataflow/Core/EditableParameter.hpp index b34319c39d5..ea6b5ac1737 100644 --- a/src/Dataflow/Core/EditableParameter.hpp +++ b/src/Dataflow/Core/EditableParameter.hpp @@ -23,8 +23,8 @@ struct RA_DATAFLOW_API EditableParameterBase { /// \name Constructors /// @{ /// \brief delete default constructors. - EditableParameterBase() = delete; - EditableParameterBase( const EditableParameterBase& ) = delete; + EditableParameterBase() = delete; + EditableParameterBase( const EditableParameterBase& ) = delete; EditableParameterBase& operator=( const EditableParameterBase& ) = delete; /// Construct an base editable parameter from its name and type hash @@ -53,8 +53,8 @@ struct EditableParameter : public EditableParameterBase { /// \name Constructors /// @{ /// \brief delete default constructors. - EditableParameter() = delete; - EditableParameter( const EditableParameter& ) = delete; + EditableParameter() = delete; + EditableParameter( const EditableParameter& ) = delete; EditableParameter& operator=( const EditableParameter& ) = delete; /// Construct an editable parameter from its name and type hash diff --git a/src/Dataflow/Core/Enumerator.hpp b/src/Dataflow/Core/Enumerator.hpp index 2b9c1ea570b..f8e1d8446a1 100644 --- a/src/Dataflow/Core/Enumerator.hpp +++ b/src/Dataflow/Core/Enumerator.hpp @@ -60,9 +60,7 @@ bool Enumerator::set( size_t p ) { this->notify( *this ); return true; } - else { - return false; - } + else { return false; } } template diff --git a/src/Dataflow/Core/Node.hpp b/src/Dataflow/Core/Node.hpp index 867644886c5..ecd2e3a014f 100644 --- a/src/Dataflow/Core/Node.hpp +++ b/src/Dataflow/Core/Node.hpp @@ -35,8 +35,8 @@ class RA_DATAFLOW_API Node /// @{ /// \brief delete default constructors. /// \see https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual - Node() = delete; - Node( const Node& ) = delete; + Node() = delete; + Node( const Node& ) = delete; Node& operator=( const Node& ) = delete; /// @} diff --git a/src/Dataflow/Core/NodeFactory.hpp b/src/Dataflow/Core/NodeFactory.hpp index 3d4d979fdc7..82897987ce6 100644 --- a/src/Dataflow/Core/NodeFactory.hpp +++ b/src/Dataflow/Core/NodeFactory.hpp @@ -266,9 +266,7 @@ auto NodeFactory::registerNodeCreator( const std::string& instanceNamePrefix, [this, instanceNamePrefix]( const nlohmann::json& data ) { std::string instanceName; if ( data.contains( "instance" ) ) { instanceName = data["instance"]; } - else { - instanceName = instanceNamePrefix + std::to_string( this->nextNodeId() ); - } + else { instanceName = instanceNamePrefix + std::to_string( this->nextNodeId() ); } auto node = new T( instanceName ); node->fromJson( data ); return node; diff --git a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp index f608563334b..1d99e2331a6 100644 --- a/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp +++ b/src/Dataflow/Core/Nodes/Sources/FunctionSource.hpp @@ -71,9 +71,7 @@ template bool FunctionSourceNode::execute() { auto interfacePort = static_cast*>( m_interface[0] ); if ( interfacePort->isLinked() ) { m_data = &( interfacePort->getData() ); } - else { - m_data = &m_localData; - } + else { m_data = &m_localData; } m_portOut->setData( m_data ); return true; } diff --git a/src/Dataflow/Core/Port.hpp b/src/Dataflow/Core/Port.hpp index b3c7d3e1b9b..e2b8e4039ef 100644 --- a/src/Dataflow/Core/Port.hpp +++ b/src/Dataflow/Core/Port.hpp @@ -45,8 +45,8 @@ class RA_DATAFLOW_API PortBase /// \brief delete default constructors. /// \see https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual /// Constructors. - PortBase() = delete; - PortBase( const PortBase& ) = delete; + PortBase() = delete; + PortBase( const PortBase& ) = delete; PortBase& operator=( const PortBase& ) = delete; /// @param name The name of the port. @@ -120,8 +120,8 @@ class PortOut : public PortBase /// \name Constructors /// @{ /// \brief delete default constructors. - PortOut() = delete; - PortOut( const PortOut& ) = delete; + PortOut() = delete; + PortOut( const PortOut& ) = delete; PortOut& operator=( const PortOut& ) = delete; /// Constructor. /// @param name The name of the port. @@ -170,8 +170,8 @@ class PortIn : public PortBase, /// \name Constructors /// @{ /// \brief delete default constructors. - PortIn() = delete; - PortIn( const PortIn& ) = delete; + PortIn() = delete; + PortIn( const PortIn& ) = delete; PortIn& operator=( const PortIn& ) = delete; /// Constructor. /// @param name The name of the port. diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp index 6ae75c23be2..34e9aebaca5 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorView.cpp @@ -141,9 +141,7 @@ void GraphEditorView::editGraph( DataflowGraph* g ) { } scene->setSceneName( m_dataflowGraph->getInstanceName().c_str() ); } - else { - scene->setSceneName( "untitled" ); - } + else { scene->setSceneName( "untitled" ); } scene->iterateOverNodes( []( QtNodes::Node* n ) { n->onNodeSizeUpdated(); } ); // view->fitInView( view->sceneRect(), Qt::KeepAspectRatio); diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index 9d2d4031edf..261b0a3d60e 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -14,13 +14,9 @@ GraphEditorWindow::~GraphEditorWindow() { if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( true ); } delete m_graphEdit; if ( !m_ownGraph ) { m_graph->setNodesAndLinksProtection( graphProtection ); } - else { - delete m_graph; - } - } - else { - delete m_graphEdit; + else { delete m_graph; } } + else { delete m_graphEdit; } } GraphEditorWindow::GraphEditorWindow( DataflowGraph* graph ) : @@ -42,9 +38,7 @@ GraphEditorWindow::GraphEditorWindow( DataflowGraph* graph ) : setCurrentFile( QString() ); setUnifiedTitleAndToolBarOnMac( true ); if ( m_ownGraph ) { newFile(); } - else { - m_graphEdit->editGraph( m_graph ); - } + else { m_graphEdit->editGraph( m_graph ); } m_graphEdit->show(); } @@ -66,9 +60,7 @@ void GraphEditorWindow::closeEvent( QCloseEvent* event ) { event->accept(); if ( !m_ownGraph ) { deleteLater(); } } - else { - event->ignore(); - } + else { event->ignore(); } } void GraphEditorWindow::newFile() { @@ -79,9 +71,7 @@ void GraphEditorWindow::newFile() { delete m_graph; m_graph = new DataflowGraph( "untitled.flow" ); } - else { - m_graph->destroy(); - } + else { m_graph->destroy(); } setCurrentFile( "" ); m_graphEdit->editGraph( m_graph ); @@ -97,9 +87,7 @@ void GraphEditorWindow::open() { bool GraphEditorWindow::save() { if ( m_curFile.isEmpty() ) { return saveAs(); } - else { - return saveFile( m_curFile ); - } + else { return saveFile( m_curFile ); } } bool GraphEditorWindow::saveAs() { @@ -192,14 +180,10 @@ void GraphEditorWindow::readSettings() { move( ( availableGeometry.width() - width() ) / 2, ( availableGeometry.height() - height() ) / 2 ); } - else { - restoreGeometry( geometry ); - } + else { restoreGeometry( geometry ); } const QByteArray graphGeometry = settings.value( "graph", QByteArray() ).toByteArray(); if ( graphGeometry.isEmpty() ) { m_graphEdit->resize( 800, 600 ); } - else { - m_graphEdit->restoreGeometry( graphGeometry ); - } + else { m_graphEdit->restoreGeometry( graphGeometry ); } settings.endGroup(); } diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp index 098b0020977..7ceb27eb1b4 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.cpp @@ -69,9 +69,7 @@ void NodeAdapterModel::checkConnections() const { std::string( std::to_string( errors ) + " mandatory ports are not linked (*)." ) .c_str() ); } - else { - m_validationError = "1 mandatory port is not linked (*)."; - } + else { m_validationError = "1 mandatory port is not linked (*)."; } m_validationState = QtNodes::NodeValidationState::Error; } @@ -238,9 +236,7 @@ void QJsonEntryToNlohmannEntry( const QString& key, // TODO find a better way to do that ... // type is a specific entry of envmapdatasource if ( key.compare( "type" ) == 0 ) { data[key.toStdString()] = int( value.toDouble() ); } - else { - data[key.toStdString()] = Scalar( value.toDouble() ); - } + else { data[key.toStdString()] = Scalar( value.toDouble() ); } break; case QJsonValue::String: @@ -313,9 +309,7 @@ void QJsonObjectToNlohmannObject( const QJsonObject& p, nlohmann::json& data ) { QJsonObjectToNlohmannObject( value.toObject(), j ); data[key.toStdString()] = j; } - else { - QJsonEntryToNlohmannEntry( key, value, data ); - } + else { QJsonEntryToNlohmannEntry( key, value, data ); } } } diff --git a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp index b25c3a50a80..d21d0781151 100644 --- a/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp +++ b/src/Dataflow/QtGui/GraphEditor/NodeAdapterModel.hpp @@ -20,11 +20,11 @@ class RA_DATAFLOW_API NodeAdapterModel : public QtNodes::NodeDataModel { public: NodeAdapterModel( DataflowGraph* graph, Node* n ); - NodeAdapterModel() = delete; - NodeAdapterModel( const NodeAdapterModel& ) = delete; - NodeAdapterModel( NodeAdapterModel&& ) = delete; + NodeAdapterModel() = delete; + NodeAdapterModel( const NodeAdapterModel& ) = delete; + NodeAdapterModel( NodeAdapterModel&& ) = delete; NodeAdapterModel& operator=( const NodeAdapterModel& ) = delete; - NodeAdapterModel& operator=( NodeAdapterModel&& ) = delete; + NodeAdapterModel& operator=( NodeAdapterModel&& ) = delete; ~NodeAdapterModel() override; public: diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index a59e4007d01..cac3d862345 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -111,9 +111,7 @@ void initializeWidgetFactory() { slider->setValue( editable->m_data->getStrength() * 100. ); return true; } - else { - return slider != nullptr; - } + else { return slider != nullptr; } } ); /* @@ -144,9 +142,7 @@ void initializeWidgetFactory() { slider->setValue( editable->m_data ); return true; } - else { - return false; - } + else { return false; } } ); /* * int edition @@ -178,9 +174,7 @@ void initializeWidgetFactory() { slider->setValue( editable->m_data ); return true; } - else { - return false; - } + else { return false; } } ); /* * Boolean edition @@ -194,9 +188,7 @@ void initializeWidgetFactory() { : Qt::CheckState::Unchecked ); QCheckBox::connect( checkBox, &QCheckBox::stateChanged, [editable]( int state ) { if ( state == Qt::Unchecked ) { editable->m_data = false; } - else if ( state == Qt::Checked ) { - editable->m_data = true; - } + else if ( state == Qt::Checked ) { editable->m_data = true; } } ); return checkBox; }, @@ -207,9 +199,7 @@ void initializeWidgetFactory() { checkBox->setCheckState( editable->m_data ? Qt::Checked : Qt::Unchecked ); return true; } - else { - return false; - } + else { return false; } } ); /* @@ -243,9 +233,7 @@ void initializeWidgetFactory() { 0.0721_ra * Scalar( clrBttn.blueF() ); QString qss = QString( "background-color: %1" ).arg( clrBttn.name() ); if ( lum > 1_ra / 3_ra ) { qss += QString( "; color: #000000" ); } - else { - qss += QString( "; color: #FFFFFF" ); - } + else { qss += QString( "; color: #FFFFFF" ); } button->setStyleSheet( qss ); return true; } @@ -282,9 +270,7 @@ void initializeWidgetFactory() { auto editable = dynamic_cast*>( editableParameter ); auto button = widget->findChild( editable->getName().c_str() ); if ( button ) { return true; } - else { - return false; - } + else { return false; } } ); #if HAS_TRANSFER_FUNCTION @@ -349,9 +335,7 @@ void initializeWidgetFactory() { comboBox->setCurrentText( editable->m_data.get().c_str() ); return true; } - else { - return false; - } + else { return false; } } ); /* @@ -374,9 +358,7 @@ void initializeWidgetFactory() { line->setText( editable->m_data.c_str() ); return true; } - else { - return false; - } + else { return false; } } ); /* * Ra::Engine::Data::ShaderConfiguration --> Code Editor diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp index 39e94ae2375..87957cfac71 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp @@ -107,9 +107,7 @@ bool DataflowRenderer::buildRenderTechnique( Ra::Engine::Rendering::RenderObject m_controller.m_renderGraph->buildRenderTechnique( ro ); return true; } - else { - return false; - } + else { return false; } } void DataflowRenderer::initResources() { @@ -204,9 +202,7 @@ void DataflowRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters // The first image is the "beauty" channel, set the color texture to this m_colorTexture = images[0]; } - else { - m_colorTexture = nullptr; - } + else { m_colorTexture = nullptr; } } void DataflowRenderer::postProcessInternal( const Ra::Engine::Data::ViewingParameters& ) { diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp index fe2a637137f..929a192d82a 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp @@ -63,10 +63,10 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer struct RA_DATAFLOW_API RenderGraphController : Ra::Core::Resources::ObservableVoid { RenderGraphController(); - virtual ~RenderGraphController() = default; - RenderGraphController( const RenderGraphController& ) = delete; - RenderGraphController( const RenderGraphController&& ) = delete; - RenderGraphController& operator=( RenderGraphController&& ) = delete; + virtual ~RenderGraphController() = default; + RenderGraphController( const RenderGraphController& ) = delete; + RenderGraphController( const RenderGraphController&& ) = delete; + RenderGraphController& operator=( RenderGraphController&& ) = delete; RenderGraphController& operator=( const RenderGraphController& ) = delete; /// Configuration function. diff --git a/tests/unittest/Dataflow/customnodes.cpp b/tests/unittest/Dataflow/customnodes.cpp index 606405a123b..9f63934de7a 100644 --- a/tests/unittest/Dataflow/customnodes.cpp +++ b/tests/unittest/Dataflow/customnodes.cpp @@ -72,13 +72,9 @@ class FilterSelector final : public Node protected: bool fromJsonInternal( const nlohmann::json& data ) override { if ( data.contains( "operator" ) ) { m_operatorName = data["operator"]; } - else { - m_operatorName = "true"; - } + else { m_operatorName = "true"; } if ( data.contains( "threshold" ) ) { m_threshold = data["threshold"]; } - else { - m_threshold = T {}; - } + else { m_threshold = T {}; } return true; } From 89f89ff45d1705d4d9177dff65d68e461e582b4d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 30 Jun 2023 11:09:29 +0200 Subject: [PATCH 167/239] [cmake][dataflow] update cmake min version to 3.18 --- examples/DataflowExamples/CMakeLists.txt | 2 +- examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt | 7 +++---- examples/DataflowExamples/GraphEditor/CMakeLists.txt | 7 +++---- .../DataflowExamples/GraphSerialization/CMakeLists.txt | 7 +++---- examples/DataflowExamples/HelloGraph/CMakeLists.txt | 7 +++---- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/examples/DataflowExamples/CMakeLists.txt b/examples/DataflowExamples/CMakeLists.txt index 63c722d05e5..74cf144a541 100644 --- a/examples/DataflowExamples/CMakeLists.txt +++ b/examples/DataflowExamples/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.16) +cmake_minimum_required(VERSION 3.18) cmake_policy(SET CMP0042 NEW) project(DataflowExamples VERSION 1.0.0) diff --git a/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt b/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt index 8a16f43290f..3cf726e8a27 100644 --- a/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt +++ b/examples/DataflowExamples/FunctionalsGraph/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 3.6) -if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") - cmake_policy(SET CMP0071 NEW) -endif() +cmake_minimum_required(VERSION 3.18) +cmake_policy(SET CMP0071 NEW) + if(APPLE) cmake_policy(SET CMP0042 NEW) endif(APPLE) diff --git a/examples/DataflowExamples/GraphEditor/CMakeLists.txt b/examples/DataflowExamples/GraphEditor/CMakeLists.txt index 4c4a7b3fac2..e3b4b73d69d 100644 --- a/examples/DataflowExamples/GraphEditor/CMakeLists.txt +++ b/examples/DataflowExamples/GraphEditor/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 3.6) -if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") - cmake_policy(SET CMP0071 NEW) -endif() +cmake_minimum_required(VERSION 3.18) +cmake_policy(SET CMP0071 NEW) + if(APPLE) cmake_policy(SET CMP0042 NEW) endif(APPLE) diff --git a/examples/DataflowExamples/GraphSerialization/CMakeLists.txt b/examples/DataflowExamples/GraphSerialization/CMakeLists.txt index 98f9d1aa241..fe1d256a40d 100644 --- a/examples/DataflowExamples/GraphSerialization/CMakeLists.txt +++ b/examples/DataflowExamples/GraphSerialization/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 3.6) -if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") - cmake_policy(SET CMP0071 NEW) -endif() +cmake_minimum_required(VERSION 3.18) +cmake_policy(SET CMP0071 NEW) + if(APPLE) cmake_policy(SET CMP0042 NEW) endif(APPLE) diff --git a/examples/DataflowExamples/HelloGraph/CMakeLists.txt b/examples/DataflowExamples/HelloGraph/CMakeLists.txt index 2f6a4b64b9c..6d2ab47e0ae 100644 --- a/examples/DataflowExamples/HelloGraph/CMakeLists.txt +++ b/examples/DataflowExamples/HelloGraph/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 3.6) -if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") - cmake_policy(SET CMP0071 NEW) -endif() +cmake_minimum_required(VERSION 3.18) +cmake_policy(SET CMP0071 NEW) + if(APPLE) cmake_policy(SET CMP0042 NEW) endif(APPLE) From 3ccb3c8c3b4949714beebd1967bfc6802529aabb Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 7 Sep 2022 16:51:04 +0200 Subject: [PATCH 168/239] [core] add random point sets --- src/Core/Random/RandomPointSet.inl | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/Core/Random/RandomPointSet.inl diff --git a/src/Core/Random/RandomPointSet.inl b/src/Core/Random/RandomPointSet.inl new file mode 100644 index 00000000000..525cbbfc329 --- /dev/null +++ b/src/Core/Random/RandomPointSet.inl @@ -0,0 +1,32 @@ +#pragma once +#include + +namespace Ra { +namespace Core { +namespace Random { + +template +Ra::Core::Vector3 SphericalPointSet::projectOnSphere( const Ra::Core::Vector2&& pt ) { + Scalar theta = std::acos( 2 * pt[1] - 1 ); // 0 <= tetha <= pi + Scalar phi = 2_ra * Scalar( M_PI ) * pt[0]; + return { std::sin( theta ) * std::cos( phi ), + std::sin( theta ) * std::sin( phi ), + std::cos( theta ) }; +} + +template +SphericalPointSet::SphericalPointSet( size_t n ) : p( n ) {} + +template +size_t SphericalPointSet::range() { + return p.range(); +} + +template +Ra::Core::Vector3 SphericalPointSet::operator()( size_t i ) { + return projectOnSphere( p( i ) ); +} + +} // namespace Random +} // namespace Core +} // namespace Ra From 5f95dae59d04ce7a94cbbc184f65ce9cf79991b6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 6 Feb 2023 08:41:12 +0100 Subject: [PATCH 169/239] [core] fix compilation warning --- src/Core/Random/RandomPointSet.inl | 32 ------------------------------ 1 file changed, 32 deletions(-) delete mode 100644 src/Core/Random/RandomPointSet.inl diff --git a/src/Core/Random/RandomPointSet.inl b/src/Core/Random/RandomPointSet.inl deleted file mode 100644 index 525cbbfc329..00000000000 --- a/src/Core/Random/RandomPointSet.inl +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include - -namespace Ra { -namespace Core { -namespace Random { - -template -Ra::Core::Vector3 SphericalPointSet::projectOnSphere( const Ra::Core::Vector2&& pt ) { - Scalar theta = std::acos( 2 * pt[1] - 1 ); // 0 <= tetha <= pi - Scalar phi = 2_ra * Scalar( M_PI ) * pt[0]; - return { std::sin( theta ) * std::cos( phi ), - std::sin( theta ) * std::sin( phi ), - std::cos( theta ) }; -} - -template -SphericalPointSet::SphericalPointSet( size_t n ) : p( n ) {} - -template -size_t SphericalPointSet::range() { - return p.range(); -} - -template -Ra::Core::Vector3 SphericalPointSet::operator()( size_t i ) { - return projectOnSphere( p( i ) ); -} - -} // namespace Random -} // namespace Core -} // namespace Ra From ea1fdefd14cd30faaa6ace09aafbdacb3a546432 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 2 Oct 2022 18:40:07 +0200 Subject: [PATCH 170/239] [core] improve type utilities : add type traits utility and fix type demangling on windows --- src/Core/Utils/TypesUtils.hpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index 6305cad088f..2cfec6c2327 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -54,6 +54,32 @@ std::false_type is_container_impl( ... ); template using is_container = decltype( detail::is_container_impl( 0 ) ); +// Check if a type is a container with access to its element type and number +// adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable +namespace detail { + +using std::begin; +using std::end; + +template +auto is_container_impl( int ) + -> decltype( begin( std::declval() ) != + end( std::declval() ), // begin/end and operator != + void(), // Handle evil operator , + std::declval().empty(), + std::declval().size(), + ++std::declval() ) )&>(), // operator ++ + void( *begin( std::declval() ) ), // operator* + std::true_type {} ); + +template +std::false_type is_container_impl( ... ); + +} // namespace detail + +template +using is_container = decltype( detail::is_container_impl( 0 ) ); + // TypeList taken and adapted from // https://github.com/AcademySoftwareFoundation/openvdb/blob/master/openvdb/openvdb/TypeList.h // Only took small part of TypeList utilities From 219a795aea4014aa901b310943db53275e19775d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 2 Oct 2022 18:40:07 +0200 Subject: [PATCH 171/239] [core] improve type utilities : add type traits utility and fix type demangling on windows --- src/Core/Utils/TypesUtils.hpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index 2cfec6c2327..19dc4f7f51d 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -190,6 +190,32 @@ std::string demangleType( const T& ) noexcept { return demangleType(); } +// Check if a type is a container with access to its element type and number +// adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable +namespace detail { + +using std::begin; +using std::end; + +template +auto is_container_impl( int ) + -> decltype( begin( std::declval() ) != + end( std::declval() ), // begin/end and operator != + void(), // Handle evil operator , + std::declval().empty(), + std::declval().size(), + ++std::declval() ) )&>(), // operator ++ + void( *begin( std::declval() ) ), // operator* + std::true_type {} ); + +template +std::false_type is_container_impl( ... ); + +} // namespace detail + +template +using is_container = decltype( detail::is_container_impl( 0 ) ); + } // namespace Utils } // namespace Core } // namespace Ra From c8bc5961db13a668de06c9af08ea9fe8ace1e915 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 09:05:51 +0100 Subject: [PATCH 172/239] [dataflow][rendering] fix minimal dataflow renderer --- src/Dataflow/CMakeLists.txt | 8 +++++--- src/Dataflow/Config.cmake.in | 20 +++++++++---------- src/Dataflow/Rendering/Config.cmake.in | 4 ++-- .../Rendering/Nodes/RenderingNode.hpp | 9 +++++++-- .../Rendering/Renderer/DataflowRenderer.cpp | 10 +++++----- .../Rendering/Renderer/DataflowRenderer.hpp | 4 ++-- .../Rendering/Renderer/RenderingGraph.hpp | 2 +- .../Rendering/Renderer/RenderingGraph.inl | 0 8 files changed, 32 insertions(+), 25 deletions(-) create mode 100644 src/Dataflow/Rendering/Renderer/RenderingGraph.inl diff --git a/src/Dataflow/CMakeLists.txt b/src/Dataflow/CMakeLists.txt index 14e45548a65..2360159307a 100644 --- a/src/Dataflow/CMakeLists.txt +++ b/src/Dataflow/CMakeLists.txt @@ -12,9 +12,11 @@ if(RADIUM_GENERATE_LIB_CORE) target_link_libraries(${ra_dataflow_target} INTERFACE DataflowCore) endif() -# if(RADIUM_GENERATE_LIB_ENGINE) add_subdirectory(Rendering) add_dependencies(${ra_dataflow_target} -# DataflowRendering) target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) -# endif() +if(RADIUM_GENERATE_LIB_ENGINE) + add_subdirectory(Rendering) + add_dependencies(${ra_dataflow_target} DataflowRendering) + target_link_libraries(${ra_dataflow_target} INTERFACE DataflowRendering) +endif() if(RADIUM_GENERATE_LIB_GUI) add_subdirectory(QtGui) diff --git a/src/Dataflow/Config.cmake.in b/src/Dataflow/Config.cmake.in index 562e08464e7..1df6db7e0d7 100644 --- a/src/Dataflow/Config.cmake.in +++ b/src/Dataflow/Config.cmake.in @@ -26,16 +26,16 @@ if (Dataflow_FOUND AND NOT TARGET Dataflow) endif() # to be uncommented when dataflow rendering subpackage will be available -# if(NOT DataflowRendering_FOUND) -# if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake") -# set(DataflowRendering_FOUND TRUE) -# include(${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake) -# else() -# set(Radium_FOUND False) -# set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowRendering not found") -# set(Configure_Dataflow OFF) -# endif() -# endif() + if(NOT DataflowRendering_FOUND) + if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake") + set(DataflowRendering_FOUND TRUE) + include(${CMAKE_CURRENT_LIST_DIR}/../DataflowRendering/RadiumDataflowRenderingConfig.cmake) + else() + set(Radium_FOUND False) + set(Radium_NOT_FOUND_MESSAGE "Radium::Dataflow: dependency DataflowRendering not found") + set(Configure_Dataflow OFF) + endif() + endif() endif() # configure Dataflow component diff --git a/src/Dataflow/Rendering/Config.cmake.in b/src/Dataflow/Rendering/Config.cmake.in index 23bd179f1ab..578e992e349 100644 --- a/src/Dataflow/Rendering/Config.cmake.in +++ b/src/Dataflow/Rendering/Config.cmake.in @@ -16,14 +16,14 @@ if (DataflowRendering_FOUND AND NOT TARGET DataflowRendering) include(${CMAKE_CURRENT_LIST_DIR}/../DataflowCore/RadiumDataflowCoreConfig.cmake) else() set(Radium_FOUND False) - set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowQtGui: dependency DataflowCore not found") + set(Radium_NOT_FOUND_MESSAGE "Radium::DataflowRendering: dependency DataflowCore not found") set(Configure_DataflowRendering OFF) endif() endif() endif() if(NOT Engine_FOUND) if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake") - set(Gui_FOUND TRUE) + set(Engine_FOUND TRUE) include(${CMAKE_CURRENT_LIST_DIR}/../Engine/RadiumEngineConfig.cmake) else() set(Radium_FOUND False) diff --git a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp index 9c893c8cfc9..1705a5abfba 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp @@ -62,8 +62,13 @@ class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, static const std::string getTypename() { return "RenderingNode"; } protected: - void toJsonInternal( nlohmann::json& ) const override {} - void fromJsonInternal( const nlohmann::json& ) override {} + void toJsonInternal( nlohmann::json& data ) const override { + Dataflow::Core::Node::toJsonInternal( data ); + } + bool fromJsonInternal( const nlohmann::json& data ) override { + auto r = Dataflow::Core::Node::fromJsonInternal( data ); + return r; + } /// The renderer's shader program manager Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp index 87957cfac71..82e56cdfe8a 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp @@ -48,7 +48,7 @@ void DataflowRenderer::RenderGraphController::update( const Ra::Engine::Data::Vi if ( m_renderGraph && m_renderGraph->m_recompile ) { // compile the model - m_renderGraph->init(); + m_renderGraph->compile(); // notify the view the model changes notify(); // notify the model the view may have changed @@ -56,7 +56,7 @@ void DataflowRenderer::RenderGraphController::update( const Ra::Engine::Data::Vi } } -void DataflowRenderer::RenderGraphController::loadGraph( const std::string filename ) { +void DataflowRenderer::RenderGraphController::loadGraph( const std::string& filename ) { m_renderGraph.release(); auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); m_renderGraph = std::make_unique( graphName ); @@ -65,11 +65,11 @@ void DataflowRenderer::RenderGraphController::loadGraph( const std::string filen notify(); } -void DataflowRenderer::RenderGraphController::defferedLoadGraph( const std::string filename ) { +void DataflowRenderer::RenderGraphController::deferredLoadGraph( const std::string& filename ) { m_graphToLoad = filename; } -void DataflowRenderer::RenderGraphController::saveGraph( const std::string filename ) { +void DataflowRenderer::RenderGraphController::saveGraph( const std::string& filename ) { if ( m_renderGraph ) { auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); m_renderGraph->saveToJson( filename ); @@ -156,7 +156,7 @@ void DataflowRenderer::resizeInternal() { m_postprocessFbo->attachTexture( GL_COLOR_ATTACHMENT0, m_fancyTexture->texture() ); #ifdef PASSES_LOG if ( m_postprocessFbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE ) { - LOG( Ra::Core::Utils::logERROR ) << "FBO Error (NodeBasedRenderer::m_postprocessFbo) : " + LOG( Ra::Core::Utils::logERROR ) << "FBO Error (DataflowRenderer::m_postprocessFbo) : " << m_postprocessFbo->statusString(); } #endif diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp index 929a192d82a..eadd225404b 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp @@ -81,13 +81,13 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer /// Called once before each frame to update the internal state of the renderer virtual void update( const Ra::Engine::Data::ViewingParameters& renderData ); - [[nodiscard]] virtual std::string getRendererName() const { return "Dataflow Renderer"; } + [[nodiscard]] virtual std::string getRendererName() const { return "Node Renderer"; } void loadGraph( const std::string& filename ); void saveGraph( const std::string& filename ); void resetGraph(); /// Call this to set a graph to load before OpenGL is OK - void defferedLoadGraph( const std::string& filename ); + void deferredLoadGraph( const std::string& filename ); /// The controlled graph. /// The controller own the graph and manage loading/saving of the renderer diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index 06fbab96038..9622fedf6f8 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -50,7 +50,7 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph static const std::string& getTypename(); protected: - void fromJsonInternal( const nlohmann::json& ) override; + bool fromJsonInternal( const nlohmann::json& data ) override; void toJsonInternal( nlohmann::json& ) const override; private: diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl b/src/Dataflow/Rendering/Renderer/RenderingGraph.inl new file mode 100644 index 00000000000..e69de29bb2d From 8061346c534e321037147bcec76fe488ec00c531 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 09:06:40 +0100 Subject: [PATCH 173/239] [examples][dataflow] start rendering example --- examples/DataflowExamples/CMakeLists.txt | 2 +- .../GraphRendering/CMakeLists.txt | 58 ++++++ .../DataflowExamples/GraphRendering/main.cpp | 184 ++++++++++++++++++ 3 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 examples/DataflowExamples/GraphRendering/CMakeLists.txt create mode 100644 examples/DataflowExamples/GraphRendering/main.cpp diff --git a/examples/DataflowExamples/CMakeLists.txt b/examples/DataflowExamples/CMakeLists.txt index 74cf144a541..c3ebc4be8b8 100644 --- a/examples/DataflowExamples/CMakeLists.txt +++ b/examples/DataflowExamples/CMakeLists.txt @@ -5,7 +5,7 @@ project(DataflowExamples VERSION 1.0.0) add_custom_target(${PROJECT_NAME}) add_custom_target(Install_${PROJECT_NAME}) -foreach(APP GraphSerialization HelloGraph GraphEditor FunctionalsGraph) +foreach(APP GraphSerialization HelloGraph GraphEditor FunctionalsGraph GraphRendering) add_subdirectory(${APP}) add_dependencies(${PROJECT_NAME} ${APP}) add_dependencies(Install_${PROJECT_NAME} Install_${APP}) diff --git a/examples/DataflowExamples/GraphRendering/CMakeLists.txt b/examples/DataflowExamples/GraphRendering/CMakeLists.txt new file mode 100644 index 00000000000..7bb6e7d1adf --- /dev/null +++ b/examples/DataflowExamples/GraphRendering/CMakeLists.txt @@ -0,0 +1,58 @@ +cmake_minimum_required(VERSION 3.6) +if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") + cmake_policy(SET CMP0071 NEW) +endif() +if(APPLE) + cmake_policy(SET CMP0042 NEW) +endif(APPLE) + +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) + +project(GraphRendering VERSION 0.0.1) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + +# Set default install location to installed- folder in build dir we do not want to +# install to /usr by default +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX + "${CMAKE_CURRENT_BINARY_DIR}/installed-${CMAKE_CXX_COMPILER_ID}-${CMAKE_BUILD_TYPE}" + CACHE PATH "Install path prefix, prepended onto install directories." FORCE + ) + message(STATUS "Set install prefix to ${CMAKE_INSTALL_PREFIX}") +endif() + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# /////////////////////////////// +find_package(Radium REQUIRED COMPONENTS Dataflow) + +# Qt utility functions +include(QtFunctions) + +# Find Qt packages +find_qt_package(COMPONENTS Core Widgets REQUIRED) +set(Qt_LIBRARIES Qt::Core Qt::Widgets) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +# qt_wrap_ui(gui_uis ${gui_uis}) + +# -------------------------------------------------------------------------------------------------- + +set(app_sources main.cpp) +# set(app_headers) + +# delete ${app_headers} +add_executable(${PROJECT_NAME} ${app_sources}) + +target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) + +target_link_libraries(${PROJECT_NAME} PUBLIC ${Qt_LIBRARIES} Radium::Dataflow) + +configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp new file mode 100644 index 00000000000..36fabfca03c --- /dev/null +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -0,0 +1,184 @@ +// Include Radium base application and its simple Gui + +#include +#include + +#include +#include + +#include + +#include +#include +#include + +// Qt Widgets +#include + +/* ----------------------------------------------------------------------------------- */ +using namespace Ra::Core::Utils; +using namespace Ra::Engine; +using namespace Ra::Gui; +using namespace Ra::Dataflow::Rendering::Renderer; + +/** + * SimpleWindow for demonstration + */ +class DemoWindowFactory : public BaseApplication::WindowFactory +{ + std::vector> m_renderers; + + static void addFileMenu( MainWindowInterface* window ) { + // Add a menu to load a scene + auto fileMenu = window->menuBar()->addMenu( "&File" ); + auto fileOpenAction = new QAction( "&Open...", window ); + fileOpenAction->setShortcuts( QKeySequence::Open ); + fileOpenAction->setStatusTip( "Open a file." ); + fileMenu->addAction( fileOpenAction ); + + // Connect the menu + auto openFile = [window]() { + QString filter; + QString allexts; + auto engine = RadiumEngine::getInstance(); + for ( const auto& loader : engine->getFileLoaders() ) { + QString exts; + for ( const auto& e : loader->getFileExtensions() ) { + exts.append( QString::fromStdString( e ) + " " ); + } + allexts.append( exts + " " ); + filter.append( QString::fromStdString( loader->name() ) + " (" + exts + ");;" ); + } + // add a filter concatenating all the supported extensions + filter.prepend( "Supported files (" + allexts + ");;" ); + + // remove the last ";;" of the string + filter.remove( filter.size() - 2, 2 ); + + QSettings settings; + auto path = settings.value( "files/load", QDir::homePath() ).toString(); + auto pathList = QFileDialog::getOpenFileNames( window, "Open Files", path, filter ); + + if ( !pathList.empty() ) { + engine->getEntityManager()->deleteEntities(); + settings.setValue( "files/load", pathList.front() ); + engine->loadFile( pathList.front().toStdString() ); + engine->releaseFile(); + window->prepareDisplay(); + emit window->getViewer()->needUpdate(); + } + }; + QAction::connect( fileOpenAction, &QAction::triggered, openFile ); + + // Add an exit entry + auto exitAct = fileMenu->addAction( "E&xit", window, &QWidget::close ); + exitAct->setShortcuts( QKeySequence::Quit ); + } + + static void + addRendererMenu( MainWindowInterface* window, + const std::vector>& renderers ) { + auto renderMenu = window->menuBar()->addMenu( "&Renderer" ); + int renderNum = 0; + + for ( const auto& rndr : renderers ) { + window->addRenderer( rndr->getRendererName(), rndr ); + auto rndAct = new QAction( rndr->getRendererName().c_str(), window ); + renderMenu->addAction( rndAct ); + QAction::connect( rndAct, &QAction::triggered, [renderNum, window]() { + window->getViewer()->changeRenderer( renderNum ); + window->getViewer()->needUpdate(); + } ); + ++renderNum; + } + } + + public: + explicit DemoWindowFactory( + const std::vector>& renderers ) : + m_renderers( renderers ) {} + + inline Ra::Gui::MainWindowInterface* createMainWindow() const override { + auto window = new SimpleWindow(); + addFileMenu( window ); + addRendererMenu( window, m_renderers ); + return window; + } +}; +/* ----------------------------------------------------------------------------------- */ +// Renderer controller +class MyRendererController : public DataflowRenderer::RenderGraphController +{ + public: + using DataflowRenderer::RenderGraphController::RenderGraphController; + /// Configuration function. + /// Called once at the configuration of the renderer + void configure( DataflowRenderer* renderer, int w, int h ) override { + LOG( logINFO ) << "MyRendererController::configure"; + DataflowRenderer::RenderGraphController::configure( renderer, w, h ); + }; + + /// Resize function + /// Called each time the renderer is resized + void resize( int w, int h ) override { + LOG( logINFO ) << "MyRendererController::resize"; + DataflowRenderer::RenderGraphController::resize( w, h ); + }; + + /// Update function + /// Called once before each frame to update the internal state of the renderer + void update( const Ra::Engine::Data::ViewingParameters& renderData ) override { + LOG( logINFO ) << "MyRendererController::update"; + DataflowRenderer::RenderGraphController::update( renderData ); + }; + + [[nodiscard]] std::string getRendererName() const override { return "Custom Node Renderer"; } +}; + +/* ----------------------------------------------------------------------------------- */ + +/** + * main function. + */ +int main( int argc, char* argv[] ) { + + //! [Instatiating the application] + BaseApplication app( argc, argv ); + //! [Instatiating the application] + + //! getting graph argument on the command line + std::optional graphOption { std::nullopt }; + QCommandLineParser parser; + QCommandLineOption graphOpt( + { "g", "graph", "nodes" }, "Open a node at startup.", "graph", "" ); + parser.addOptions( { graphOpt } ); + if ( !parser.parse( app.arguments() ) ) { + LOG( Ra::Core::Utils::logWARNING ) + << "GraphDemo : Command line parsing failed due to unsupported or " + "missing options : \n\t" + << parser.errorText().toStdString(); + } + if ( parser.isSet( graphOpt ) ) { + graphOption = parser.value( graphOpt ).toStdString(); + std::cout << "Got a graph option : " << *graphOption << std::endl; + } + else { + std::cout << "No graph option" << std::endl; + } + //! getting graph argument on the command line + + //! [Initializing the application] + std::vector> renderers; + renderers.emplace_back( new Rendering::ForwardRenderer ); + + MyRendererController graphController; + if ( graphOption ) { graphController.deferredLoadGraph( *graphOption ); } + renderers.emplace_back( new DataflowRenderer( graphController ) ); + + app.initialize( DemoWindowFactory( renderers ) ); + app.setContinuousUpdate( false ); + app.addRadiumMenu(); + //! [Initializing the application] + + return app.exec(); +} From 6b55071ccb6b047dac853378b97b90c865e9ed48 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 11:38:54 +0100 Subject: [PATCH 174/239] [dataflow][rendering] add scene node and update renderer --- .../Rendering/Nodes/RenderingNode.hpp | 12 +-- .../Rendering/Nodes/Sources/Scene.hpp | 85 +++++++++++++++++++ .../Rendering/Renderer/DataflowRenderer.cpp | 18 ++-- .../Rendering/Renderer/DataflowRenderer.hpp | 7 +- .../Rendering/Renderer/RenderingGraph.cpp | 78 ++++------------- .../Rendering/Renderer/RenderingGraph.hpp | 16 +--- src/Dataflow/Rendering/filelist.cmake | 2 +- 7 files changed, 122 insertions(+), 96 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/Sources/Scene.hpp diff --git a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp index 1705a5abfba..210e9ef28ed 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp @@ -23,11 +23,11 @@ namespace Nodes { * */ -using RenderObjectType = std::shared_ptr; -using LightType = const Ra::Engine::Scene::Light*; -using CameraType = Ra::Engine::Data::ViewingParameters; -using ColorType = Ra::Core::Utils::Color; -using TextureType = Ra::Engine::Data::Texture; +using RenderObjectPtrType = std::shared_ptr; +using LightPtrType = const Ra::Engine::Scene::Light*; +using CameraType = Ra::Engine::Data::ViewingParameters; +using ColorType = Ra::Core::Utils::Color; +using TextureType = Ra::Engine::Data::Texture; /** * Base class for Rendering nodes. @@ -51,7 +51,7 @@ class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, virtual void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject*, Ra::Engine::Rendering::RenderTechnique& ) const {}; - /// Indicate if the nod needs to setup a rendertechnique on RenderObjects + /// Indicate if the node needs to setup a rendertechnique on RenderObjects virtual bool hasRenderTechnique() { return false; } /// Sets the shader program manager diff --git a/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp b/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp new file mode 100644 index 00000000000..17ecae1312a --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp @@ -0,0 +1,85 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API SceneNode : public Node +{ + protected: + SceneNode( const std::string& instanceName, const std::string& typeName ) : + Node( instanceName, typeName ) { + addOutput( m_roOut, m_objects ); + addOutput( m_lightOut, m_lights ); + addOutput( m_cameraOut, m_camera ); + } + + public: + explicit SceneNode( const std::string& name ) : SceneNode( name, SceneNode::getTypename() ) {} + + bool execute() override { + auto interfaceRo = static_cast>*>( m_interface[0] ); + if ( interfaceRo->isLinked() ) { m_roOut->setData( &( interfaceRo->getData() ) ); } + else { + m_roOut->setData( m_objects ); + } + + auto interfaceLights = static_cast>*>( m_interface[1] ); + if ( interfaceLights->isLinked() ) { + m_lightOut->setData( &( interfaceLights->getData() ) ); + } + else { + m_lightOut->setData( m_lights ); + } + + auto interfaceCamera = static_cast*>( m_interface[2] ); + if ( interfaceCamera->isLinked() ) { + m_cameraOut->setData( &( interfaceCamera->getData() ) ); + } + else { + m_cameraOut->setData( m_camera ); + } + return true; + } + + static const std::string getTypename() { return "Scene data provider"; } + + /// Set te scene data collection + /// @note, these collections must have a lifetime longer than the Scene Node they are associated + /// to. + void setScene( std::vector* ros, std::vector* lights ) { + m_objects = ros; + m_lights = lights; + } + + void setCamera( CameraType* camera ) { m_camera = camera; } + + protected: + bool fromJsonInternal( const nlohmann::json& ) override { return true; } + void toJsonInternal( nlohmann::json& ) const override {} + + private: + std::vector* m_objects { nullptr }; + PortOut>* m_roOut { + new PortOut>( "objects", this ) }; + + std::vector* m_lights { nullptr }; + PortOut>* m_lightOut { + new PortOut>( "lights", this ) }; + + CameraType* m_camera { nullptr }; + PortOut* m_cameraOut { new PortOut( "camera", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp index 82e56cdfe8a..d7a443eeb39 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp @@ -46,7 +46,7 @@ void DataflowRenderer::RenderGraphController::resize( int w, int h ) { void DataflowRenderer::RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { - if ( m_renderGraph && m_renderGraph->m_recompile ) { + if ( m_renderGraph && !m_renderGraph->m_ready ) { // compile the model m_renderGraph->compile(); // notify the view the model changes @@ -100,14 +100,14 @@ void DataflowRenderer::graphChanged() { bool DataflowRenderer::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { if ( m_controller.m_renderGraph ) { - if ( m_controller.m_renderGraph->m_recompile ) { - m_controller.m_renderGraph->init(); + if ( !m_controller.m_renderGraph->m_ready ) { m_controller.m_renderGraph->compile(); } + if ( m_controller.m_renderGraph->m_ready ) { m_controller.resize( m_width, m_height ); + m_controller.m_renderGraph->buildRenderTechnique( ro ); + return true; } - m_controller.m_renderGraph->buildRenderTechnique( ro ); - return true; } - else { return false; } + return false; } void DataflowRenderer::initResources() { @@ -192,15 +192,13 @@ void DataflowRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters if ( m_controller.m_renderGraph && m_controller.m_renderGraph->m_ready ) { // Cameras // set input data - m_cameras.clear(); - m_cameras.push_back( renderData ); - m_controller.m_renderGraph->setCameras( &m_cameras ); + m_controller.m_renderGraph->setCameras( &renderData ); // execute the graph m_controller.m_renderGraph->execute(); // TODO : get all the resulting images (not only the "Beauty" channel const auto& images = m_controller.m_renderGraph->getImagesOutput(); // The first image is the "beauty" channel, set the color texture to this - m_colorTexture = images[0]; + m_colorTexture = nullptr; // images[0]; } else { m_colorTexture = nullptr; } } diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp index eadd225404b..3c3f84b4fb6 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp @@ -93,10 +93,12 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer /// The controller own the graph and manage loading/saving of the renderer std::unique_ptr m_renderGraph { nullptr }; + protected: + Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; + private: int m_width { -1 }; int m_height { -1 }; - Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; std::string m_graphToLoad; }; @@ -191,9 +193,6 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer /// Vector of lights ... std::vector m_lights; - - /// Vector of camera - std::vector m_cameras; }; } // namespace Renderer diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp index 5f21dd94eb4..c6e4a6dc6dc 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp @@ -32,76 +32,28 @@ bool RenderingGraph::removeNode( Node* node ) { return removed; } -/* -bool RenderingGraph::postCompilationOperation() { -#if 0 +bool RenderingGraph::compile() { m_renderingNodes.clear(); m_rtIndexedNodes.clear(); - m_dataProviders.clear(); - if ( m_displaySinkNode && m_displayObserverId != -1 ) { - m_displaySinkNode->detach( m_displayObserverId ); - m_displayObserverId = -1; - m_displaySinkNode = nullptr; - } - const auto& compiledNodes = getNodesByLevel(); - int idx = 1; // The renderTechnique id = 0 is reserved for ui/debug objects - for ( const auto& lvl : *compiledNodes ) { - for ( auto n : lvl ) { - auto renderNode = dynamic_cast( n ); - if ( renderNode != nullptr ) { - m_renderingNodes.push_back( renderNode ); - if ( renderNode->hasRenderTechnique() ) { - renderNode->setIndex( idx++ ); - m_rtIndexedNodes.push_back( renderNode ); - } - renderNode->setShaderProgramManager( m_shaderMngr ); - } - else { - auto sceneNode = dynamic_cast( n ); - if ( sceneNode ) { m_dataProviders.push_back( sceneNode ); } - else { - // Manage all sinks ... - auto displaySink = dynamic_cast( n ); - if ( displaySink ) { - m_displaySinkNode = displaySink; - // observe the displaySink - m_displayObserverId = - displaySink->attachMember( this, &RenderingGraph::observeSinks ); + auto compiled = DataflowGraph::compile(); + if ( compiled ) { + const auto& compiledNodes = getNodesByLevel(); + int idx = 1; // The renderTechnique id = 0 is reserved for ui/debug objects + for ( const auto& lvl : *compiledNodes ) { + for ( auto n : lvl ) { + auto renderNode = dynamic_cast( n ); + if ( renderNode != nullptr ) { + m_renderingNodes.push_back( renderNode ); + if ( renderNode->hasRenderTechnique() ) { + renderNode->setIndex( idx++ ); + m_rtIndexedNodes.push_back( renderNode ); } + renderNode->setShaderProgramManager( m_shaderMngr ); } } } } -#if 0 - std::cout << "RenderingGraph::postCompilationOperation : got " << - m_renderingNodes.size() << " compiled rendering nodes with " << - m_rtIndexedNodes.size() << " render-passes, " << - m_dataProviders.size() << " scene nodes. \n"; -#endif -#endif -return true; -} -*/ -#if 0 -void RenderingGraph::observeSinks( const std::vector& graphOutput ) { - m_outputTextures = graphOutput; - /* - std::cout << "Available output textures are :" << std::endl; - int i = 0; - for (auto t : m_outputTextures ) { - std::cout << "\t tex " << i++ << " : "; - if (t) { - std::cout << t->getName() << std::endl; - } else { - std::cout << "nullptr" << std::endl; - } - } - */ -} -#endif - -const std::vector& RenderingGraph::getImagesOutput() const { - return m_outputTextures; + return compiled; } void RenderingGraph::clearNodes() { diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index 9622fedf6f8..a11e59bf2c9 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -28,6 +28,8 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph bool removeNode( Node* node ) override; void clearNodes() override; + bool compile() override; + /// Sets the shader program manager void setShaderProgramManager( Ra::Engine::Data::ShaderProgramManager* shaderMngr ) { m_shaderMngr = shaderMngr; @@ -37,9 +39,9 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph void resize( uint32_t width, uint32_t height ); /// Set the scene accessors on the graph - void setDataSources( std::vector* ros, std::vector* lights ); + void setDataSources( std::vector* ros, std::vector* lights ); /// Set the viewpoint on the graph - void setCameras( std::vector* cameras ); + void setCameras( const CameraType* cameras ); /// get the computed texture vector const std::vector& getImagesOutput() const; @@ -59,17 +61,7 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph /// List of nodes that requires some particular processing std::vector m_renderingNodes; // to resize std::vector m_rtIndexedNodes; // associate an index and buildRenderTechnique -#if 0 - /// List of nodes that serve as data provider - std::vector m_dataProviders; - // DisplaySink observerMethod : right now, observe only displaySink node - void observeSinks( const std::vector& graphOutput ); - /// The display sink node used to get rendered images - DisplaySinkNode* m_displaySinkNode { nullptr }; -#endif - /// ObserverId for displaySink; - int m_displayObserverId { -1 }; std::vector m_outputTextures; }; diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 7c05dd525ed..b6ced275d18 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -5,5 +5,5 @@ set(dataflow_rendering_sources Renderer/DataflowRenderer.cpp Renderer/RenderingGraph.cpp) set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/DataflowRenderer.hpp - Renderer/RenderingGraph.hpp + Renderer/RenderingGraph.hpp Nodes/Sources/Scene.hpp ) From 3f0d08630ce8e1b071470421c51633bbf84b68fa Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 11:39:27 +0100 Subject: [PATCH 175/239] [examples][dataflow] update rendering demo --- examples/DataflowExamples/GraphRendering/main.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 36fabfca03c..f45089b0159 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -20,6 +20,7 @@ using namespace Ra::Core::Utils; using namespace Ra::Engine; using namespace Ra::Gui; using namespace Ra::Dataflow::Rendering::Renderer; +using namespace Ra::Dataflow::Rendering; /** * SimpleWindow for demonstration @@ -107,6 +108,8 @@ class DemoWindowFactory : public BaseApplication::WindowFactory }; /* ----------------------------------------------------------------------------------- */ // Renderer controller +#include + class MyRendererController : public DataflowRenderer::RenderGraphController { public: @@ -116,6 +119,10 @@ class MyRendererController : public DataflowRenderer::RenderGraphController void configure( DataflowRenderer* renderer, int w, int h ) override { LOG( logINFO ) << "MyRendererController::configure"; DataflowRenderer::RenderGraphController::configure( renderer, w, h ); + m_renderGraph = std::make_unique( "Demonstration graph" ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); + auto sceneNode = new SceneNode( "Scene" ); + m_renderGraph->addNode( sceneNode ); }; /// Resize function From 1f77e4ea6449aa6416385976455764cb551d792e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 15:21:13 +0100 Subject: [PATCH 176/239] [dataflow][rendering] improve configurable renderer --- .../Nodes/RenderingBuiltInsNodes.cpp | 27 + .../Nodes/RenderingBuiltInsNodes.hpp | 17 + .../Renderer/ControllableRenderer.cpp | 263 ++++++++ ...wRenderer.hpp => ControllableRenderer.hpp} | 139 ++-- .../Rendering/Renderer/DataflowRenderer.cpp | 634 ------------------ .../Rendering/Renderer/RenderingGraph.hpp | 20 +- src/Dataflow/Rendering/filelist.cmake | 9 +- 7 files changed, 401 insertions(+), 708 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp create mode 100644 src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp rename src/Dataflow/Rendering/Renderer/{DataflowRenderer.hpp => ControllableRenderer.hpp} (59%) delete mode 100644 src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp new file mode 100644 index 00000000000..b7b624f2e1a --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -0,0 +1,27 @@ +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { + +void registerRenderingNodesFactories() { + Core::NodeFactorySet::mapped_type renderingFactory { + new Core::NodeFactorySet::mapped_type::element_type( "RenderingNodes" ) }; + + /* --- Sources --- */ + renderingFactory->registerNodeCreator( Nodes::SceneNode::getTypename() + "_", + "Source" ); + + /* --- Sinks --- */ + + /* --- operators --- */ + + /* -- end --*/ + Core::NodeFactoriesManager::registerFactory( renderingFactory ); +} + +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp new file mode 100644 index 00000000000..f097aefc521 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +#include +namespace Ra { +namespace Dataflow { +namespace Rendering { + +/** + * \brief Create the node system default factory for rendering nodes. + * + * If needed, the definition of all the rendering nodes can be included using one of the headers + * - #include + */ +RA_DATAFLOW_API void registerRenderingNodesFactories(); +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp new file mode 100644 index 00000000000..64e670ea232 --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp @@ -0,0 +1,263 @@ +#include + +#include +#include +#include + +#include + +#include + +#include + +using namespace Ra::Engine; +using namespace Ra::Engine::Scene; +using namespace Ra::Engine::Data; +using namespace Ra::Engine::Rendering; +using namespace gl; + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { +using namespace Ra::Dataflow::Core; + +ControllableRenderer::RendererController::RendererController() : + Ra::Core::Resources::ObservableVoid() {} + +void ControllableRenderer::RendererController::configure( ControllableRenderer* renderer, + int w, + int h ) { + m_attachedRenderer = renderer; + m_shaderMngr = m_attachedRenderer->m_shaderProgramManager; + m_width = w; + m_height = h; +} + +void ControllableRenderer::RendererController::resize( int w, int h ) { + m_width = w; + m_height = h; +} + +// ------------------------------------------------------------------------------------------ +// Interface with Radium renderer ... +// ------------------------------------------------------------------------------------------ + +ControllableRenderer::ControllableRenderer( RendererController& controller ) : + Renderer(), m_controller { controller }, m_name { m_controller.getRendererName() } { + m_controller.attachMember( this, &ControllableRenderer::controllerStateChanged ); +} + +ControllableRenderer::~ControllableRenderer() = default; + +void ControllableRenderer::controllerStateChanged() { + m_controllerStateChanged = true; +} + +bool ControllableRenderer::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { + return m_controller.buildRenderTechnique( ro ); +} + +void ControllableRenderer::initResources() { + // uses several resources from the Radium engine + auto resourcesRootDir { RadiumEngine::getInstance()->getResourcesDir() + "Shaders/" }; + + m_shaderProgramManager->addShaderProgram( + { { "Hdr2Ldr" }, + resourcesRootDir + "2DShaders/Basic2D.vert.glsl", + resourcesRootDir + "2DShaders/Hdr2Ldr.frag.glsl" } ); + + m_postprocessFbo = std::make_unique(); +} + +void ControllableRenderer::initializeInternal() { + auto lmngr = dynamic_cast( + Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultLightManager" ) ); + if ( lmngr == nullptr ) { + lmngr = new DefaultLightManager(); + Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultLightManager", lmngr ); + } + m_lightmanagers.push_back( lmngr ); + + // Initialize renderer resources + initResources(); + m_controller.configure( this, m_width, m_height ); + + // TODO update shared textures as the controller modify its output : observe the controller ? + for ( const auto& t : m_sharedTextures ) { + m_secondaryTextures.insert( { t.first, t.second.get() } ); + } +} + +void ControllableRenderer::resizeInternal() { + // Resize the controller + m_controller.resize( m_width, m_height ); + + // Resize the internal resources + m_postprocessFbo->bind(); + m_postprocessFbo->attachTexture( GL_COLOR_ATTACHMENT0, m_fancyTexture->texture() ); + // finished with fbo, unbind to bind default + globjects::Framebuffer::unbind(); +} + +void ControllableRenderer::updateStepInternal( + const Ra::Engine::Data::ViewingParameters& renderData ) { + m_controller.update( renderData ); + if ( m_controllerStateChanged ) { + buildAllRenderTechniques(); + m_controllerStateChanged = false; + } + + // TODO, improve light and camera management to prevent multiple alloc/copy ... + auto lightMngr = getLightManager(); + auto lights = getLights(); + lights->clear(); + lights->reserve( lightMngr->count() ); + for ( size_t i = 0; i < lightMngr->count(); i++ ) { + lights->push_back( lightMngr->getLight( i ) ); + } +} + +void ControllableRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { + + if ( m_controller.render( allRenderObjects(), getLights(), &renderData ) ) { + m_colorTexture = nullptr; // TODO fetch the images from the controler + } + else { + m_colorTexture = nullptr; + } +} + +void ControllableRenderer::postProcessInternal( const Ra::Engine::Data::ViewingParameters& ) { + if ( m_colorTexture ) { + m_postprocessFbo->bind(); + + // GL_ASSERT( glDrawBuffers( 1, buffers ) ); + GL_ASSERT( glDrawBuffer( GL_COLOR_ATTACHMENT0 ) ); + GL_ASSERT( glDisable( GL_DEPTH_TEST ) ); + GL_ASSERT( glDepthMask( GL_FALSE ) ); + + auto shader = m_postProcessEnabled + ? m_shaderProgramManager->getShaderProgram( "Hdr2Ldr" ) + : m_shaderProgramManager->getShaderProgram( "DrawScreen" ); + shader->bind(); + shader->setUniform( "screenTexture", m_colorTexture, 0 ); + m_quadMesh->render( shader ); + + GL_ASSERT( glDepthMask( GL_TRUE ) ); + GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); + + m_postprocessFbo->unbind(); + } +} + +void ControllableRenderer::debugInternal( const Ra::Engine::Data::ViewingParameters& ) {} + +void ControllableRenderer::uiInternal( const Ra::Engine::Data::ViewingParameters& ) {} + +/* + * ************* + */ + +RenderGraphController::RenderGraphController() : ControllableRenderer::RendererController() {} + +void RenderGraphController::configure( ControllableRenderer* renderer, int w, int h ) { + ControllableRenderer::RendererController::configure( renderer, w, h ); + if ( !m_graphToLoad.empty() ) { + loadGraph( m_graphToLoad ); + m_graphToLoad = ""; + } +} + +void RenderGraphController::resize( int w, int h ) { + ControllableRenderer::RendererController::resize( w, h ); + if ( m_renderGraph ) { m_renderGraph->resize( m_width, m_height ); } +} + +void RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { + if ( m_renderGraph && !m_renderGraph->m_ready ) { + // compile the model + m_renderGraph->compile(); + // notify the view the model changes + notify(); + // notify the model the view may have changed + m_renderGraph->resize( m_width, m_height ); + + // fetch the data setters and getters from the graph + m_renderGraphInputs = m_renderGraph->getAllDataSetters(); + m_renderGraphOutputs = m_renderGraph->getAllDataGetters(); + } +} + +bool RenderGraphController::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { + if ( m_renderGraph ) { + if ( !m_renderGraph->m_ready ) { + m_renderGraph->compile(); + m_renderGraph->resize( m_width, m_height ); + // fetch the data setters and getters from the graph + m_renderGraphInputs = m_renderGraph->getAllDataSetters(); + m_renderGraphOutputs = m_renderGraph->getAllDataGetters(); + } + if ( m_renderGraph->m_ready ) { + m_renderGraph->buildRenderTechnique( ro ); + return true; + } + } + return false; +} + +bool RenderGraphController::render( std::vector* ros, + std::vector* lights, + const CameraType* cameras ) const { + // TODO, replace this kind of test by a call to a controller method + if ( m_renderGraph && m_renderGraph->m_ready ) { + // set input data + for ( const auto& [ptr, name, type] : m_renderGraphInputs ) { + if ( type == simplifiedDemangledType( *ros ) ) { ptr->setData( ros ); } + if ( type == simplifiedDemangledType( *lights ) ) { ptr->setData( lights ); } + if ( type == simplifiedDemangledType( *cameras ) ) { ptr->setData( cameras ); } + } + + // execute the graph + m_renderGraph->execute(); + + // get output + // TODO : get all the resulting images (not only the "Beauty" channel + return true; + } + else { + return false; + } +} + +void RenderGraphController::loadGraph( const std::string& filename ) { + m_renderGraph.release(); + auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); + m_renderGraph = std::make_unique( graphName ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); + m_renderGraph->loadFromJson( filename ); + notify(); +} + +void RenderGraphController::deferredLoadGraph( const std::string& filename ) { + m_graphToLoad = filename; +} + +void RenderGraphController::saveGraph( const std::string& filename ) { + if ( m_renderGraph ) { + auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); + m_renderGraph->saveToJson( filename ); + m_renderGraph->setInstanceName( graphName ); + } +} + +void RenderGraphController::resetGraph() { + m_renderGraph.release(); + m_renderGraph = std::make_unique( "untitled" ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); +} + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp similarity index 59% rename from src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp rename to src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index 3c3f84b4fb6..6e45148936b 100644 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -58,20 +58,22 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer public: /** - * RenderGraph controller + * Renderer controller */ - struct RA_DATAFLOW_API RenderGraphController : Ra::Core::Resources::ObservableVoid { + class RA_DATAFLOW_API RendererController : public Ra::Core::Resources::ObservableVoid + { - RenderGraphController(); - virtual ~RenderGraphController() = default; - RenderGraphController( const RenderGraphController& ) = delete; - RenderGraphController( const RenderGraphController&& ) = delete; - RenderGraphController& operator=( RenderGraphController&& ) = delete; - RenderGraphController& operator=( const RenderGraphController& ) = delete; + public: + RendererController(); + virtual ~RendererController() = default; + RendererController( const RendererController& ) = delete; + RendererController( const RendererController&& ) = delete; + RendererController& operator=( RendererController&& ) = delete; + RendererController& operator=( const RendererController& ) = delete; /// Configuration function. /// Called once at the configuration of the renderer - virtual void configure( DataflowRenderer* renderer, int w, int h ); + virtual void configure( ControllableRenderer* renderer, int w, int h ); /// Resize function /// Called each time the renderer is resized @@ -79,34 +81,35 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer /// Update function /// Called once before each frame to update the internal state of the renderer - virtual void update( const Ra::Engine::Data::ViewingParameters& renderData ); + virtual void update( const Ra::Engine::Data::ViewingParameters& renderData ) = 0; - [[nodiscard]] virtual std::string getRendererName() const { return "Node Renderer"; } + /// RenderTechnique builder + /// Called each time the render techniques should be built (aftre switching from renderer, + /// loading a scene, ...). + virtual bool buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const = 0; - void loadGraph( const std::string& filename ); - void saveGraph( const std::string& filename ); - void resetGraph(); - /// Call this to set a graph to load before OpenGL is OK - void deferredLoadGraph( const std::string& filename ); + /// Render the given scene + /// \return true if rendering output is available + virtual bool render( std::vector* ros, + std::vector* lights, + const CameraType* cameras ) const = 0; - /// The controlled graph. - /// The controller own the graph and manage loading/saving of the renderer - std::unique_ptr m_renderGraph { nullptr }; + [[nodiscard]] virtual std::string getRendererName() const { + return "Base RendererController"; + } protected: + ControllableRenderer* m_attachedRenderer; Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; - - private: int m_width { -1 }; int m_height { -1 }; - std::string m_graphToLoad; }; /// Construct a renderer configured and managed through the controller - explicit DataflowRenderer( RenderGraphController& controller ); + explicit ControllableRenderer( RendererController& controller ); /// The destructor is used to destroy the render graph - ~DataflowRenderer() override; + ~ControllableRenderer() override; [[nodiscard]] std::string getRendererName() const override { return m_name; } @@ -116,33 +119,7 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer Ra::Engine::Scene::LightManager* getLightManager() { return m_lightmanagers[0]; } /// Access the controller - RenderGraphController& getController() { return m_controller; } - - /// Sets the display sink node - // void setDisplayNode( DisplaySinkNode* displayNode ); - - /// Loads the render graph from a Json file. - // void loadFromJson( const std::string& jsonFilePath ); - - /// Gets the Json file path - // const std::string& getJsonFilePath() { return m_jsonFilePath; } - - /// Sets the Json file path - // void setJsonFilePath( const std::string& jsonFilePath ) { m_jsonFilePath = jsonFilePath; } - - /// Reloads the render graph - // void compileRenderGraph(); - - /// Reloads the render graph according to the current Json file - // void reloadRenderGraphFromJson(); - - /// Raises the flag to reload the Json - /* - void signalReloadJson( bool resetPath = false ) { - m_reloadJson = true; - m_resetPath = resetPath; - } - */ + RendererController& getController() { return m_controller; } protected: void initializeInternal() override; @@ -171,10 +148,10 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer inline std::vector* getLights() { return &m_lights; } private: - /// Controler observer method - void graphChanged(); + /// Controller observer method + void controllerStateChanged(); - bool m_graphChanged { false }; + bool m_controllerStateChanged { false }; /// textures own by the Renderer but shared across passes std::map> m_sharedTextures; @@ -183,7 +160,7 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer std::unique_ptr m_postprocessFbo; /// The configurator functor to use - RenderGraphController& m_controller; + RendererController& m_controller; /// The name of the renderer std::string m_name { "RenderGraph renderer" }; @@ -195,6 +172,58 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer std::vector m_lights; }; +/** + * RenderGraph controller + */ +class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::RendererController +{ + + public: + RenderGraphController(); + virtual ~RenderGraphController() = default; + RenderGraphController( const RenderGraphController& ) = delete; + RenderGraphController( const RenderGraphController&& ) = delete; + RenderGraphController& operator=( RenderGraphController&& ) = delete; + RenderGraphController& operator=( const RenderGraphController& ) = delete; + + /// Configuration function. + /// Called once at the configuration of the renderer + void configure( ControllableRenderer* renderer, int w, int h ) override; + + /// Resize function + /// Called each time the renderer is resized + void resize( int w, int h ) override; + + /// Update function + /// Called once before each frame to update the internal state of the renderer + void update( const Ra::Engine::Data::ViewingParameters& renderData ) override; + + /// RenderTechnique builder + /// Called each time the render techniques should be built. + bool buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const override; + + bool render( std::vector* ros, + std::vector* lights, + const CameraType* cameras ) const override; + + [[nodiscard]] std::string getRendererName() const override { return "Node Renderer"; } + + void loadGraph( const std::string& filename ); + void saveGraph( const std::string& filename ); + void resetGraph(); + /// Call this to set a graph to load before OpenGL is OK + void deferredLoadGraph( const std::string& filename ); + + protected: + /// The controlled graph. + /// The controller own the graph and manage loading/saving of the renderer + std::unique_ptr m_renderGraph { nullptr }; + mutable std::vector m_renderGraphInputs; + mutable std::vector m_renderGraphOutputs; + + std::string m_graphToLoad; +}; + } // namespace Renderer } // namespace Rendering } // namespace Dataflow diff --git a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp b/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp deleted file mode 100644 index d7a443eeb39..00000000000 --- a/src/Dataflow/Rendering/Renderer/DataflowRenderer.cpp +++ /dev/null @@ -1,634 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include - -#include - -using namespace Ra::Engine; -using namespace Ra::Engine::Scene; -using namespace Ra::Engine::Data; -using namespace Ra::Engine::Rendering; -using namespace gl; - -namespace Ra { -namespace Dataflow { -namespace Rendering { -namespace Renderer { -using namespace Ra::Dataflow::Core; - -DataflowRenderer::RenderGraphController::RenderGraphController() : - Ra::Core::Resources::ObservableVoid() {} - -void DataflowRenderer::RenderGraphController::configure( DataflowRenderer* renderer, - int w, - int h ) { - m_shaderMngr = renderer->m_shaderProgramManager; - m_width = w; - m_height = h; - if ( !m_graphToLoad.empty() ) { - loadGraph( m_graphToLoad ); - m_graphToLoad = ""; - } -} - -void DataflowRenderer::RenderGraphController::resize( int w, int h ) { - m_width = w; - m_height = h; - if ( m_renderGraph ) { m_renderGraph->resize( m_width, m_height ); } -} - -void DataflowRenderer::RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { - - if ( m_renderGraph && !m_renderGraph->m_ready ) { - // compile the model - m_renderGraph->compile(); - // notify the view the model changes - notify(); - // notify the model the view may have changed - m_renderGraph->resize( m_width, m_height ); - } -} - -void DataflowRenderer::RenderGraphController::loadGraph( const std::string& filename ) { - m_renderGraph.release(); - auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); - m_renderGraph = std::make_unique( graphName ); - m_renderGraph->setShaderProgramManager( m_shaderMngr ); - m_renderGraph->loadFromJson( filename ); - notify(); -} - -void DataflowRenderer::RenderGraphController::deferredLoadGraph( const std::string& filename ) { - m_graphToLoad = filename; -} - -void DataflowRenderer::RenderGraphController::saveGraph( const std::string& filename ) { - if ( m_renderGraph ) { - auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); - m_renderGraph->saveToJson( filename ); - m_renderGraph->setInstanceName( graphName ); - } -} - -void DataflowRenderer::RenderGraphController::resetGraph() { - m_renderGraph.release(); - m_renderGraph = std::make_unique( "untitled" ); - m_renderGraph->setShaderProgramManager( m_shaderMngr ); -} - -// ------------------------------------------------------------------------------------------ -// Interface with Radium renderer ... -// ------------------------------------------------------------------------------------------ - -DataflowRenderer::DataflowRenderer( RenderGraphController& controller ) : - Renderer(), m_controller { controller }, m_name { m_controller.getRendererName() } { - m_controller.attachMember( this, &DataflowRenderer::graphChanged ); -} - -DataflowRenderer::~DataflowRenderer() = default; - -void DataflowRenderer::graphChanged() { - m_graphChanged = true; -} - -bool DataflowRenderer::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { - if ( m_controller.m_renderGraph ) { - if ( !m_controller.m_renderGraph->m_ready ) { m_controller.m_renderGraph->compile(); } - if ( m_controller.m_renderGraph->m_ready ) { - m_controller.resize( m_width, m_height ); - m_controller.m_renderGraph->buildRenderTechnique( ro ); - return true; - } - } - return false; -} - -void DataflowRenderer::initResources() { - // uses several resources from the Radium engine - auto resourcesRootDir { RadiumEngine::getInstance()->getResourcesDir() + "Shaders/" }; - - m_shaderProgramManager->addShaderProgram( - { { "Hdr2Ldr" }, - resourcesRootDir + "2DShaders/Basic2D.vert.glsl", - resourcesRootDir + "2DShaders/Hdr2Ldr.frag.glsl" } ); - - m_postprocessFbo = std::make_unique(); -} - -void DataflowRenderer::initializeInternal() { - auto cmngr = dynamic_cast( - Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultCameraManager" ) ); - if ( cmngr == nullptr ) { - cmngr = new DefaultCameraManager(); - Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultCameraManager", cmngr ); - } - auto lmngr = dynamic_cast( - Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultLightManager" ) ); - if ( lmngr == nullptr ) { - lmngr = new DefaultLightManager(); - Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultLightManager", lmngr ); - } - m_lightmanagers.push_back( lmngr ); - - // Initialize renderer resources - initResources(); - m_controller.configure( this, m_width, m_height ); - - // TODO update shared textures as the rengerGraphe modify its output : observe the renderGraph - for ( const auto& t : m_sharedTextures ) { - m_secondaryTextures.insert( { t.first, t.second.get() } ); - } -} - -void DataflowRenderer::resizeInternal() { - // Resize the graph resources - m_controller.resize( m_width, m_height ); - - // Resize the internal resources - m_postprocessFbo->bind(); - m_postprocessFbo->attachTexture( GL_COLOR_ATTACHMENT0, m_fancyTexture->texture() ); -#ifdef PASSES_LOG - if ( m_postprocessFbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE ) { - LOG( Ra::Core::Utils::logERROR ) << "FBO Error (DataflowRenderer::m_postprocessFbo) : " - << m_postprocessFbo->statusString(); - } -#endif - // finished with fbo, unbind to bind default - globjects::Framebuffer::unbind(); -} - -void DataflowRenderer::updateStepInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { - // std::cout << "DataflowRenderer::updateStepInternal() : calling update on graph controller." - // << std::endl; - m_controller.update( renderData ); - if ( m_controller.m_renderGraph ) { - // Update renderTechnique if needed - if ( m_graphChanged ) { - buildAllRenderTechniques(); - m_graphChanged = false; - } - // TODO, improve light and camera management to prevent multiple alloc/copy ... - auto lights = getLights(); - lights->clear(); - lights->reserve( getLightManager()->count() ); - for ( size_t i = 0; i < getLightManager()->count(); i++ ) { - lights->push_back( getLightManager()->getLight( i ) ); - } - // The graph will take ownership of the light pointer ... - m_controller.m_renderGraph->setDataSources( allRenderObjects(), lights ); - } -} - -void DataflowRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { - // std::cout << "DataflowRenderer::renderInternal() : executing the graph." << std::endl; - // TODO, replace this kind of test by a call to a controller method - if ( m_controller.m_renderGraph && m_controller.m_renderGraph->m_ready ) { - // Cameras - // set input data - m_controller.m_renderGraph->setCameras( &renderData ); - // execute the graph - m_controller.m_renderGraph->execute(); - // TODO : get all the resulting images (not only the "Beauty" channel - const auto& images = m_controller.m_renderGraph->getImagesOutput(); - // The first image is the "beauty" channel, set the color texture to this - m_colorTexture = nullptr; // images[0]; - } - else { m_colorTexture = nullptr; } -} - -void DataflowRenderer::postProcessInternal( const Ra::Engine::Data::ViewingParameters& ) { - if ( m_colorTexture ) { - m_postprocessFbo->bind(); - - // GL_ASSERT( glDrawBuffers( 1, buffers ) ); - GL_ASSERT( glDrawBuffer( GL_COLOR_ATTACHMENT0 ) ); - GL_ASSERT( glDisable( GL_DEPTH_TEST ) ); - GL_ASSERT( glDepthMask( GL_FALSE ) ); - - auto shader = m_postProcessEnabled - ? m_shaderProgramManager->getShaderProgram( "Hdr2Ldr" ) - : m_shaderProgramManager->getShaderProgram( "DrawScreen" ); - shader->bind(); - shader->setUniform( "screenTexture", m_colorTexture, 0 ); - m_quadMesh->render( shader ); - - GL_ASSERT( glDepthMask( GL_TRUE ) ); - GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); - - m_postprocessFbo->unbind(); - } -} - -void DataflowRenderer::debugInternal( const Ra::Engine::Data::ViewingParameters& ) {} - -void DataflowRenderer::uiInternal( const Ra::Engine::Data::ViewingParameters& ) {} - -} // namespace Renderer -} // namespace Rendering -} // namespace Dataflow -} // namespace Ra - -#if 0 -# include - -# include - -# ifdef PASSES_LOG -# include -using namespace Ra::Core::Utils; // log -# endif - -# include -# include -# include - -# include -# include -# include - -# include - -# include -# include - -using namespace Ra::Engine; -using namespace Ra::Engine::Scene; -using namespace Ra::Engine::Data; -using namespace Ra::Engine::Rendering; -namespace RadiumNBR { -using namespace gl; - -int NodeBasedRendererMagic = 0xFF0F00F0; - -static const GLenum buffers[] = { GL_COLOR_ATTACHMENT0 }; - -static NodeBasedRenderer::RenderControlFunctor noOpController; - -NodeBasedRenderer::NodeBasedRenderer() : Renderer(), m_controller{ noOpController } {} - -NodeBasedRenderer::NodeBasedRenderer( NodeBasedRenderer::RenderControlFunctor& controller ) : - Renderer(), m_controller{ controller }, m_name{ m_controller.getRendererName() } { - setDisplayNode( m_originalRenderGraph.getDisplayNode() ); -} - -NodeBasedRenderer::~NodeBasedRenderer() { - m_displaySinkNode->detach( m_displayObserverId ); - m_originalRenderGraph.destroy(); -} - -bool NodeBasedRenderer::buildRenderTechnique( RenderObject* ro ) const { - auto rt = Ra::Core::make_shared(); - for ( size_t level = 0; level < m_originalRenderGraph.getNodesByLevel()->size(); level++ ) - { - for ( size_t node = 0; node < m_originalRenderGraph.getNodesByLevel()->at( level ).size(); - node++ ) - { - m_originalRenderGraph.getNodesByLevel()->at( level ).at( node )->buildRenderTechnique( - ro, *rt ); - } - } - rt->updateGL(); - ro->setRenderTechnique( rt ); - return true; -} - -void NodeBasedRenderer::initResources() { - // uses several resources from the Radium engine - auto resourcesRootDir{ RadiumEngine::getInstance()->getResourcesDir() + "Shaders/" }; - - m_shaderProgramManager->addShaderProgram( - { { "Hdr2Ldr" }, - resourcesRootDir + "2DShaders/Basic2D.vert.glsl", - resourcesRootDir + "2DShaders/Hdr2Ldr.frag.glsl" } ); - - m_postprocessFbo = std::make_unique(); -} - -void NodeBasedRenderer::loadFromJson( const std::string& jsonFilePath ) { - m_jsonFilePath = jsonFilePath; - - if ( m_jsonFilePath != "" ) - { - m_originalRenderGraph.loadFromJson( jsonFilePath ); - m_originalRenderGraph.init(); - } - else - { std::cerr << "No Json was given to load a render graph." << std::endl; } -} - -void NodeBasedRenderer::compileRenderGraph() { - m_originalRenderGraph.init(); - m_originalRenderGraph.resize( m_width, m_height ); - buildAllRenderTechniques(); - m_displayedTexture = m_fancyTexture.get(); -} - -void NodeBasedRenderer::reloadRenderGraphFromJson() { - if ( m_jsonFilePath != "" ) - { - std::cout << "Reloading Render Graph from Json..." << std::endl; - // Destroy the resources used by the nodes - m_originalRenderGraph.destroy(); - - // Clear the nodes - m_originalRenderGraph.clearNodes(); - - // Reload - m_originalRenderGraph.loadFromJson( m_jsonFilePath ); - m_originalRenderGraph.init(); - m_originalRenderGraph.resize( m_width, m_height ); - buildAllRenderTechniques(); - - // Reset displayed texture - m_displayedTexture = m_fancyTexture.get(); - - std::cout << "Render Graph Reloaded!" << std::endl; - } - else - { std::cerr << "No Json was given to reload a render graph." << std::endl; } -} - -void NodeBasedRenderer::initializeInternal() { - - // TODO : this must be done only once, see register system ... - auto cmngr = dynamic_cast( - Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultCameraManager" ) ); - if ( cmngr == nullptr ) - { - cmngr = new DefaultCameraManager(); - Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultCameraManager", cmngr ); - } - auto lmngr = dynamic_cast( - Ra::Engine::RadiumEngine::getInstance()->getSystem( "DefaultLightManager" ) ); - if ( lmngr == nullptr ) - { - lmngr = new DefaultLightManager(); - Ra::Engine::RadiumEngine::getInstance()->registerSystem( "DefaultLightManager", lmngr ); - } - m_lightmanagers.push_back( lmngr ); - - // Initialize renderer resources - initResources(); - m_controller.configure( this, m_width, m_height ); - - for ( const auto& t : m_sharedTextures ) - { m_secondaryTextures.insert( { t.first, t.second.get() } ); } - - // Todo cache this in an attribute ? - auto resourcesCheck = Ra::Core::Resources::getResourcesPath( - reinterpret_cast( &RadiumNBR::NodeBasedRendererMagic ), { "Resources/RadiumNBR" } ); - if ( !resourcesCheck ) - { - LOG( Ra::Core::Utils::logERROR ) << "Unable to find resources for NodeBasedRenderer!"; - return; - } - auto resourcesPath{ *resourcesCheck }; -} - -void NodeBasedRenderer::resizeInternal() { - // Resize each internal resources - m_controller.resize( m_width, m_height ); - m_originalRenderGraph.resize( m_width, m_height ); - - m_postprocessFbo->bind(); - m_postprocessFbo->attachTexture( GL_COLOR_ATTACHMENT0, m_fancyTexture->texture() ); -# ifdef PASSES_LOG - if ( m_postprocessFbo->checkStatus() != GL_FRAMEBUFFER_COMPLETE ) - { - LOG( Ra::Core::Utils::logERROR ) << "FBO Error (NodeBasedRenderer::m_postprocessFbo) : " - << m_postprocessFbo->checkStatus(); - } -# endif - // finished with fbo, unbind to bind default - globjects::Framebuffer::unbind(); -} - -void NodeBasedRenderer::renderInternal( const ViewingParameters& renderData ) { - // Run the render graph - m_originalRenderGraph.execute(); -} - -// Draw debug stuff, do not overwrite depth map but do depth testing -void NodeBasedRenderer::debugInternal( const ViewingParameters& renderData ) { -# if 0 - if ( m_drawDebug ) - { - const ShaderProgram* shader; - - m_postprocessFbo->bind(); - GL_ASSERT( glDisable( GL_BLEND ) ); - GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); - GL_ASSERT( glDepthMask( GL_FALSE ) ); - GL_ASSERT( glDepthFunc( GL_LESS ) ); - - glDrawBuffers( 1, buffers ); - - for ( const auto& ro : m_debugRenderObjects ) - { - ro->render( RenderParameters{}, renderData ); - } - - DebugRender::getInstance()->render( renderData.viewMatrix, renderData.projMatrix ); - - m_postprocessFbo->unbind(); - - m_uiXrayFbo->bind(); - // Draw X rayed objects always on top of normal objects - GL_ASSERT( glDepthMask( GL_TRUE ) ); - GL_ASSERT( glClear( GL_DEPTH_BUFFER_BIT ) ); - for ( const auto& ro : m_xrayRenderObjects ) - { - if ( ro->isVisible() ) - { - shader = ro->getRenderTechnique()->getShader(); - - // bind data - shader->bind(); - // lighting for Xray : fixed - shader->setUniform( "light.color", Ra::Core::Utils::Color::Grey( 5.0 ) ); - shader->setUniform( "light.type", Light::LightType::DIRECTIONAL ); - shader->setUniform( "light.directional.direction", Ra::Core::Vector3( 0, -1, 0 ) ); - - Ra::Core::Matrix4 M = ro->getTransformAsMatrix(); - shader->setUniform( "transform.proj", renderData.projMatrix ); - shader->setUniform( "transform.view", renderData.viewMatrix ); - shader->setUniform( "transform.model", M ); - - ro->getRenderTechnique()->getMaterial()->bind( shader ); - - // render - ro->getMesh()->render(); - } - } - m_uiXrayFbo->unbind(); - } -# endif -} - -// Draw UI stuff, always drawn on top of everything else + clear ZMask -// TODO: NODEGRAPH! Unused ? -void NodeBasedRenderer::uiInternal( const ViewingParameters& renderData ) { -# if 0 - const ShaderProgram* shader; - - m_uiXrayFbo->bind(); - glDrawBuffers( 1, buffers ); - // Enable z-test - GL_ASSERT( glDepthMask( GL_TRUE ) ); - GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); - GL_ASSERT( glDepthFunc( GL_LESS ) ); - GL_ASSERT( glClear( GL_DEPTH_BUFFER_BIT ) ); - for ( const auto& ro : m_uiRenderObjects ) - { - if ( ro->isVisible() ) - { - shader = ro->getRenderTechnique()->getShader(); - - // bind data - shader->bind(); - - Ra::Core::Matrix4 M = ro->getTransformAsMatrix(); - Ra::Core::Matrix4 MV = renderData.viewMatrix * M; - Ra::Core::Vector3 V = MV.block<3, 1>( 0, 3 ); - Scalar d = V.norm(); - - Ra::Core::Matrix4 S = Ra::Core::Matrix4::Identity(); - S.coeffRef( 0, 0 ) = S.coeffRef( 1, 1 ) = S.coeffRef( 2, 2 ) = d; - - M = M * S; - - shader->setUniform( "transform.proj", renderData.projMatrix ); - shader->setUniform( "transform.view", renderData.viewMatrix ); - shader->setUniform( "transform.model", M ); - - ro->getRenderTechnique()->getMaterial()->bind( shader ); - - // render - ro->getMesh()->render(); - } - } - m_uiXrayFbo->unbind(); -# endif -} - -void NodeBasedRenderer::postProcessInternal( const ViewingParameters& /* renderData */ ) { - if ( m_colorTexture ) - { - m_postprocessFbo->bind(); - - // GL_ASSERT( glDrawBuffers( 1, buffers ) ); - GL_ASSERT( glDrawBuffer( GL_COLOR_ATTACHMENT0 ) ); - GL_ASSERT( glDisable( GL_DEPTH_TEST ) ); - GL_ASSERT( glDepthMask( GL_FALSE ) ); - - auto shader = m_postProcessEnabled - ? m_shaderProgramManager->getShaderProgram( "Hdr2Ldr" ) - : m_shaderProgramManager->getShaderProgram( "DrawScreen" ); - shader->bind(); - shader->setUniform( "screenTexture", m_colorTexture, 0 ); - m_quadMesh->render( shader ); - - GL_ASSERT( glDepthMask( GL_TRUE ) ); - GL_ASSERT( glEnable( GL_DEPTH_TEST ) ); - - m_postprocessFbo->unbind(); - } -} - -void NodeBasedRenderer::updateStepInternal( const ViewingParameters& renderData ) { - if ( m_reloadJson ) - { - reloadRenderGraphFromJson(); - if ( m_resetPath ) - { - m_jsonFilePath = m_jsonFilePath.substr( 0, m_jsonFilePath.rfind( '/' ) + 1 ); - m_resetPath = false; - } - m_reloadJson = false; - } - - if ( m_originalRenderGraph.m_recompile ) - { - std::cerr << "NodeBasedRenderer::updateStepInternal :Recompiling Graph\n"; - compileRenderGraph(); - m_originalRenderGraph.m_recompile = false; - } - - // Render objects - m_originalRenderGraph.getDataNode()->setElements( *allRenderObjects() ); - // Lights - std::vector lights; - for ( size_t i = 0; i < getLightManager()->count(); i++ ) - { lights.push_back( getLightManager()->getLight( i ) ); } - m_originalRenderGraph.getDataNode()->setElements( lights ); - // Cameras - std::vector cameras; - cameras.push_back( renderData ); - m_originalRenderGraph.getDataNode()->setElements( cameras ); - // Update the render graph - - m_originalRenderGraph.update(); -} - -void NodeBasedRenderer::setDisplayNode( DisplaySinkNode* displayNode ) { - m_displaySinkNode = displayNode; - m_displayObserverId = - m_displaySinkNode->attachMember( this, &NodeBasedRenderer::observeDisplaySink ); -} - -void NodeBasedRenderer::observeDisplaySink( const std::vector& graphOutput ) { - // TODO : find a way to make the renderer observable to manage ouput texture in the applicaiton - // gui if needed - std::cout << "NodeBasedRenderer::observeDisplaySink - connected textures (" - << graphOutput.size() << ") : \n"; - /* - for ( const auto t : graphOutput ) - { - if ( t ) { std::cout << "\t" << t->getName() << "\n"; } - else - { std::cout << "\tName not available yet. Must run the graph a first time\n"; } - } - */ - // Add display nodes' linked textures to secondary textures - m_secondaryTextures.clear(); - for ( const auto& t : m_sharedTextures ) - { m_secondaryTextures.insert( { t.first, t.second.get() } ); } - - bool colorTextureSet = false; - if ( m_displaySinkNode ) - { - auto textures = m_displaySinkNode->getTextures(); - for ( const auto t : textures ) - { -# ifdef GRAPH_CALL_TRACE - std::cout << t->getName() << std::endl; -# endif - if ( t ) - { - if ( !colorTextureSet ) - { - m_colorTexture = t; - colorTextureSet = true; - } - else - { m_secondaryTextures.insert( { t->getName(), t } ); } - } - } - } - - if ( !colorTextureSet ) - { - m_colorTexture = m_fancyTexture.get(); - colorTextureSet = true; - } -} - -} // namespace RadiumNBR -#endif diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp index a11e59bf2c9..0d63fc8dead 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp @@ -5,11 +5,6 @@ #include -#if 0 -# include -# include -#endif - namespace Ra { namespace Dataflow { namespace Rendering { @@ -23,11 +18,14 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph explicit RenderingGraph( const std::string& name ); ~RenderingGraph() override = default; + // Remove the 3 following methods if there is no need to specialize void init() override; bool addNode( Node* newNode ) override; bool removeNode( Node* node ) override; - void clearNodes() override; + // These methods are specialized to identify rendering nodes (and those who needs + // rendertechnique) + void clearNodes() override; bool compile() override; /// Sets the shader program manager @@ -38,14 +36,6 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph /// Resize all the rendering output void resize( uint32_t width, uint32_t height ); - /// Set the scene accessors on the graph - void setDataSources( std::vector* ros, std::vector* lights ); - /// Set the viewpoint on the graph - void setCameras( const CameraType* cameras ); - - /// get the computed texture vector - const std::vector& getImagesOutput() const; - /// Set render techniques needed by the rendering nodes void buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const; /// Return the typename of the Graph @@ -61,8 +51,6 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph /// List of nodes that requires some particular processing std::vector m_renderingNodes; // to resize std::vector m_rtIndexedNodes; // associate an index and buildRenderTechnique - - std::vector m_outputTextures; }; inline RenderingGraph::RenderingGraph( const std::string& name ) : diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index b6ced275d18..bc53b4be3d3 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -2,8 +2,11 @@ # This file should not be generated by the radium script # ---------------------------------------------------- -set(dataflow_rendering_sources Renderer/DataflowRenderer.cpp Renderer/RenderingGraph.cpp) +set(dataflow_rendering_sources Renderer/ControllableRenderer.cpp Renderer/RenderingGraph.cpp + Nodes/RenderingBuiltInsNodes.cpp +) -set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/DataflowRenderer.hpp - Renderer/RenderingGraph.hpp Nodes/Sources/Scene.hpp +set(dataflow_rendering_headers + Nodes/RenderingNode.hpp Renderer/ControllableRenderer.hpp Nodes/RenderingBuiltInsNodes.hpp + Renderer/RenderingGraph.hpp Nodes/Sources/Scene.hpp ) From 479589926ceeaaf59f8e60629955053d9824e882 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 15:23:50 +0100 Subject: [PATCH 177/239] [examples][dataflow] better comments in rendering example --- .../DataflowExamples/GraphRendering/main.cpp | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index f45089b0159..316d565149e 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -23,7 +23,7 @@ using namespace Ra::Dataflow::Rendering::Renderer; using namespace Ra::Dataflow::Rendering; /** - * SimpleWindow for demonstration + * Extending Ra::SimpleWindow with some menus for demonstration purpose */ class DemoWindowFactory : public BaseApplication::WindowFactory { @@ -110,33 +110,39 @@ class DemoWindowFactory : public BaseApplication::WindowFactory // Renderer controller #include -class MyRendererController : public DataflowRenderer::RenderGraphController +class MyRendererController : public RenderGraphController { public: - using DataflowRenderer::RenderGraphController::RenderGraphController; + using RenderGraphController::RenderGraphController; + /// Configuration function. /// Called once at the configuration of the renderer - void configure( DataflowRenderer* renderer, int w, int h ) override { + /// If a graph should be loaded at configure time, it was set on the controller using + /// deferredLoadGraph(...) before configuring the renderer. + void configure( ControllableRenderer* renderer, int w, int h ) override { LOG( logINFO ) << "MyRendererController::configure"; - DataflowRenderer::RenderGraphController::configure( renderer, w, h ); - m_renderGraph = std::make_unique( "Demonstration graph" ); - m_renderGraph->setShaderProgramManager( m_shaderMngr ); - auto sceneNode = new SceneNode( "Scene" ); - m_renderGraph->addNode( sceneNode ); + RenderGraphController::configure( renderer, w, h ); + if ( m_renderGraph == nullptr ) { + // a graph was not given on the command line, build a simple one + m_renderGraph = std::make_unique( "Demonstration graph" ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); + auto sceneNode = new SceneNode( "Scene" ); + m_renderGraph->addNode( sceneNode ); + } }; /// Resize function /// Called each time the renderer is resized void resize( int w, int h ) override { LOG( logINFO ) << "MyRendererController::resize"; - DataflowRenderer::RenderGraphController::resize( w, h ); + RenderGraphController::resize( w, h ); }; /// Update function /// Called once before each frame to update the internal state of the renderer void update( const Ra::Engine::Data::ViewingParameters& renderData ) override { LOG( logINFO ) << "MyRendererController::update"; - DataflowRenderer::RenderGraphController::update( renderData ); + RenderGraphController::update( renderData ); }; [[nodiscard]] std::string getRendererName() const override { return "Custom Node Renderer"; } @@ -180,7 +186,7 @@ int main( int argc, char* argv[] ) { MyRendererController graphController; if ( graphOption ) { graphController.deferredLoadGraph( *graphOption ); } - renderers.emplace_back( new DataflowRenderer( graphController ) ); + renderers.emplace_back( new ControllableRenderer( graphController ) ); app.initialize( DemoWindowFactory( renderers ) ); app.setContinuousUpdate( false ); From d5ffbc240cc64fd622a8c570bb4a6c2ac2ed6452 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 15:24:15 +0100 Subject: [PATCH 178/239] [examples][dataflow] add rendering factory to the node editor --- examples/DataflowExamples/GraphEditor/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/DataflowExamples/GraphEditor/CMakeLists.txt b/examples/DataflowExamples/GraphEditor/CMakeLists.txt index e3b4b73d69d..fdfdb2dcdf2 100644 --- a/examples/DataflowExamples/GraphEditor/CMakeLists.txt +++ b/examples/DataflowExamples/GraphEditor/CMakeLists.txt @@ -28,7 +28,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # /////////////////////////////// -find_package(Radium REQUIRED COMPONENTS DataflowQtGui) +find_package(Radium REQUIRED COMPONENTS Dataflow) # Qt utility functions include(QtFunctions) @@ -52,6 +52,6 @@ add_executable(${PROJECT_NAME} ${app_sources}) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) -target_link_libraries(${PROJECT_NAME} PUBLIC ${Qt_LIBRARIES} Radium::DataflowQtGui) +target_link_libraries(${PROJECT_NAME} PUBLIC ${Qt_LIBRARIES} Radium::Dataflow) configure_radium_app(NAME ${PROJECT_NAME}) From 42bed3370a8736c4547022872e8971870a0d4208 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 16:56:04 +0100 Subject: [PATCH 179/239] [dataflow][rendering] start autoregistration of rendering nodes --- src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index b7b624f2e1a..91aada58b69 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -1,6 +1,7 @@ #include #include +#include namespace Ra { namespace Dataflow { @@ -18,6 +19,10 @@ void registerRenderingNodesFactories() { /* --- operators --- */ + /* --- Graphs --- */ + renderingFactory->registerNodeCreator( + Renderer::RenderingGraph::getTypename() + "_", "Graph" ); + /* -- end --*/ Core::NodeFactoriesManager::registerFactory( renderingFactory ); } @@ -25,3 +30,7 @@ void registerRenderingNodesFactories() { } // namespace Rendering } // namespace Dataflow } // namespace Ra + +DATAFLOW_LIBRARY_INITIALIZER( RenderingNodes ) { + Ra::Dataflow::Rendering::registerRenderingNodesFactories(); +} From 065f9b1c6b3f2ea6202887c559de86515783114b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 17:56:46 +0100 Subject: [PATCH 180/239] [dataflow][rendering] move RenderingGraph class --- src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp | 6 +++--- src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp | 2 +- src/Dataflow/Rendering/{Renderer => }/RenderingGraph.cpp | 5 ++--- src/Dataflow/Rendering/{Renderer => }/RenderingGraph.hpp | 2 +- src/Dataflow/Rendering/{Renderer => }/RenderingGraph.inl | 0 src/Dataflow/Rendering/filelist.cmake | 4 ++-- 6 files changed, 9 insertions(+), 10 deletions(-) rename src/Dataflow/Rendering/{Renderer => }/RenderingGraph.cpp (95%) rename src/Dataflow/Rendering/{Renderer => }/RenderingGraph.hpp (99%) rename src/Dataflow/Rendering/{Renderer => }/RenderingGraph.inl (100%) diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 91aada58b69..f1584a7ad0a 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include namespace Ra { namespace Dataflow { @@ -20,8 +20,8 @@ void registerRenderingNodesFactories() { /* --- operators --- */ /* --- Graphs --- */ - renderingFactory->registerNodeCreator( - Renderer::RenderingGraph::getTypename() + "_", "Graph" ); + renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", + "Graph" ); /* -- end --*/ Core::NodeFactoriesManager::registerFactory( renderingFactory ); diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index 6e45148936b..38abc554c69 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -12,7 +12,7 @@ #include #include -#include +#include namespace globjects { class Framebuffer; diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp b/src/Dataflow/Rendering/RenderingGraph.cpp similarity index 95% rename from src/Dataflow/Rendering/Renderer/RenderingGraph.cpp rename to src/Dataflow/Rendering/RenderingGraph.cpp index c6e4a6dc6dc..d307c54f3b5 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/RenderingGraph.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -8,7 +8,6 @@ using namespace Ra::Engine::Rendering; namespace Ra { namespace Dataflow { namespace Rendering { -namespace Renderer { using namespace Ra::Dataflow::Rendering::Nodes; using namespace Ra::Dataflow::Core; @@ -70,7 +69,7 @@ void RenderingGraph::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* rt->updateGL(); ro->setRenderTechnique( rt ); } -} // namespace Renderer + } // namespace Rendering } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp b/src/Dataflow/Rendering/RenderingGraph.hpp similarity index 99% rename from src/Dataflow/Rendering/Renderer/RenderingGraph.hpp rename to src/Dataflow/Rendering/RenderingGraph.hpp index 0d63fc8dead..8b662902be2 100644 --- a/src/Dataflow/Rendering/Renderer/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/RenderingGraph.hpp @@ -8,7 +8,7 @@ namespace Ra { namespace Dataflow { namespace Rendering { -namespace Renderer { + using namespace Ra::Dataflow::Rendering::Nodes; using namespace Ra::Dataflow::Core; diff --git a/src/Dataflow/Rendering/Renderer/RenderingGraph.inl b/src/Dataflow/Rendering/RenderingGraph.inl similarity index 100% rename from src/Dataflow/Rendering/Renderer/RenderingGraph.inl rename to src/Dataflow/Rendering/RenderingGraph.inl diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index bc53b4be3d3..6be6fe7414a 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -2,11 +2,11 @@ # This file should not be generated by the radium script # ---------------------------------------------------- -set(dataflow_rendering_sources Renderer/ControllableRenderer.cpp Renderer/RenderingGraph.cpp +set(dataflow_rendering_sources Renderer/ControllableRenderer.cpp RenderingGraph.cpp Nodes/RenderingBuiltInsNodes.cpp ) set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/ControllableRenderer.hpp Nodes/RenderingBuiltInsNodes.hpp - Renderer/RenderingGraph.hpp Nodes/Sources/Scene.hpp + RenderingGraph.hpp Nodes/Sources/Scene.hpp ) From ac52443cf67fb169448258677f2ecc0515236a79 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 19:16:00 +0100 Subject: [PATCH 181/239] [dataflow][rendering] add texture sources and sink --- .../Nodes/RenderingBuiltInsNodes.cpp | 9 ++- .../Rendering/Nodes/RenderingNode.hpp | 5 +- .../Rendering/Nodes/Sinks/DisplaySinkNode.cpp | 70 +++++++++++++++++ .../Rendering/Nodes/Sinks/DisplaySinkNode.hpp | 50 ++++++++++++ .../Nodes/Sources/TextureSourceNode.cpp | 76 +++++++++++++++++++ .../Nodes/Sources/TextureSourceNode.hpp | 54 +++++++++++++ .../Renderer/ControllableRenderer.cpp | 27 ++++--- .../Renderer/ControllableRenderer.hpp | 14 ++-- src/Dataflow/Rendering/filelist.cmake | 8 +- 9 files changed, 293 insertions(+), 20 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp create mode 100644 src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index f1584a7ad0a..5f1a87788b7 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -1,6 +1,8 @@ #include +#include #include +#include #include namespace Ra { @@ -14,8 +16,13 @@ void registerRenderingNodesFactories() { /* --- Sources --- */ renderingFactory->registerNodeCreator( Nodes::SceneNode::getTypename() + "_", "Source" ); - + renderingFactory->registerNodeCreator( + Nodes::ColorTextureNode::getTypename() + "_", "Source" ); + renderingFactory->registerNodeCreator( + Nodes::DepthTextureNode::getTypename() + "_", "Source" ); /* --- Sinks --- */ + renderingFactory->registerNodeCreator( + Nodes::DisplaySinkNode::getTypename() + "_", "Sinks" ); /* --- operators --- */ diff --git a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp index 210e9ef28ed..213eb63248c 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp @@ -63,11 +63,14 @@ class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, protected: void toJsonInternal( nlohmann::json& data ) const override { - Dataflow::Core::Node::toJsonInternal( data ); + // Dataflow::Core::Node::toJsonInternal( data ); } bool fromJsonInternal( const nlohmann::json& data ) override { + /* auto r = Dataflow::Core::Node::fromJsonInternal( data ); return r; + */ + return true; } /// The renderer's shader program manager diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp new file mode 100644 index 00000000000..6b9585abef4 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp @@ -0,0 +1,70 @@ +#include + +#define MAX_DISPLAY_INPUTS 8 + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +DisplaySinkNode::DisplaySinkNode( const std::string& name ) : Node( name, getTypename() ) { + auto beautyTex = new PortIn( "Beauty", this ); + addInput( beautyTex ); + beautyTex->attachMember( this, &DisplaySinkNode::observeConnection ); + for ( size_t i = 0; i < DisplaySinkNode::MaxImages; i++ ) { + auto portIn = new PortIn( "AOV_" + std::to_string( i ), this ); + addInput( portIn ); + portIn->attachMember( this, &DisplaySinkNode::observeConnection ); + } + m_textures.resize( DisplaySinkNode::MaxImages ); +} + +DisplaySinkNode::~DisplaySinkNode() { + detachAll(); +} + +void DisplaySinkNode::execute() { + // TODO verify the robustness of this (address of port data stored in a vector ....) + if ( m_firstRun ) { + m_firstRun = false; + for ( size_t i = 0; i <= MAX_DISPLAY_INPUTS; i++ ) { + if ( m_inputs[i]->isLinked() ) { + auto input = static_cast*>( m_inputs[i].get() ); + m_textures[i] = &( input->getData() ); + auto interface = static_cast*>( m_interface[i] ); + interface->setData( m_textures[i] ); + } + else { + m_textures[i] = nullptr; + } + } + // not sure DisplaySink should be observable + this->notify( m_textures ); + } +} + +const std::vector& DisplaySinkNode::getTextures() { + return m_textures; +} + +void DisplaySinkNode::observeConnection( + const std::string& name, + // Will be used for efficient management of connection/de-connection + const PortIn& /* port */, + bool connected ) { + // deffer the port management to DisplaySinkNode::execute() + m_firstRun = true; +} + +const std::vector& DisplaySinkNode::buildInterfaces( Node* parent ) { + m_interface.clear(); + m_interface.shrink_to_fit(); + m_interface.reserve( 1 ); + auto interfacePort = new PortOut>( "Beauty+AOV", parent ); + m_interface.push_back( interfacePort ); + return m_interface; +} +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp new file mode 100644 index 00000000000..4fcfd9bc1c2 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp @@ -0,0 +1,50 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API DisplaySinkNode + : public Node, + public Ra::Core::Utils::Observable&> +{ + public: + static constexpr int MaxImages = 9; + + explicit DisplaySinkNode( const std::string& name ); + ~DisplaySinkNode() override; + + void execute() override; + + const std::vector& buildInterfaces( Node* parent ) override; + + static const std::string getTypename() { return "Display Sink"; } + + const std::vector& getTextures(); + + protected: + void toJsonInternal( nlohmann::json& ) const override {} + bool fromJsonInternal( const nlohmann::json& ) override { return true; } + + private: + std::vector m_textures; + + // the observer method + void + observeConnection( const std::string& name, const PortIn& port, bool connected ); + + bool m_firstRun { true }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp new file mode 100644 index 00000000000..463681591b3 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp @@ -0,0 +1,76 @@ +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +TextureSourceNode::TextureSourceNode( const std::string& instanceName, + const Ra::Engine::Data::TextureParameters& texParams ) : + TextureSourceNode( instanceName, getTypename(), texParams ) {} + +TextureSourceNode::TextureSourceNode( const std::string& instanceName, + const std::string& typeName, + const Ra::Engine::Data::TextureParameters& texParams ) : + RenderingNode( instanceName, typeName ) { + if ( !m_texture ) { m_texture = new Ra::Engine::Data::Texture( texParams ); } + + auto portOut = new PortOut( "texture", this ); + addOutput( portOut, m_texture ); +} + +void TextureSourceNode::execute() { + auto interface = static_cast*>( m_interface[0] ); + auto output = static_cast*>( m_outputs[0].get() ); + if ( interface->isLinked() ) { m_texture = &interface->getData(); } + output->setData( m_texture ); +} + +void TextureSourceNode::destroy() { + delete m_texture; +} + +void TextureSourceNode::resize( uint32_t width, uint32_t height ) { + m_texture->resize( width, height ); +} + +ColorTextureNode::ColorTextureNode( const std::string& name ) : + TextureSourceNode( name, + getTypename(), + Ra::Engine::Data::TextureParameters { name + " (Color)", + gl::GL_TEXTURE_2D, + 1, + 1, + 1, + gl::GL_RGBA, + gl::GL_RGBA32F, + gl::GL_FLOAT, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_LINEAR, + gl::GL_LINEAR, + nullptr } ) {} + +DepthTextureNode::DepthTextureNode( const std::string& name ) : + TextureSourceNode( name, + getTypename(), + Ra::Engine::Data::TextureParameters { name + " (Depth)", + gl::GL_TEXTURE_2D, + 1, + 1, + 1, + gl::GL_DEPTH_COMPONENT, + gl::GL_DEPTH_COMPONENT24, + gl::GL_UNSIGNED_INT, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_NEAREST, + gl::GL_NEAREST, + nullptr } ) {} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp new file mode 100644 index 00000000000..c648692e546 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp @@ -0,0 +1,54 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Dataflow::Core; + +class RA_DATAFLOW_API TextureSourceNode : public RenderingNode +{ + public: + TextureSourceNode( const std::string& instanceName, + const Ra::Engine::Data::TextureParameters& texParams ); + + void execute() override; + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + static const std::string getTypename() { return "TextureSource"; } + + protected: + TextureSourceNode( const std::string& instanceName, + const std::string& typeName, + const Ra::Engine::Data::TextureParameters& texParams ); + + private: + // TODO : editable parameter for TextureParameters ? + Ra::Engine::Data::Texture* m_texture { nullptr }; +}; + +class RA_DATAFLOW_API ColorTextureNode : public TextureSourceNode +{ + public: + explicit ColorTextureNode( const std::string& name ); + static const std::string getTypename() { return "Color Texture"; } +}; + +class RA_DATAFLOW_API DepthTextureNode : public TextureSourceNode +{ + public: + explicit DepthTextureNode( const std::string& name ); + static const std::string getTypename() { return "Depth Texture"; } +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp index 64e670ea232..af86646afa5 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp @@ -120,8 +120,9 @@ void ControllableRenderer::updateStepInternal( void ControllableRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { - if ( m_controller.render( allRenderObjects(), getLights(), &renderData ) ) { - m_colorTexture = nullptr; // TODO fetch the images from the controler + const auto& renderings = m_controller.render( allRenderObjects(), getLights(), &renderData ); + if ( renderings.size() > 0 ) { + m_colorTexture = renderings[0]; // allow to select which image to fetch } else { m_colorTexture = nullptr; @@ -206,10 +207,12 @@ bool RenderGraphController::buildRenderTechnique( Ra::Engine::Rendering::RenderO return false; } -bool RenderGraphController::render( std::vector* ros, - std::vector* lights, - const CameraType* cameras ) const { - // TODO, replace this kind of test by a call to a controller method +const std::vector& +RenderGraphController::render( std::vector* ros, + std::vector* lights, + const CameraType* cameras ) const { + m_images.clear(); + m_images.shrink_to_fit(); if ( m_renderGraph && m_renderGraph->m_ready ) { // set input data for ( const auto& [ptr, name, type] : m_renderGraphInputs ) { @@ -222,12 +225,18 @@ bool RenderGraphController::render( std::vector* ros, m_renderGraph->execute(); // get output - // TODO : get all the resulting images (not only the "Beauty" channel - return true; + // expect it is sufficient + m_images.reserve( m_renderGraphOutputs.size() * 4 ); + for ( const auto& [ptr, name, type] : m_renderGraphOutputs ) { + auto tex = ptr->getData(); + if ( tex != nullptr ) { m_images.push_back( tex ); } + } + LOG( Ra::Core::Utils::logINFO ) << " Graph executed. Got " << m_images.size() << " images!"; } else { - return false; + LOG( Ra::Core::Utils::logWARNING ) << " Graph not compiled : no images generated!"; } + return m_images; } void RenderGraphController::loadGraph( const std::string& filename ) { diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index 38abc554c69..08d2b331f40 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -90,9 +90,9 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer /// Render the given scene /// \return true if rendering output is available - virtual bool render( std::vector* ros, - std::vector* lights, - const CameraType* cameras ) const = 0; + virtual const std::vector& render( std::vector* ros, + std::vector* lights, + const CameraType* cameras ) const = 0; [[nodiscard]] virtual std::string getRendererName() const { return "Base RendererController"; @@ -202,9 +202,9 @@ class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::Rende /// Called each time the render techniques should be built. bool buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const override; - bool render( std::vector* ros, - std::vector* lights, - const CameraType* cameras ) const override; + const std::vector& render( std::vector* ros, + std::vector* lights, + const CameraType* cameras ) const override; [[nodiscard]] std::string getRendererName() const override { return "Node Renderer"; } @@ -218,8 +218,10 @@ class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::Rende /// The controlled graph. /// The controller own the graph and manage loading/saving of the renderer std::unique_ptr m_renderGraph { nullptr }; + mutable std::vector m_renderGraphInputs; mutable std::vector m_renderGraphOutputs; + mutable std::vector m_images; std::string m_graphToLoad; }; diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 6be6fe7414a..f4b729c493a 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -2,11 +2,13 @@ # This file should not be generated by the radium script # ---------------------------------------------------- -set(dataflow_rendering_sources Renderer/ControllableRenderer.cpp RenderingGraph.cpp - Nodes/RenderingBuiltInsNodes.cpp +set(dataflow_rendering_sources + Renderer/ControllableRenderer.cpp RenderingGraph.cpp Nodes/RenderingBuiltInsNodes.cpp + Nodes/Sinks/DisplaySinkNode.cpp Nodes/Sources/TextureSourceNode.cpp ) set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/ControllableRenderer.hpp Nodes/RenderingBuiltInsNodes.hpp - RenderingGraph.hpp Nodes/Sources/Scene.hpp + RenderingGraph.hpp Nodes/Sources/Scene.hpp Nodes/Sinks/DisplaySinkNode.hpp + Nodes/Sources/TextureSourceNode.hpp ) From 0cf44ac97534fca873e0b0af69fa9cda180d24a0 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 19:16:43 +0100 Subject: [PATCH 182/239] [examples][dataflow] add graph inspection in Graph rendering demo for debug purpose --- .../DataflowExamples/GraphRendering/main.cpp | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 316d565149e..6706a9b829e 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -108,10 +108,72 @@ class DemoWindowFactory : public BaseApplication::WindowFactory }; /* ----------------------------------------------------------------------------------- */ // Renderer controller +#include #include class MyRendererController : public RenderGraphController { + private: + static void inspectGraph( DataflowGraph& g ) { + // Factories used by the graph + auto factories = g.getNodeFactories(); + std::cout << "Used factories by the graph \"" << g.getInstanceName() << "\" with type \"" + << g.getTypeName() << "\" :\n"; + for ( const auto& f : *( factories.get() ) ) { + std::cout << "\t" << f.first << "\n"; + } + + // Nodes of the graph + auto nodes = g.getNodes(); + std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() + << ") :\n"; + for ( const auto& n : *( nodes ) ) { + std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() + << "\"\n"; + // Inspect input, output and interfaces of the node + std::cout << "\t\tInput ports :\n"; + for ( const auto& p : n->getInputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tOutput ports :\n"; + for ( const auto& p : n->getOutputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tInterface ports :\n"; + for ( const auto& p : n->getInterfaces() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + } + + // Nodes by level after the compilation + auto c = g.compile(); + auto cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto n : ( *cn )[i] ) { + std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + } + } + + // describe the graph interface : inputs and outputs port of the whole graph (not of the + // nodes) + std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; + auto inputs = g.getAllDataSetters(); + std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + } + auto outputs = g.getAllDataGetters(); + std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + } + } + public: using RenderGraphController::RenderGraphController; @@ -128,9 +190,14 @@ class MyRendererController : public RenderGraphController m_renderGraph->setShaderProgramManager( m_shaderMngr ); auto sceneNode = new SceneNode( "Scene" ); m_renderGraph->addNode( sceneNode ); + auto resultNode = new DisplaySinkNode( "Images" ); + m_renderGraph->addNode( resultNode ); + + inspectGraph( *m_renderGraph ); } }; +#if 0 /// Resize function /// Called each time the renderer is resized void resize( int w, int h ) override { @@ -144,7 +211,7 @@ class MyRendererController : public RenderGraphController LOG( logINFO ) << "MyRendererController::update"; RenderGraphController::update( renderData ); }; - +#endif [[nodiscard]] std::string getRendererName() const override { return "Custom Node Renderer"; } }; From 6792ebac2dd0da29a791cf92eaad67017dbfa0ee Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 22:48:52 +0100 Subject: [PATCH 183/239] [dataflow][rendering] add new node and improve renderer/controller --- .../Nodes/RenderNodes/ClearColorNode.cpp | 99 +++++++++++++++++++ .../Nodes/RenderNodes/ClearColorNode.hpp | 59 +++++++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 5 + .../Rendering/Nodes/RenderingNode.hpp | 4 +- .../Rendering/Nodes/Sinks/DisplaySinkNode.cpp | 30 +++--- .../Rendering/Nodes/Sinks/DisplaySinkNode.hpp | 5 +- .../Nodes/Sources/TextureSourceNode.cpp | 10 +- .../Nodes/Sources/TextureSourceNode.hpp | 4 +- .../Renderer/ControllableRenderer.cpp | 50 ++++++---- .../Renderer/ControllableRenderer.hpp | 2 + src/Dataflow/Rendering/filelist.cmake | 3 +- 11 files changed, 229 insertions(+), 42 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp new file mode 100644 index 00000000000..6b1c7018560 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp @@ -0,0 +1,99 @@ +#include + +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +using EnvironmentType = std::shared_ptr; + +ClearColorNode::ClearColorNode( const std::string& name ) : RenderingNode( name, getTypename() ) { + addInput( m_portInColorTex ); + m_portInColorTex->mustBeLinked(); + addInput( m_portInClearColor ); + addInput( m_portInEnvmap ); + addInput( m_portInCamera ); + + addOutput( m_portOutColorTex, m_colorTexture ); + + auto editableColor = new EditableParameter( "clear color", m_editableClearColor ); + addEditableParameter( editableColor ); +} + +void ClearColorNode::init() { + m_framebuffer = new globjects::Framebuffer(); +} + +void ClearColorNode::destroy() { + delete m_framebuffer; +} + +bool ClearColorNode::execute() { + + // Color texture + m_colorTexture = &m_portInColorTex->getData(); + m_portOutColorTex->setData( m_colorTexture ); + + // Clear color + Scalar* clearColor = m_editableClearColor.data(); + if ( m_portInClearColor->isLinked() ) { clearColor = m_portInClearColor->getData().data(); } + + // Envmap + EnvironmentTexture* envmap { nullptr }; + if ( m_portInEnvmap->isLinked() && m_portInCamera->isLinked() ) { + envmap = m_portInEnvmap->getData().get(); + } + + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + gl::glDisable( gl::GL_BLEND ); + + if ( envmap ) { + gl::glDepthMask( gl::GL_FALSE ); + gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + gl::glDisable( gl::GL_DEPTH_TEST ); + envmap->render( m_portInCamera->getData() ); + gl::glDepthMask( gl::GL_TRUE ); + gl::glEnable( gl::GL_DEPTH_TEST ); + } + else { + gl::glClearBufferfv( gl::GL_COLOR, 0, clearColor ); + } + + m_framebuffer->detach( gl::GL_COLOR_ATTACHMENT0 ); + m_framebuffer->unbind(); + return true; +} + +void ClearColorNode::toJsonInternal( nlohmann::json& data ) const { + auto c = ColorType ::linearRGBTosRGB( m_editableClearColor ); + std::array color { c.x(), c.y(), c.z() }; + data["clearColor"] = color; +} + +bool ClearColorNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "clearColor" ) ) { + std::array c = data["clearColor"]; + m_editableClearColor = ColorType ::sRGBToLinearRGB( ColorType( c[0], c[1], c[2] ) ); + } + else { + m_editableClearColor = + ColorType::sRGBToLinearRGB( ColorType( 42, 42, 42 ) * 1_ra / 255_ra ); + } + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp new file mode 100644 index 00000000000..28d4f2c31c1 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp @@ -0,0 +1,59 @@ +#pragma once +#include + +#include + +#include + +#include + +namespace Ra { + +namespace Engine { +namespace Data { +class EnvironmentTexture; +} +} // namespace Engine +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Dataflow::Core; +using namespace Ra::Engine::Data; +using EnvironmentType = std::shared_ptr; + +class RA_DATAFLOW_API ClearColorNode : public RenderingNode +{ + public: + explicit ClearColorNode( const std::string& name ); + + void init() override; + bool execute() override; + void destroy() override; + + void resize( uint32_t, uint32_t ) override {} + + static const std::string getTypename() { return "Clear Color Pass"; } + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + bool fromJsonInternal( const nlohmann::json& data ) override; + + private: + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + globjects::Framebuffer* m_framebuffer { nullptr }; + + ColorType m_editableClearColor { ColorType::Grey( 0.12 ) }; + + PortIn* m_portInColorTex { + new PortIn( "colorTextureToClear", this ) }; + PortIn* m_portInClearColor { new PortIn( "clearColor", this ) }; + PortIn* m_portInEnvmap { new PortIn( "environment", this ) }; + PortIn* m_portInCamera { new PortIn( "cameras", this ) }; + PortOut* m_portOutColorTex { new PortOut( "image", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 5f1a87788b7..bdbc3e5ee77 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -3,6 +3,9 @@ #include #include #include + +#include + #include namespace Ra { @@ -25,6 +28,8 @@ void registerRenderingNodesFactories() { Nodes::DisplaySinkNode::getTypename() + "_", "Sinks" ); /* --- operators --- */ + renderingFactory->registerNodeCreator( + Nodes::ClearColorNode::getTypename() + "_", "Render" ); /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", diff --git a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp index 213eb63248c..5a6f12efd68 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp @@ -62,10 +62,10 @@ class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, static const std::string getTypename() { return "RenderingNode"; } protected: - void toJsonInternal( nlohmann::json& data ) const override { + void toJsonInternal( nlohmann::json& ) const override { // Dataflow::Core::Node::toJsonInternal( data ); } - bool fromJsonInternal( const nlohmann::json& data ) override { + bool fromJsonInternal( const nlohmann::json& ) override { /* auto r = Dataflow::Core::Node::fromJsonInternal( data ); return r; diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp index 6b9585abef4..4ee1c7d767d 100644 --- a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp @@ -8,9 +8,8 @@ namespace Rendering { namespace Nodes { DisplaySinkNode::DisplaySinkNode( const std::string& name ) : Node( name, getTypename() ) { - auto beautyTex = new PortIn( "Beauty", this ); - addInput( beautyTex ); - beautyTex->attachMember( this, &DisplaySinkNode::observeConnection ); + addInput( m_beautyTex ); + m_beautyTex->attachMember( this, &DisplaySinkNode::observeConnection ); for ( size_t i = 0; i < DisplaySinkNode::MaxImages; i++ ) { auto portIn = new PortIn( "AOV_" + std::to_string( i ), this ); addInput( portIn ); @@ -18,29 +17,36 @@ DisplaySinkNode::DisplaySinkNode( const std::string& name ) : Node( name, getTyp } m_textures.resize( DisplaySinkNode::MaxImages ); } +void DisplaySinkNode::init() {} DisplaySinkNode::~DisplaySinkNode() { detachAll(); } -void DisplaySinkNode::execute() { +bool DisplaySinkNode::execute() { // TODO verify the robustness of this (address of port data stored in a vector ....) if ( m_firstRun ) { m_firstRun = false; - for ( size_t i = 0; i <= MAX_DISPLAY_INPUTS; i++ ) { + bool gotData { false }; + for ( size_t i = 0; i < DisplaySinkNode::MaxImages; i++ ) { if ( m_inputs[i]->isLinked() ) { - auto input = static_cast*>( m_inputs[i].get() ); - m_textures[i] = &( input->getData() ); - auto interface = static_cast*>( m_interface[i] ); - interface->setData( m_textures[i] ); + auto input = static_cast*>( m_inputs[i].get() ); + m_textures[i] = &( input->getData() ); + gotData = true; } else { m_textures[i] = nullptr; } } + auto interfacePort = static_cast>*>( m_interface[0] ); + if ( gotData ) { interfacePort->setData( &m_textures ); } + else { + interfacePort->setData( nullptr ); + } // not sure DisplaySink should be observable this->notify( m_textures ); } + return true; } const std::vector& DisplaySinkNode::getTextures() { @@ -48,10 +54,10 @@ const std::vector& DisplaySinkNode::getTextures() { } void DisplaySinkNode::observeConnection( - const std::string& name, + const std::string& /*name*/, // Will be used for efficient management of connection/de-connection const PortIn& /* port */, - bool connected ) { + bool /*connected*/ ) { // deffer the port management to DisplaySinkNode::execute() m_firstRun = true; } @@ -61,7 +67,7 @@ const std::vector& DisplaySinkNode::buildInterfaces( Node* parent ) { m_interface.shrink_to_fit(); m_interface.reserve( 1 ); auto interfacePort = new PortOut>( "Beauty+AOV", parent ); - m_interface.push_back( interfacePort ); + m_interface.emplace_back( interfacePort ); return m_interface; } } // namespace Nodes diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp index 4fcfd9bc1c2..a587f0c99d0 100644 --- a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp @@ -22,7 +22,8 @@ class RA_DATAFLOW_API DisplaySinkNode explicit DisplaySinkNode( const std::string& name ); ~DisplaySinkNode() override; - void execute() override; + bool execute() override; + void init() override; const std::vector& buildInterfaces( Node* parent ) override; @@ -37,6 +38,8 @@ class RA_DATAFLOW_API DisplaySinkNode private: std::vector m_textures; + PortIn* m_beautyTex { new PortIn( "Beauty", this ) }; + // the observer method void observeConnection( const std::string& name, const PortIn& port, bool connected ); diff --git a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp index 463681591b3..5fe5874dfbe 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp +++ b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp @@ -14,16 +14,14 @@ TextureSourceNode::TextureSourceNode( const std::string& instanceName, const Ra::Engine::Data::TextureParameters& texParams ) : RenderingNode( instanceName, typeName ) { if ( !m_texture ) { m_texture = new Ra::Engine::Data::Texture( texParams ); } - - auto portOut = new PortOut( "texture", this ); - addOutput( portOut, m_texture ); + addOutput( m_portOut, m_texture ); } -void TextureSourceNode::execute() { +bool TextureSourceNode::execute() { auto interface = static_cast*>( m_interface[0] ); - auto output = static_cast*>( m_outputs[0].get() ); if ( interface->isLinked() ) { m_texture = &interface->getData(); } - output->setData( m_texture ); + m_portOut->setData( m_texture ); + return true; } void TextureSourceNode::destroy() { diff --git a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp index c648692e546..ff45fe64fa8 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp +++ b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.hpp @@ -18,7 +18,7 @@ class RA_DATAFLOW_API TextureSourceNode : public RenderingNode TextureSourceNode( const std::string& instanceName, const Ra::Engine::Data::TextureParameters& texParams ); - void execute() override; + bool execute() override; void destroy() override; void resize( uint32_t width, uint32_t height ) override; @@ -32,6 +32,8 @@ class RA_DATAFLOW_API TextureSourceNode : public RenderingNode private: // TODO : editable parameter for TextureParameters ? Ra::Engine::Data::Texture* m_texture { nullptr }; + + PortOut* m_portOut { new PortOut( "texture", this ) }; }; class RA_DATAFLOW_API ColorTextureNode : public TextureSourceNode diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp index af86646afa5..033a9991d4b 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp @@ -175,34 +175,33 @@ void RenderGraphController::resize( int w, int h ) { if ( m_renderGraph ) { m_renderGraph->resize( m_width, m_height ); } } -void RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { - if ( m_renderGraph && !m_renderGraph->m_ready ) { +void RenderGraphController::compile( bool notifyObservers ) const { + if ( !m_renderGraph->m_ready ) { // compile the model m_renderGraph->compile(); // notify the view the model changes - notify(); + if ( notifyObservers ) { notify(); } // notify the model the view may have changed m_renderGraph->resize( m_width, m_height ); - // fetch the data setters and getters from the graph m_renderGraphInputs = m_renderGraph->getAllDataSetters(); m_renderGraphOutputs = m_renderGraph->getAllDataGetters(); } } +void RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { + if ( m_renderGraph ) { + // Compile and notify the observers in case of state change. + compile( true ); + } +} bool RenderGraphController::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { if ( m_renderGraph ) { - if ( !m_renderGraph->m_ready ) { - m_renderGraph->compile(); - m_renderGraph->resize( m_width, m_height ); - // fetch the data setters and getters from the graph - m_renderGraphInputs = m_renderGraph->getAllDataSetters(); - m_renderGraphOutputs = m_renderGraph->getAllDataGetters(); - } - if ( m_renderGraph->m_ready ) { - m_renderGraph->buildRenderTechnique( ro ); - return true; - } + // Only the first call of compile will effectively compile the graph + // do not notify observers here + compile(); + m_renderGraph->buildRenderTechnique( ro ); + return true; } return false; } @@ -226,12 +225,25 @@ RenderGraphController::render( std::vector* ros, // get output // expect it is sufficient - m_images.reserve( m_renderGraphOutputs.size() * 4 ); + m_images.reserve( m_renderGraphOutputs.size() * 9 ); for ( const auto& [ptr, name, type] : m_renderGraphOutputs ) { - auto tex = ptr->getData(); - if ( tex != nullptr ) { m_images.push_back( tex ); } + if ( ptr->getTypeName() == simplifiedDemangledType() ) { + // Try a simple texture + auto tex = ptr->getData(); + if ( tex != nullptr ) { m_images.push_back( tex ); } + continue; + } + if ( ptr->getTypeName() == simplifiedDemangledType>() ) { + // Try a texture vector + const auto& texv = ptr->getData>(); + for ( auto t : texv ) { + if ( t != nullptr ) { m_images.push_back( t ); } + } + continue; + } + LOG( Ra::Core::Utils::logWARNING ) << "Fetching from " << ptr->getName() << " (" + << ptr->getTypeName() << ") : type not supported !"; } - LOG( Ra::Core::Utils::logINFO ) << " Graph executed. Got " << m_images.size() << " images!"; } else { LOG( Ra::Core::Utils::logWARNING ) << " Graph not compiled : no images generated!"; diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index 08d2b331f40..dd37c3f958f 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -224,6 +224,8 @@ class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::Rende mutable std::vector m_images; std::string m_graphToLoad; + + void compile( bool notifyObservers = false ) const; }; } // namespace Renderer diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index f4b729c493a..75f2750605a 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -5,10 +5,11 @@ set(dataflow_rendering_sources Renderer/ControllableRenderer.cpp RenderingGraph.cpp Nodes/RenderingBuiltInsNodes.cpp Nodes/Sinks/DisplaySinkNode.cpp Nodes/Sources/TextureSourceNode.cpp + Nodes/RenderNodes/ClearColorNode.cpp ) set(dataflow_rendering_headers Nodes/RenderingNode.hpp Renderer/ControllableRenderer.hpp Nodes/RenderingBuiltInsNodes.hpp RenderingGraph.hpp Nodes/Sources/Scene.hpp Nodes/Sinks/DisplaySinkNode.hpp - Nodes/Sources/TextureSourceNode.hpp + Nodes/Sources/TextureSourceNode.hpp Nodes/RenderNodes/ClearColorNode.hpp ) From cfe56c9fc00412dbc49bbecb999f7ac0c81b7911 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 3 Nov 2022 22:49:05 +0100 Subject: [PATCH 184/239] [examples][dataflow] extends Graph rendering demo --- examples/DataflowExamples/GraphRendering/main.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 6706a9b829e..3953583233f 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -108,8 +108,10 @@ class DemoWindowFactory : public BaseApplication::WindowFactory }; /* ----------------------------------------------------------------------------------- */ // Renderer controller +#include #include #include +#include class MyRendererController : public RenderGraphController { @@ -192,8 +194,21 @@ class MyRendererController : public RenderGraphController m_renderGraph->addNode( sceneNode ); auto resultNode = new DisplaySinkNode( "Images" ); m_renderGraph->addNode( resultNode ); + auto textureSource = new ColorTextureNode( "Beauty" ); + m_renderGraph->addNode( textureSource ); + auto clearNode = new ClearColorNode( " Clear" ); + m_renderGraph->addNode( clearNode ); + + bool linksOK = true; + linksOK = m_renderGraph->addLink( + textureSource, "texture", clearNode, "colorTextureToClear" ); + linksOK = linksOK && m_renderGraph->addLink( clearNode, "image", resultNode, "Beauty" ); inspectGraph( *m_renderGraph ); + + // force recompilation and introspection of the graph by the renderer + m_renderGraph->m_ready = false; + notify(); } }; From 5e5467be35705c26f920b304944f3b7dcb91d517 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 07:47:11 +0100 Subject: [PATCH 185/239] [dataflow][rendering] Separate RenderGraphController from ConfigurableRenderer --- .../Renderer/ControllableRenderer.cpp | 124 +----------- .../Renderer/ControllableRenderer.hpp | 62 +----- .../Renderer/RenderGraphController.cpp | 177 ++++++++++++++++++ .../Renderer/RenderGraphController.hpp | 73 ++++++++ src/Dataflow/Rendering/filelist.cmake | 18 +- 5 files changed, 264 insertions(+), 190 deletions(-) create mode 100644 src/Dataflow/Rendering/Renderer/RenderGraphController.cpp create mode 100644 src/Dataflow/Rendering/Renderer/RenderGraphController.hpp diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp index 033a9991d4b..59b5bdecc2c 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp @@ -120,7 +120,7 @@ void ControllableRenderer::updateStepInternal( void ControllableRenderer::renderInternal( const Ra::Engine::Data::ViewingParameters& renderData ) { - const auto& renderings = m_controller.render( allRenderObjects(), getLights(), &renderData ); + const auto& renderings = m_controller.render( allRenderObjects(), getLights(), renderData ); if ( renderings.size() > 0 ) { m_colorTexture = renderings[0]; // allow to select which image to fetch } @@ -156,128 +156,6 @@ void ControllableRenderer::debugInternal( const Ra::Engine::Data::ViewingParamet void ControllableRenderer::uiInternal( const Ra::Engine::Data::ViewingParameters& ) {} -/* - * ************* - */ - -RenderGraphController::RenderGraphController() : ControllableRenderer::RendererController() {} - -void RenderGraphController::configure( ControllableRenderer* renderer, int w, int h ) { - ControllableRenderer::RendererController::configure( renderer, w, h ); - if ( !m_graphToLoad.empty() ) { - loadGraph( m_graphToLoad ); - m_graphToLoad = ""; - } -} - -void RenderGraphController::resize( int w, int h ) { - ControllableRenderer::RendererController::resize( w, h ); - if ( m_renderGraph ) { m_renderGraph->resize( m_width, m_height ); } -} - -void RenderGraphController::compile( bool notifyObservers ) const { - if ( !m_renderGraph->m_ready ) { - // compile the model - m_renderGraph->compile(); - // notify the view the model changes - if ( notifyObservers ) { notify(); } - // notify the model the view may have changed - m_renderGraph->resize( m_width, m_height ); - // fetch the data setters and getters from the graph - m_renderGraphInputs = m_renderGraph->getAllDataSetters(); - m_renderGraphOutputs = m_renderGraph->getAllDataGetters(); - } -} -void RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { - if ( m_renderGraph ) { - // Compile and notify the observers in case of state change. - compile( true ); - } -} - -bool RenderGraphController::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { - if ( m_renderGraph ) { - // Only the first call of compile will effectively compile the graph - // do not notify observers here - compile(); - m_renderGraph->buildRenderTechnique( ro ); - return true; - } - return false; -} - -const std::vector& -RenderGraphController::render( std::vector* ros, - std::vector* lights, - const CameraType* cameras ) const { - m_images.clear(); - m_images.shrink_to_fit(); - if ( m_renderGraph && m_renderGraph->m_ready ) { - // set input data - for ( const auto& [ptr, name, type] : m_renderGraphInputs ) { - if ( type == simplifiedDemangledType( *ros ) ) { ptr->setData( ros ); } - if ( type == simplifiedDemangledType( *lights ) ) { ptr->setData( lights ); } - if ( type == simplifiedDemangledType( *cameras ) ) { ptr->setData( cameras ); } - } - - // execute the graph - m_renderGraph->execute(); - - // get output - // expect it is sufficient - m_images.reserve( m_renderGraphOutputs.size() * 9 ); - for ( const auto& [ptr, name, type] : m_renderGraphOutputs ) { - if ( ptr->getTypeName() == simplifiedDemangledType() ) { - // Try a simple texture - auto tex = ptr->getData(); - if ( tex != nullptr ) { m_images.push_back( tex ); } - continue; - } - if ( ptr->getTypeName() == simplifiedDemangledType>() ) { - // Try a texture vector - const auto& texv = ptr->getData>(); - for ( auto t : texv ) { - if ( t != nullptr ) { m_images.push_back( t ); } - } - continue; - } - LOG( Ra::Core::Utils::logWARNING ) << "Fetching from " << ptr->getName() << " (" - << ptr->getTypeName() << ") : type not supported !"; - } - } - else { - LOG( Ra::Core::Utils::logWARNING ) << " Graph not compiled : no images generated!"; - } - return m_images; -} - -void RenderGraphController::loadGraph( const std::string& filename ) { - m_renderGraph.release(); - auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); - m_renderGraph = std::make_unique( graphName ); - m_renderGraph->setShaderProgramManager( m_shaderMngr ); - m_renderGraph->loadFromJson( filename ); - notify(); -} - -void RenderGraphController::deferredLoadGraph( const std::string& filename ) { - m_graphToLoad = filename; -} - -void RenderGraphController::saveGraph( const std::string& filename ) { - if ( m_renderGraph ) { - auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); - m_renderGraph->saveToJson( filename ); - m_renderGraph->setInstanceName( graphName ); - } -} - -void RenderGraphController::resetGraph() { - m_renderGraph.release(); - m_renderGraph = std::make_unique( "untitled" ); - m_renderGraph->setShaderProgramManager( m_shaderMngr ); -} - } // namespace Renderer } // namespace Rendering } // namespace Dataflow diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index dd37c3f958f..be88d19f2a2 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -23,10 +23,6 @@ namespace Dataflow { namespace Rendering { namespace Renderer { -/// Todo, put this somewhere else. This is needed to locate resources by client applications -/// Todo (bis), remove this requirement -extern int DataflowRendererMagic; - /** Dataflow renderer for the Radium Engine * This Renderer is fully configurable, either dynamically or programmatically. * It implements the Ra::Engine::Rendering/Renderer interface. @@ -92,7 +88,7 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer /// \return true if rendering output is available virtual const std::vector& render( std::vector* ros, std::vector* lights, - const CameraType* cameras ) const = 0; + const CameraType& cameras ) const = 0; [[nodiscard]] virtual std::string getRendererName() const { return "Base RendererController"; @@ -172,62 +168,6 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer std::vector m_lights; }; -/** - * RenderGraph controller - */ -class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::RendererController -{ - - public: - RenderGraphController(); - virtual ~RenderGraphController() = default; - RenderGraphController( const RenderGraphController& ) = delete; - RenderGraphController( const RenderGraphController&& ) = delete; - RenderGraphController& operator=( RenderGraphController&& ) = delete; - RenderGraphController& operator=( const RenderGraphController& ) = delete; - - /// Configuration function. - /// Called once at the configuration of the renderer - void configure( ControllableRenderer* renderer, int w, int h ) override; - - /// Resize function - /// Called each time the renderer is resized - void resize( int w, int h ) override; - - /// Update function - /// Called once before each frame to update the internal state of the renderer - void update( const Ra::Engine::Data::ViewingParameters& renderData ) override; - - /// RenderTechnique builder - /// Called each time the render techniques should be built. - bool buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const override; - - const std::vector& render( std::vector* ros, - std::vector* lights, - const CameraType* cameras ) const override; - - [[nodiscard]] std::string getRendererName() const override { return "Node Renderer"; } - - void loadGraph( const std::string& filename ); - void saveGraph( const std::string& filename ); - void resetGraph(); - /// Call this to set a graph to load before OpenGL is OK - void deferredLoadGraph( const std::string& filename ); - - protected: - /// The controlled graph. - /// The controller own the graph and manage loading/saving of the renderer - std::unique_ptr m_renderGraph { nullptr }; - - mutable std::vector m_renderGraphInputs; - mutable std::vector m_renderGraphOutputs; - mutable std::vector m_images; - - std::string m_graphToLoad; - - void compile( bool notifyObservers = false ) const; -}; - } // namespace Renderer } // namespace Rendering } // namespace Dataflow diff --git a/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp b/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp new file mode 100644 index 00000000000..17246993998 --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp @@ -0,0 +1,177 @@ +#include + +#include +#include +#include + +#include + +#include + +#include + +using namespace Ra::Engine; +using namespace Ra::Engine::Scene; +using namespace Ra::Engine::Data; +using namespace Ra::Engine::Rendering; +using namespace gl; + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { +using namespace Ra::Dataflow::Core; + +RenderGraphController::RenderGraphController() : ControllableRenderer::RendererController() {} + +void RenderGraphController::configure( ControllableRenderer* renderer, int w, int h ) { + ControllableRenderer::RendererController::configure( renderer, w, h ); + if ( !m_graphToLoad.empty() ) { + loadGraph( m_graphToLoad ); + m_graphToLoad = ""; + } +} + +void RenderGraphController::resize( int w, int h ) { + ControllableRenderer::RendererController::resize( w, h ); + if ( m_renderGraph ) { m_renderGraph->resize( m_width, m_height ); } +} + +void RenderGraphController::compile( bool notifyObservers ) const { + if ( !m_renderGraph->m_ready ) { + // compile the model + m_renderGraph->compile(); + // notify the view the model changes + if ( notifyObservers ) { notify(); } + // notify the model the view may have changed + m_renderGraph->resize( m_width, m_height ); + // fetch the data setters and getters from the graph + m_renderGraphInputs = m_renderGraph->getAllDataSetters(); + m_renderGraphOutputs = m_renderGraph->getAllDataGetters(); + } +} +void RenderGraphController::update( const Ra::Engine::Data::ViewingParameters& ) { + if ( m_renderGraph ) { + // Compile and notify the observers in case of state change. + compile( true ); + } +} + +bool RenderGraphController::buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const { + if ( m_renderGraph ) { + // Only the first call of compile will effectively compile the graph + // do not notify observers here + compile(); + m_renderGraph->buildRenderTechnique( ro ); + return true; + } + return false; +} + +const std::vector& +RenderGraphController::render( std::vector* ros, + std::vector* lights, + const CameraType& cameras ) const { + m_images.clear(); + m_images.shrink_to_fit(); + + // remove the const using const_cast as scene node expect non const object + auto& localCamera = const_cast( cameras ); + + bool status = false; + + if ( m_renderGraph && m_renderGraph->m_ready ) { + // set input data + for ( const auto& [ptr, name, type] : m_renderGraphInputs ) { + if ( type == simplifiedDemangledType( *ros ) ) { + ptr->setData( ros ); + m_renderGraph->activateDataSetter( name ); + } + if ( type == simplifiedDemangledType( *lights ) ) { + ptr->setData( lights ); + m_renderGraph->activateDataSetter( name ); + } + if ( type == simplifiedDemangledType( localCamera ) ) { + ptr->setData( &localCamera ); + m_renderGraph->activateDataSetter( name ); + } + } + + // execute the graph + status = m_renderGraph->execute(); + + if ( status ) { + // reset the status so that it will indicate that images are available + status = false; + // get output + // expect it is sufficient + m_images.reserve( m_renderGraphOutputs.size() * 9 ); + for ( const auto& [ptr, name, type] : m_renderGraphOutputs ) { + if ( ptr->hasData() ) { + status = true; + if ( ptr->getTypeName() == simplifiedDemangledType() ) { + // Try a simple texture + auto tex = ptr->getData(); + if ( tex != nullptr ) { m_images.push_back( tex ); } + continue; + } + if ( ptr->getTypeName() == + simplifiedDemangledType>() ) { + // Try a texture vector + const auto& texv = ptr->getData>(); + for ( auto t : texv ) { + if ( t != nullptr ) { m_images.push_back( t ); } + } + continue; + } + LOG( Ra::Core::Utils::logWARNING ) + << "Fetching from " << ptr->getName() << " (" << ptr->getTypeName() + << ") : type not supported !"; + } + } + } + } + + if ( !status ) { + LOG( Ra::Core::Utils::logWARNING ) << " Graph execution failed : no images generated!"; + } + + return m_images; +} + +void RenderGraphController::loadGraph( const std::string& filename ) { + auto loadedGraph = + dynamic_cast( DataflowGraph::loadGraphFromJsonFile( filename ) ); + if ( loadedGraph ) { + m_renderGraph.reset( loadedGraph ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); + notify(); + } + else { + LOG( Ra::Core::Utils::logERROR ) + << "RenderGraphController::loadGraph : unable to load " << filename; + } +} + +void RenderGraphController::deferredLoadGraph( const std::string& filename ) { + m_graphToLoad = filename; +} + +void RenderGraphController::saveGraph( const std::string& filename ) { + if ( m_renderGraph ) { + auto graphName = filename.substr( filename.find_last_of( '/' ) + 1 ); + m_renderGraph->saveToJson( filename ); + m_renderGraph->setInstanceName( graphName ); + } +} + +void RenderGraphController::resetGraph() { + m_renderGraph.release(); + m_renderGraph = std::make_unique( "untitled" ); + m_renderGraph->setShaderProgramManager( m_shaderMngr ); +} + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp b/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp new file mode 100644 index 00000000000..7c4f3ff9d06 --- /dev/null +++ b/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp @@ -0,0 +1,73 @@ +#pragma once +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Renderer { + +/** + * RenderGraph controller + */ +class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::RendererController +{ + + public: + RenderGraphController(); + virtual ~RenderGraphController() = default; + RenderGraphController( const RenderGraphController& ) = delete; + RenderGraphController( const RenderGraphController&& ) = delete; + RenderGraphController& operator=( RenderGraphController&& ) = delete; + RenderGraphController& operator=( const RenderGraphController& ) = delete; + + /// Configuration function. + /// Called once at the configuration of the renderer + void configure( ControllableRenderer* renderer, int w, int h ) override; + + /// Resize function + /// Called each time the renderer is resized + void resize( int w, int h ) override; + + /// Update function + /// Called once before each frame to update the internal state of the renderer + void update( const Ra::Engine::Data::ViewingParameters& renderData ) override; + + /// RenderTechnique builder + /// Called each time the render techniques should be built. + bool buildRenderTechnique( Ra::Engine::Rendering::RenderObject* ro ) const override; + + const std::vector& render( std::vector* ros, + std::vector* lights, + const CameraType& cameras ) const override; + + [[nodiscard]] std::string getRendererName() const override { return "Node Renderer"; } + + void loadGraph( const std::string& filename ); + void saveGraph( const std::string& filename ); + void resetGraph(); + /// Call this to set a graph to load before OpenGL is OK + void deferredLoadGraph( const std::string& filename ); + + // allow to "edit" the graph + RenderingGraph* getGraph() { return m_renderGraph.get(); } + + protected: + /// The controlled graph. + /// The controller own the graph and manage loading/saving of the renderer + std::unique_ptr m_renderGraph { nullptr }; + + mutable std::vector m_renderGraphInputs; + mutable std::vector m_renderGraphOutputs; + mutable std::vector m_images; + + std::string m_graphToLoad; + + void compile( bool notifyObservers = false ) const; +}; + +} // namespace Renderer +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 75f2750605a..447e0f852bb 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -3,13 +3,19 @@ # ---------------------------------------------------- set(dataflow_rendering_sources - Renderer/ControllableRenderer.cpp RenderingGraph.cpp Nodes/RenderingBuiltInsNodes.cpp - Nodes/Sinks/DisplaySinkNode.cpp Nodes/Sources/TextureSourceNode.cpp - Nodes/RenderNodes/ClearColorNode.cpp + Renderer/ControllableRenderer.cpp Renderer/RenderGraphController.cpp RenderingGraph.cpp + Nodes/RenderingBuiltInsNodes.cpp Nodes/Sinks/DisplaySinkNode.cpp + Nodes/Sources/TextureSourceNode.cpp Nodes/RenderNodes/ClearColorNode.cpp ) set(dataflow_rendering_headers - Nodes/RenderingNode.hpp Renderer/ControllableRenderer.hpp Nodes/RenderingBuiltInsNodes.hpp - RenderingGraph.hpp Nodes/Sources/Scene.hpp Nodes/Sinks/DisplaySinkNode.hpp - Nodes/Sources/TextureSourceNode.hpp Nodes/RenderNodes/ClearColorNode.hpp + Nodes/RenderingNode.hpp + Renderer/ControllableRenderer.hpp + Renderer/RenderGraphController.hpp + Nodes/RenderingBuiltInsNodes.hpp + RenderingGraph.hpp + Nodes/Sources/Scene.hpp + Nodes/Sinks/DisplaySinkNode.hpp + Nodes/Sources/TextureSourceNode.hpp + Nodes/RenderNodes/ClearColorNode.hpp ) From d810d3b042139bd1227d43174c462132187cd839 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 07:47:24 +0100 Subject: [PATCH 186/239] [examples][dataflow] update Graph rendering demo --- examples/DataflowExamples/GraphRendering/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 3953583233f..f740018d117 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include From ae6925308a8fa9c3441ddfb5cdba3832e930089d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sat, 5 Nov 2022 19:43:19 +0100 Subject: [PATCH 187/239] [dataflow][rendering] add simpleRenderNodes and allow resources path automatic init --- src/Dataflow/Rendering/CMakeLists.txt | 8 + .../Nodes/RenderNodes/SimpleRenderNode.cpp | 187 ++++++++++++++++++ .../Nodes/RenderNodes/SimpleRenderNode.hpp | 63 ++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 43 +++- .../Nodes/RenderingBuiltInsNodes.hpp | 4 +- .../Rendering/Nodes/RenderingNode.hpp | 19 ++ .../Shaders/LocalLightNode/shader.frag.glsl | 87 ++++++++ .../Rendering/Shaders/shader.vert.glsl | 39 ++++ src/Dataflow/Rendering/filelist.cmake | 14 +- 9 files changed, 457 insertions(+), 7 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/LocalLightNode/shader.frag.glsl create mode 100644 src/Dataflow/Rendering/Shaders/shader.vert.glsl diff --git a/src/Dataflow/Rendering/CMakeLists.txt b/src/Dataflow/Rendering/CMakeLists.txt index c5e2b13f0e1..830b876b24c 100644 --- a/src/Dataflow/Rendering/CMakeLists.txt +++ b/src/Dataflow/Rendering/CMakeLists.txt @@ -9,6 +9,7 @@ include(filelist.cmake) add_library( ${ra_dataflowrendering_target} SHARED ${dataflow_rendering_sources} ${dataflow_rendering_headers} + ${dataflow_rendering_resources} ) # This one should be extracted directly from parent project properties. @@ -32,6 +33,13 @@ configure_radium_package( PACKAGE_DIR "lib/cmake/Radium/${ra_dataflowrendering_target}" NAME_PREFIX "Radium" ) +foreach(resourceDir ${dataflow_rendering_resources}) + install_target_resources( + TARGET ${ra_dataflowrendering_target} RESOURCES_INSTALL_DIR "Radium/Dataflow" + RESOURCES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/${resourceDir} + ) +endforeach() + set(RADIUM_COMPONENTS ${RADIUM_COMPONENTS} ${ra_dataflowrendering_target} PARENT_SCOPE) if(RADIUM_ENABLE_PCH) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.cpp new file mode 100644 index 00000000000..796ef48ad9b --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.cpp @@ -0,0 +1,187 @@ +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +SimpleRenderNode::SimpleRenderNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inLights ); + m_inLights->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + Ra::Engine::Data::TextureParameters colorTexParams = { name, + gl::GL_TEXTURE_2D, + 1, + 1, + 1, + gl::GL_RGBA, + gl::GL_RGBA32F, + gl::GL_FLOAT, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_LINEAR, + gl::GL_LINEAR, + nullptr }; + m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams ); + addOutput( m_outColorTex, m_colorTexture ); + + Ra::Engine::Data::TextureParameters depthTexParams = { "Simple Depth image", + gl::GL_TEXTURE_2D, + 1, + 1, + 1, + gl::GL_DEPTH_COMPONENT, + gl::GL_DEPTH_COMPONENT24, + gl::GL_UNSIGNED_INT, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_NEAREST, + gl::GL_NEAREST, + nullptr }; + m_depthTexture = new Ra::Engine::Data::Texture( depthTexParams ); + addOutput( m_outDepthTex, m_depthTexture ); +} + +void SimpleRenderNode::init() { + m_framebuffer = new globjects::Framebuffer(); + + float blankAO[4] = { 1.f, 1.f, 1.f, 1.f }; + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.width = 1; + texParams.height = 1; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.minFilter = gl::GL_NEAREST; + texParams.magFilter = gl::GL_NEAREST; + texParams.name = "Blank AO"; + texParams.texels = &blankAO; + m_blankAO = new Ra::Engine::Data::Texture( texParams ); + m_blankAO->initializeGL(); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthMask( gl::GL_TRUE ); + m_nodeState->depthFunc( gl::GL_LEQUAL ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->blendFunc( gl::GL_ONE, gl::GL_ONE ); + m_nodeState->disable( gl::GL_BLEND ); +} + +void SimpleRenderNode::destroy() { + delete m_framebuffer; + delete m_colorTexture; + delete m_depthTexture; + delete m_blankAO; + + delete m_nodeState; +} + +void SimpleRenderNode::resize( uint32_t width, uint32_t height ) { + m_colorTexture->resize( width, height ); + m_depthTexture->resize( width, height ); + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, m_depthTexture->texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + m_framebuffer->unbind(); +} + +void SimpleRenderNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not used in EnvLightPass + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "LocalLightNode::" + mat->getMaterialName() } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + std::string resourcesRootDir = m_resourceDir + "Shaders/LocalLightNode/"; + // Build the shader configuration + Ra::Engine::Data::ShaderConfiguration theConfig { + { "LocalLightNode::" + mat->getMaterialName() }, + m_resourceDir + "Shaders/shader.vert.glsl", + resourcesRootDir + "shader.frag.glsl" }; + // add the material interface to the fragment shader + theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"", + Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechnique + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool SimpleRenderNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + // Lights + auto& lights = m_inLights->getData(); + + // Compute the result + m_framebuffer->bind(); + + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + + float clearColor[4] = { 0.02f, 0.03f, 0.04f, 0.0f }; + float clearDepth = 1.0f; + gl::glClearBufferfv( gl::GL_COLOR, 0, clearColor ); + gl::glClearBufferfv( gl::GL_DEPTH, 0, &clearDepth ); + + if ( lights.size() > 0 ) { + bool first_light = true; + for ( const auto& l : lights ) { + Ra::Engine::Data::RenderParameters inPassParams; + inPassParams.addParameter( "amb_occ_sampler", m_blankAO ); + l->getRenderParameters( inPassParams ); + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + if ( first_light ) { + // break; + first_light = false; + gl::glEnable( gl::GL_BLEND ); + gl::glBlendFunc( gl::GL_ONE, gl::GL_ONE ); + gl::glDepthMask( gl::GL_FALSE ); + } + } + } + + currentState->apply(); + + m_framebuffer->unbind(); + + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.hpp new file mode 100644 index 00000000000..c7f8d0a38d6 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SimpleRenderNode.hpp @@ -0,0 +1,63 @@ +#pragma once +#include + +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Simple render node : render only opaque objects with direct lighting. + * + */ +class RA_DATAFLOW_API SimpleRenderNode : public RenderingNode +{ + public: + explicit SimpleRenderNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Simple Render Node"; } + + private: + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + Ra::Engine::Data::Texture* m_depthTexture { nullptr }; + + Ra::Engine::Data::Texture* m_blankAO { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn>* m_inLights { + new PortIn>( "lights", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outColorTex { new PortOut( "Beauty", this ) }; + PortOut* m_outDepthTex { new PortOut( "Depth AOV", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index bdbc3e5ee77..cf109f620a8 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -1,18 +1,43 @@ #include +DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include #include +#include #include +#include + namespace Ra { namespace Dataflow { namespace Rendering { -void registerRenderingNodesFactories() { +namespace Nodes { +// This is here right now, can be moved if requested : +std::string RenderingNode::s_defaultResourceDir; +} // namespace Nodes + +/// Todo, put this somewhere else. This is needed to locate resources by client applications +/// Todo (bis), remove this requirement +static int DataflowRendererMagic = 0x01020304; + +std::string registerRenderingNodesFactories() { + auto resourcesCheck = Ra::Core::Resources::getResourcesPath( + reinterpret_cast( &DataflowRendererMagic ), { "Resources/Radium/Dataflow" } ); + if ( !resourcesCheck ) { + LOG( Ra::Core::Utils::logERROR ) << "Unable to find resources for renderingNodeFactory, " + "setting it to current directory!"; + resourcesCheck = "./"; + } + auto resourcesPath { *resourcesCheck }; + + LOG( Ra::Core::Utils::logINFO ) + << "Resources for renderingNodes will be fetched from " << resourcesPath; + Core::NodeFactorySet::mapped_type renderingFactory { new Core::NodeFactorySet::mapped_type::element_type( "RenderingNodes" ) }; @@ -31,18 +56,30 @@ void registerRenderingNodesFactories() { renderingFactory->registerNodeCreator( Nodes::ClearColorNode::getTypename() + "_", "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new SimpleRenderNode( "SimpleRender_" + + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", "Graph" ); /* -- end --*/ Core::NodeFactoriesManager::registerFactory( renderingFactory ); + + return resourcesPath; } } // namespace Rendering } // namespace Dataflow } // namespace Ra -DATAFLOW_LIBRARY_INITIALIZER( RenderingNodes ) { - Ra::Dataflow::Rendering::registerRenderingNodesFactories(); +DATAFLOW_LIBRARY_INITIALIZER_IMPL( RenderingNodes ) { + Ra::Dataflow::Rendering::Nodes::RenderingNode::s_defaultResourceDir = + Ra::Dataflow::Rendering::registerRenderingNodesFactories(); } diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp index f097aefc521..407e96740b3 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.hpp @@ -10,8 +10,10 @@ namespace Rendering { * * If needed, the definition of all the rendering nodes can be included using one of the headers * - #include + * + * \return the default resources location for rendering nodes */ -RA_DATAFLOW_API void registerRenderingNodesFactories(); +RA_DATAFLOW_API std::string registerRenderingNodesFactories(); } // namespace Rendering } // namespace Dataflow } // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp index 5a6f12efd68..dfd8e78f23e 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderingNode.hpp @@ -13,6 +13,8 @@ namespace Ra::Engine::Data { class ShaderProgramManager; } +void RenderingNodes__Initializer(); + namespace Ra { namespace Dataflow { namespace Rendering { @@ -54,11 +56,21 @@ class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, /// Indicate if the node needs to setup a rendertechnique on RenderObjects virtual bool hasRenderTechnique() { return false; } + /// Initialize shaders internally used by the node. + /// This method should be override by rendering nodes that requires specific shaders, + /// independent from renderObjects, to compute their result. + virtual bool initInternalShaders() { return true; } + /// Sets the shader program manager void setShaderProgramManager( Ra::Engine::Data::ShaderProgramManager* shaderMngr ) { m_shaderMngr = shaderMngr; } + /// Sets the filesystem (real or virtual) location for the node resources (shaders, ...) + void setResourcesDir( std::string resourcesRootDir ) { + m_resourceDir = std::move( resourcesRootDir ); + } + static const std::string getTypename() { return "RenderingNode"; } protected: @@ -75,6 +87,13 @@ class RA_DATAFLOW_API RenderingNode : public Dataflow::Core::Node, /// The renderer's shader program manager Ra::Engine::Data::ShaderProgramManager* m_shaderMngr; + /// The resource directory to use + std::string m_resourceDir { s_defaultResourceDir }; + + private: + friend DATAFLOW_LIBRARY_INITIALIZER_DECL( ::RenderingNodes ); + /// The default resources directory for nodes, set once + static std::string s_defaultResourceDir; }; } // namespace Nodes diff --git a/src/Dataflow/Rendering/Shaders/LocalLightNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/LocalLightNode/shader.frag.glsl new file mode 100644 index 00000000000..cd1d4df8589 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/LocalLightNode/shader.frag.glsl @@ -0,0 +1,87 @@ +/* +the functions used to discard or shade the fragment are given by the implementation of the Material +Interface. The appropriate code will be added at runtime by the shader composer or the render pass +initialiser. +*/ +#include "DefaultLight.glsl" +#include "VertexAttribInterface.frag.glsl" + +layout( location = 5 ) in vec3 in_viewVector; +layout( location = 6 ) in vec3 in_lightVector; + +uniform sampler2D amb_occ_sampler; + +layout( location = 0 ) out vec4 out_color; // Position in World Space + +//------------------- main --------------------- + +void main() { + /// quick access to texture coordinates + vec3 tc = getPerVertexTexCoord(); + // discard non opaque fragment + vec4 bc = getBaseColor( material, tc ); + if ( toDiscard( material, bc ) ) discard; + +#ifdef GLTF_MATERIAL_INTERFACE + // Experiment on a new GLSL/Material interface allowing more efficient PBR and composition + + // Compute the normal closure. Do normal map and capture state about normal and tangent space + NormalInfo nrm_info = getNormalInfo( material.baseMaterial, tc ); + + // Compute the BSDF closure. Compte all view and lighting independant propserties on the + // material + MaterialInfo bsdf_params; + extractBSDFParameters( material, tc, nrm_info, bsdf_params ); + + // get dynamic lighting informations : wiew and light properties + vec3 wo = normalize( in_viewVector ); // outgoing direction + + // Per local light computation + vec3 wi = normalize( in_lightVector ); // incident direction + vec3 lighting = lightContributionFrom( light, getWorldSpacePosition().xyz ); + + // evaluate the BSDF : get a set of layers representing several optical effects + BsdfInfo layers = evaluateBSDF( material, + bsdf_params, + nrm_info, + wi, + wo, + lighting ); + + // Final evaluation of BSDF/layers closure + vec3 color = combineLayers( bsdf_params, layers, nrm_info, wo ); + +#else + // All vectors are in world space + // A material is always evaluated in the fragment local Frame + // compute matrix from World to local Frame + vec3 normalWorld = getWorldSpaceNormal(); // normalized interpolated normal + vec3 tangentWorld = getWorldSpaceTangent(); // normalized tangent + vec3 binormalWorld = getWorldSpaceBiTangent(); // normalized bitangent + + // Apply normal mapping + normalWorld = getNormal( material, + tc, + normalWorld, + tangentWorld, + binormalWorld ); // normalized bump-mapped normal + binormalWorld = normalize( cross( normalWorld, tangentWorld ) ); // normalized tangent + tangentWorld = normalize( cross( binormalWorld, normalWorld ) ); // normalized bitangent + + mat3 world2local; + world2local[0] = vec3( tangentWorld.x, binormalWorld.x, normalWorld.x ); + world2local[1] = vec3( tangentWorld.y, binormalWorld.y, normalWorld.y ); + world2local[2] = vec3( tangentWorld.z, binormalWorld.z, normalWorld.z ); + // transform all vectors in local frame so that N = (0, 0, 1); + vec3 lightDir = world2local * normalize( in_lightVector ); // incident direction + vec3 viewDir = world2local * normalize( in_viewVector ); // outgoing direction + + vec3 color = evaluateBSDF( material, tc, lightDir, viewDir ) * + lightContributionFrom( light, getWorldSpacePosition().xyz ); +#endif + + vec2 size = textureSize( amb_occ_sampler, 0 ).xy; + vec3 ao = texture( amb_occ_sampler, gl_FragCoord.xy / size ).rgb; + + out_color = vec4( color * ao, 1.0 ); +} diff --git a/src/Dataflow/Rendering/Shaders/shader.vert.glsl b/src/Dataflow/Rendering/Shaders/shader.vert.glsl new file mode 100644 index 00000000000..34610cd1f0d --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/shader.vert.glsl @@ -0,0 +1,39 @@ +#include "TransformStructs.glsl" +#include "DefaultLight.glsl" + +layout (location = 0) in vec3 in_position; +layout (location = 1) in vec3 in_normal; +layout (location = 2) in vec3 in_tangent; +layout (location = 3) in vec3 in_bitangent; +layout (location = 4) in vec3 in_texcoord; +layout (location = 5) in vec4 in_color; + +uniform Transform transform; + +layout (location = 0) out vec3 out_position;// Position in World Space +layout (location = 1) out vec3 out_normal; +layout (location = 2) out vec3 out_texcoord;// Used in case of normal mapping +layout (location = 3) out vec3 out_vertexcolor; +layout (location = 4) out vec3 out_tangent; +layout (location = 5) out vec3 out_viewVector; +layout (location = 6) out vec3 out_lightVector; + + +void main() +{ + mat4 mvp = transform.proj * transform.view * transform.model; + gl_Position = mvp * vec4(in_position, 1.0); + + vec4 pos = transform.model * vec4(in_position, 1.0); + pos /= pos.w; + + vec3 eye = -transform.view[3].xyz * mat3(transform.view); + + out_position = vec3(pos); + out_texcoord = in_texcoord; + out_normal = normalize(mat3(transform.worldNormal) * in_normal); + out_tangent = normalize(mat3(transform.model) * in_tangent); + out_viewVector = normalize(eye - pos.xyz); + out_lightVector = getLightDirection(light, pos.xyz); + out_vertexcolor = in_color.rgb; +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 447e0f852bb..6718d0f5319 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -3,9 +3,14 @@ # ---------------------------------------------------- set(dataflow_rendering_sources - Renderer/ControllableRenderer.cpp Renderer/RenderGraphController.cpp RenderingGraph.cpp - Nodes/RenderingBuiltInsNodes.cpp Nodes/Sinks/DisplaySinkNode.cpp - Nodes/Sources/TextureSourceNode.cpp Nodes/RenderNodes/ClearColorNode.cpp + Renderer/ControllableRenderer.cpp + Renderer/RenderGraphController.cpp + RenderingGraph.cpp + Nodes/RenderingBuiltInsNodes.cpp + Nodes/Sinks/DisplaySinkNode.cpp + Nodes/Sources/TextureSourceNode.cpp + Nodes/RenderNodes/ClearColorNode.cpp + Nodes/RenderNodes/SimpleRenderNode.cpp ) set(dataflow_rendering_headers @@ -18,4 +23,7 @@ set(dataflow_rendering_headers Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/TextureSourceNode.hpp Nodes/RenderNodes/ClearColorNode.hpp + Nodes/RenderNodes/SimpleRenderNode.hpp ) + +set(dataflow_rendering_resources Shaders) From ce0f45eb95a20a8959e2b26a1ac9eef4ecf2a28d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 7 Nov 2022 13:06:16 +0100 Subject: [PATCH 188/239] [dataflow][rendering] compiling rendering graphs --- src/Dataflow/Rendering/RenderingGraph.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dataflow/Rendering/RenderingGraph.cpp b/src/Dataflow/Rendering/RenderingGraph.cpp index d307c54f3b5..9773d52f07a 100644 --- a/src/Dataflow/Rendering/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/RenderingGraph.cpp @@ -48,6 +48,7 @@ bool RenderingGraph::compile() { m_rtIndexedNodes.push_back( renderNode ); } renderNode->setShaderProgramManager( m_shaderMngr ); + renderNode->initInternalShaders(); } } } From 3ae517e59b3ae3fc037008a4f6ebb3912f977c40 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 4 Nov 2022 15:18:41 +0100 Subject: [PATCH 189/239] [example][dataflow] modify rendering demo. --- .../DataflowExamples/GraphRendering/main.cpp | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index f740018d117..a7cb6977177 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -12,6 +12,8 @@ #include #include +#include + // Qt Widgets #include @@ -83,13 +85,52 @@ class DemoWindowFactory : public BaseApplication::WindowFactory int renderNum = 0; for ( const auto& rndr : renderers ) { + std::cout << "adding renderer : " << rndr->getRendererName() << "\n"; + window->addRenderer( rndr->getRendererName(), rndr ); auto rndAct = new QAction( rndr->getRendererName().c_str(), window ); renderMenu->addAction( rndAct ); QAction::connect( rndAct, &QAction::triggered, [renderNum, window]() { window->getViewer()->changeRenderer( renderNum ); - window->getViewer()->needUpdate(); + // window->getViewer()->needUpdate(); } ); + + // nodeToolBar* ff; + // build a toolbar for the renderers + if ( rndr->getRendererName() == "Custom Node Renderer" ) { + auto nodeToolBar = window->addToolBar( "NodeGraph control" ); + nodeToolBar->setObjectName( "Edit Graph" ); + auto grphAction = new QAction( "Edit Graph", window ); + nodeToolBar->addAction( grphAction ); + nodeToolBar->hide(); + auto* cnfgRndr = + dynamic_cast( + rndr.get() ); + + QAction::connect( grphAction, &QAction::triggered, [cnfgRndr, window]() { + auto& nbrCtrl = + dynamic_cast( + cnfgRndr->getController() ); + auto editor = new Ra::Dataflow::QtGui::GraphEditor::GraphEditorWindow( + nbrCtrl.getGraph() ); + Ra::Dataflow::QtGui::GraphEditor::GraphEditorWindow::connect( + editor, + &Ra::Dataflow::QtGui::GraphEditor::GraphEditorWindow::needUpdate, + [window]() { window->getViewer()->needUpdate(); } ); + editor->show(); + } ); + QAction::connect( + rndAct, &QAction::triggered, [nodeToolBar]() { nodeToolBar->show(); } ); + } + else { + QAction::connect( rndAct, &QAction::triggered, [window]() { + auto toolbars = window->findChildren(); + for ( auto tb : toolbars ) { + if ( tb->objectName() == "Edit Graph" ) { tb->hide(); } + } + } ); + } + ++renderNum; } } @@ -109,7 +150,10 @@ class DemoWindowFactory : public BaseApplication::WindowFactory /* ----------------------------------------------------------------------------------- */ // Renderer controller #include +#include + #include + #include #include @@ -194,6 +238,7 @@ class MyRendererController : public RenderGraphController m_renderGraph->addNode( sceneNode ); auto resultNode = new DisplaySinkNode( "Images" ); m_renderGraph->addNode( resultNode ); +#if 0 auto textureSource = new ColorTextureNode( "Beauty" ); m_renderGraph->addNode( textureSource ); auto clearNode = new ClearColorNode( " Clear" ); @@ -203,6 +248,25 @@ class MyRendererController : public RenderGraphController linksOK = m_renderGraph->addLink( textureSource, "texture", clearNode, "colorTextureToClear" ); linksOK = linksOK && m_renderGraph->addLink( clearNode, "image", resultNode, "Beauty" ); +#endif + auto simpleRenderNode = new SimpleRenderNode( "renderOperator" ); + m_renderGraph->addNode( simpleRenderNode ); + bool linksOK = true; + linksOK = linksOK && + m_renderGraph->addLink( sceneNode, "objects", simpleRenderNode, "objects" ); + linksOK = linksOK && + m_renderGraph->addLink( sceneNode, "camera", simpleRenderNode, "camera" ); + linksOK = linksOK && + m_renderGraph->addLink( sceneNode, "lights", simpleRenderNode, "lights" ); + linksOK = linksOK && + m_renderGraph->addLink( simpleRenderNode, "Beauty", resultNode, "Beauty" ); + linksOK = linksOK && + m_renderGraph->addLink( simpleRenderNode, "Depth AOV", resultNode, "AOV_0" ); + + if ( !linksOK ) { LOG( logERROR ) << "Something went wrong when linking nodes !!! "; } + else { + LOG( logINFO ) << "Graph linked successfully!!! "; + } inspectGraph( *m_renderGraph ); From 339e8272470a7e165ee807b8c32e74ded033d9f7 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sat, 5 Nov 2022 16:09:20 +0100 Subject: [PATCH 190/239] [dataflow][rendering] add environment map source node --- .../Nodes/RenderingBuiltInsNodes.cpp | 5 ++ .../Nodes/Sources/EnvMapSourceNode.hpp | 84 +++++++++++++++++++ src/Dataflow/Rendering/filelist.cmake | 13 +-- 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index cf109f620a8..036ba86a3f3 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -2,6 +2,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include +#include #include #include @@ -48,6 +49,10 @@ std::string registerRenderingNodesFactories() { Nodes::ColorTextureNode::getTypename() + "_", "Source" ); renderingFactory->registerNodeCreator( Nodes::DepthTextureNode::getTypename() + "_", "Source" ); + + renderingFactory->registerNodeCreator( + Nodes::EnvMapSourceNode::getTypename() + "_", "Source" ); + /* --- Sinks --- */ renderingFactory->registerNodeCreator( Nodes::DisplaySinkNode::getTypename() + "_", "Sinks" ); diff --git a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp new file mode 100644 index 00000000000..569edd4b726 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp @@ -0,0 +1,84 @@ +#pragma once +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +using namespace Ra::Dataflow; + +namespace Rendering { +using namespace Ra::Engine::Data; +using EnvmapPtrType = std::shared_ptr; + +namespace Nodes { + +using EnvMapSourceNode = Core::Sources::SingleDataSourceNode; + +} +} // namespace Rendering + +namespace Core { +namespace Sources { +template <> +inline const std::string& SingleDataSourceNode::getTypename() { + static std::string typeName { "Source" }; + return typeName; +} + +template <> +inline SingleDataSourceNode::SingleDataSourceNode( + const std::string& name ) : + SingleDataSourceNode( name, SingleDataSourceNode::getTypename() ) { + setEditable( "EnvMap" ); +} + +template <> +inline void +SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { + auto envTex = getData(); + if ( envTex ) { + data["files"] = ( *envTex )->getImageName().c_str(); + data["strength"] = ( *envTex )->getStrength() * 100.; + } +} + +template <> +inline bool +SingleDataSourceNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "files" ) ) { + std::string files = data["files"]; + float strength = data["strength"]; + float s = strength / 100.; + // check if the file exists + bool envmap_exist; + auto pos = files.find( ';' ); + if ( pos != std::string::npos ) { + std::string f1 = files.substr( 0, pos - 1 ); + envmap_exist = std::filesystem::exists( f1 ); + } + else { + envmap_exist = std::filesystem::exists( files ); + } + if ( envmap_exist ) { + auto envmp = std::make_shared( files, true ); + envmp->setStrength( s ); + setData( envmp ); + } + else { + setData( nullptr ); + } + } + else { + setData( nullptr ); + } + return true; +} + +} // namespace Sources +} // namespace Core +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 6718d0f5319..ec327844f85 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -14,16 +14,17 @@ set(dataflow_rendering_sources ) set(dataflow_rendering_headers + Nodes/RenderingBuiltInsNodes.hpp Nodes/RenderingNode.hpp + Nodes/RenderNodes/ClearColorNode.hpp + Nodes/RenderNodes/SimpleRenderNode.hpp + Nodes/Sinks/DisplaySinkNode.hpp + Nodes/Sources/Scene.hpp + Nodes/Sources/EnvMapSourceNode.hpp + Nodes/Sources/TextureSourceNode.hpp Renderer/ControllableRenderer.hpp Renderer/RenderGraphController.hpp - Nodes/RenderingBuiltInsNodes.hpp RenderingGraph.hpp - Nodes/Sources/Scene.hpp - Nodes/Sinks/DisplaySinkNode.hpp - Nodes/Sources/TextureSourceNode.hpp - Nodes/RenderNodes/ClearColorNode.hpp - Nodes/RenderNodes/SimpleRenderNode.hpp ) set(dataflow_rendering_resources Shaders) From 22b7ac2bda7ea96e09c801b70a3afcad815b31a5 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sat, 5 Nov 2022 16:57:15 +0100 Subject: [PATCH 191/239] [dataflow][rendering] add geometry aovs node --- .../Nodes/RenderNodes/GeometryAovsNode.cpp | 149 ++++++++++++++++++ .../Nodes/RenderNodes/GeometryAovsNode.hpp | 63 ++++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 12 ++ .../Shaders/GeometryAovs/shader.frag.glsl | 29 ++++ .../Shaders/shader_nolight.vert.glsl | 37 +++++ src/Dataflow/Rendering/filelist.cmake | 2 + 6 files changed, 292 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/GeometryAovs/shader.frag.glsl create mode 100644 src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp new file mode 100644 index 00000000000..3b595b8e5f4 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp @@ -0,0 +1,149 @@ +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +GeometryAovsNode::GeometryAovsNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + // Create internal texture + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.minFilter = gl::GL_NEAREST; + texParams.magFilter = gl::GL_NEAREST; + texParams.name = "world pos"; + m_posInWorldTexture = new Ra::Engine::Data::Texture( texParams ); + + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.name = "world normal"; + m_normalInWorldTexture = new Ra::Engine::Data::Texture( texParams ); + + texParams.internalFormat = gl::GL_DEPTH_COMPONENT24; + texParams.format = gl::GL_DEPTH_COMPONENT; + texParams.type = gl::GL_UNSIGNED_INT; + texParams.minFilter = gl::GL_NEAREST; + texParams.magFilter = gl::GL_NEAREST; + texParams.name = "depth"; + m_depthTexture = new Ra::Engine::Data::Texture( texParams ); + + addOutput( m_outDepthTex, m_depthTexture ); + addOutput( m_outPosInWorldTex, m_posInWorldTexture ); + addOutput( m_outNormalInWorldTex, m_normalInWorldTexture ); +} + +void GeometryAovsNode::init() { + m_framebuffer = new globjects::Framebuffer(); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthMask( gl::GL_TRUE ); + m_nodeState->depthFunc( gl::GL_LESS ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->disable( gl::GL_BLEND ); +} + +void GeometryAovsNode::destroy() { + delete m_depthTexture; + delete m_normalInWorldTexture; + delete m_posInWorldTexture; + + delete m_nodeState; + delete m_framebuffer; +} + +void GeometryAovsNode::resize( uint32_t width, uint32_t height ) { + m_depthTexture->resize( width, height ); + m_posInWorldTexture->resize( width, height ); + m_normalInWorldTexture->resize( width, height ); + + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, m_depthTexture->texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_posInWorldTexture->texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT1, m_normalInWorldTexture->texture() ); + m_framebuffer->unbind(); +} + +void GeometryAovsNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + std::string resourcesRootDir = m_resourceDir + "Shaders/GeometryAovs/"; + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not used in geomPrepass + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "GeometryAovsNode::" + mat->getMaterialName() } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + // Build the shader configuration + Ra::Engine::Data::ShaderConfiguration theConfig { + { "GeometryAovsNode::" + mat->getMaterialName() }, + m_resourceDir + "Shaders/shader_nolight.vert.glsl", + resourcesRootDir + "shader.frag.glsl" }; + // add the material interface to the fragment shader + theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"", + Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechniq + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool GeometryAovsNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + + // Compute the result + m_framebuffer->bind(); + + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0, gl::GL_COLOR_ATTACHMENT1 }; + gl::glDrawBuffers( 2, buffers ); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + + float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float clearDepth = 1.0f; + gl::glClearBufferfv( gl::GL_COLOR, 0, clearColor ); + gl::glClearBufferfv( gl::GL_COLOR, 1, clearColor ); + gl::glClearBufferfv( gl::GL_DEPTH, 0, &clearDepth ); + + Ra::Engine::Data::RenderParameters inPassParams; + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + + currentState->apply(); + m_framebuffer->unbind(); + + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.hpp new file mode 100644 index 00000000000..12d4a7d83ed --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.hpp @@ -0,0 +1,63 @@ +#pragma once +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Render a scene into several geometry buffers. + * - Image space fragment detph + * - World space fragment normal + * - World space fragment position + * + */ +class RA_DATAFLOW_API GeometryAovsNode : public RenderingNode +{ + public: + explicit GeometryAovsNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Geometry AOVs"; } + + private: + Ra::Engine::Data::Texture* m_depthTexture { nullptr }; + Ra::Engine::Data::Texture* m_posInWorldTexture { nullptr }; + Ra::Engine::Data::Texture* m_normalInWorldTexture { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outDepthTex { new PortOut( "depth", this ) }; + PortOut* m_outPosInWorldTex { new PortOut( "world pos", this ) }; + PortOut* m_outNormalInWorldTex { + new PortOut( "world normal", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 036ba86a3f3..a1a515de455 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -7,6 +7,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include +#include #include #include @@ -70,6 +71,17 @@ std::string registerRenderingNodesFactories() { return node; }, "Render" ); + + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new GeometryAovsNode( "GeometryAovs_" + + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Rendering/Shaders/GeometryAovs/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/GeometryAovs/shader.frag.glsl new file mode 100644 index 00000000000..653fdc2e701 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/GeometryAovs/shader.frag.glsl @@ -0,0 +1,29 @@ +/* +the functions used to discard or shade the fragment are given by the implementation of the Material +Interface. The appropriate code will be added at runtime by the shader composer or the render pass +initialiser. +*/ +#include "VertexAttribInterface.frag.glsl" + +layout( location = 0 ) out vec4 out_worldpos; +layout( location = 1 ) out vec4 out_normal; + +void main() { + vec3 tc = getPerVertexTexCoord(); + // discard non opaque fragment + vec4 bc = getBaseColor( material, tc ); + if ( toDiscard( material, bc ) ) discard; + +#ifdef GLTF_MATERIAL_INTERFACE + // Experiment on a new GLSL/Material interface allowing more efficient PBR and composition + + // Compute the normal closure. Do normal map and capture state about normal and tangent space + NormalInfo nrm_info = getNormalInfo( material.baseMaterial, tc ); + vec3 worldNormal = nrm_info.n; +#else + vec3 worldNormal = getNormal( + material, tc, getWorldSpaceNormal(), getWorldSpaceTangent(), getWorldSpaceBiTangent() ); +#endif + out_worldpos = getWorldSpacePosition(); + out_normal = vec4( worldNormal * 0.5 + 0.5, 1.0 ); +} diff --git a/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl b/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl new file mode 100644 index 00000000000..fde2a9a03ac --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl @@ -0,0 +1,37 @@ +#include "TransformStructs.glsl" + +layout (location = 0) in vec3 in_position; +layout (location = 1) in vec3 in_normal; +layout (location = 2) in vec3 in_tangent; +layout (location = 3) in vec3 in_bitangent; +layout (location = 4) in vec3 in_texcoord; +layout (location = 5) in vec4 in_color; + +uniform Transform transform; + +layout (location = 0) out vec3 out_position; +layout (location = 1) out vec3 out_normal; +layout (location = 2) out vec3 out_texcoord; +layout (location = 3) out vec3 out_vertexcolor; +layout (location = 4) out vec3 out_tangent; + + +void main() +{ + mat4 mvp = transform.proj * transform.view * transform.model; + gl_Position = mvp * vec4(in_position, 1.0); + + vec4 pos = transform.model * vec4(in_position, 1.0); + pos /= pos.w; + + vec3 normal = mat3(transform.worldNormal) * in_normal; + vec3 tangent = mat3(transform.model) * in_tangent; + + vec3 eye = -transform.view[3].xyz * mat3(transform.view); + + out_position = vec3(pos); + out_texcoord = in_texcoord; + out_normal = normal; + out_tangent = tangent; + out_vertexcolor = in_color.rgb; +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index ec327844f85..2af1fac18cb 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -10,6 +10,7 @@ set(dataflow_rendering_sources Nodes/Sinks/DisplaySinkNode.cpp Nodes/Sources/TextureSourceNode.cpp Nodes/RenderNodes/ClearColorNode.cpp + Nodes/RenderNodes/GeometryAovsNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp ) @@ -17,6 +18,7 @@ set(dataflow_rendering_headers Nodes/RenderingBuiltInsNodes.hpp Nodes/RenderingNode.hpp Nodes/RenderNodes/ClearColorNode.hpp + Nodes/RenderNodes/GeometryAovsNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/Scene.hpp From 53d4f7bf2600c5408727b3a24311dd121ad08179 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sat, 5 Nov 2022 17:53:37 +0100 Subject: [PATCH 192/239] [dataflow][rendering] add emissivity node --- .../RenderNodes/EmissivityRenderNode.cpp | 140 ++++++++++++++++++ .../RenderNodes/EmissivityRenderNode.hpp | 67 +++++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 14 +- .../Shaders/EmissivityNode/shader.frag.glsl | 17 +++ src/Dataflow/Rendering/filelist.cmake | 2 + 5 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/EmissivityNode/shader.frag.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp new file mode 100644 index 00000000000..1aef6c35b53 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp @@ -0,0 +1,140 @@ +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +EmissivityNode::EmissivityNode( const std::string& name ) : RenderingNode( name, getTypename() ) { + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + // TODO, allow to not provide the two following inputs, then using internal textures ? + addInput( m_inColor ); + m_inColor->mustBeLinked(); + addInput( m_inDepth ); + m_inDepth->mustBeLinked(); + + addInput( m_inAo ); + + addOutput( m_outColor, m_colorTexture ); +} + +void EmissivityNode::init() { + m_framebuffer = new globjects::Framebuffer(); + + float blankAO[4] = { 1.f, 1.f, 1.f, 1.f }; + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.width = 1; + texParams.height = 1; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.minFilter = gl::GL_NEAREST; + texParams.magFilter = gl::GL_NEAREST; + texParams.name = "Blank AO"; + texParams.texels = &blankAO; + m_blankAO = new Ra::Engine::Data::Texture( texParams ); + m_blankAO->initializeGL(); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthFunc( gl::GL_LEQUAL ); + m_nodeState->depthMask( gl::GL_FALSE ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->disable( gl::GL_BLEND ); +} + +void EmissivityNode::destroy() { + delete m_framebuffer; + delete m_blankAO; + + delete m_nodeState; +} + +void EmissivityNode::resize( uint32_t, uint32_t ) {} + +void EmissivityNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + std::string resourcesRootDir = m_resourceDir + "Shaders/EmissivityNode/"; + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not used in EmissivityPass + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "EmissivityNode::" + mat->getMaterialName() } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + // Build the shader configuration + Ra::Engine::Data::ShaderConfiguration theConfig { + { "EmissivityNode::" + mat->getMaterialName() }, + m_resourceDir + "Shaders/shader_nolight.vert.glsl", + resourcesRootDir + "shader.frag.glsl" }; + // add the material interface to the fragment shader + theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"", + Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechniq + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool EmissivityNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + + // Color tex + m_colorTexture = &m_inColor->getData(); + m_outColor->setData( m_colorTexture ); + + // Depth buffer + auto& depthBuffer = m_inDepth->getData(); + + // SSAO texture + auto aoTexture = m_inAo->isLinked() ? &m_inAo->getData() : m_blankAO; + Ra::Engine::Data::RenderParameters inPassParams; + inPassParams.addParameter( "amb_occ_sampler", aoTexture ); + + // Compute the result + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + + currentState->apply(); + + m_framebuffer->unbind(); + + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp new file mode 100644 index 00000000000..f3fba6a6fd0 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp @@ -0,0 +1,67 @@ +#pragma once +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Render a scene so that only the emissivity is computed. + * This node expect to have as input the depth buffer of the scene (from any depth-prepass) and a + * color buffer with the background color set. + * This node will replace the color ofe each pixel in the color buffer by the computed emissivity + * for all visible fragments. + * pixels not covered by a fragments are left unchanged. + * + */ +class RA_DATAFLOW_API EmissivityNode : public RenderingNode +{ + public: + explicit EmissivityNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Emissivity Render Node"; } + + private: + // This texture is not owned by the node, it is just an alias of input/output color texture + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + + Ra::Engine::Data::Texture* m_blankAO { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortIn* m_inDepth { new PortIn( "depth", this ) }; + PortIn* m_inColor { new PortIn( "color", this ) }; + PortIn* m_inAo { new PortIn( "ssao", this ) }; + + PortOut* m_outColor { new PortOut( "beauty", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index a1a515de455..262fbdfe299 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -7,6 +7,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include +#include #include #include @@ -37,9 +38,6 @@ std::string registerRenderingNodesFactories() { } auto resourcesPath { *resourcesCheck }; - LOG( Ra::Core::Utils::logINFO ) - << "Resources for renderingNodes will be fetched from " << resourcesPath; - Core::NodeFactorySet::mapped_type renderingFactory { new Core::NodeFactorySet::mapped_type::element_type( "RenderingNodes" ) }; @@ -82,6 +80,16 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new EmissivityNode( "Emissivity_" + + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Rendering/Shaders/EmissivityNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/EmissivityNode/shader.frag.glsl new file mode 100644 index 00000000000..79b7e213a6e --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/EmissivityNode/shader.frag.glsl @@ -0,0 +1,17 @@ +#include "VertexAttribInterface.frag.glsl" +//------------------- main --------------------- +uniform sampler2D amb_occ_sampler; + +layout (location = 0) out vec4 out_color; +void main() { + // discard non opaque fragment + vec4 bc = getBaseColor(material, getPerVertexTexCoord()); + if (toDiscard(material, bc)) { + discard; + } + + vec2 size = textureSize(amb_occ_sampler, 0).xy; + vec3 ao = texture(amb_occ_sampler, gl_FragCoord.xy/size).rgb; + + out_color = vec4(bc.rgb * 0.01 * ao + getEmissiveColor(material, getPerVertexTexCoord()), 1.0); +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 2af1fac18cb..18a4d0a61ff 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -10,6 +10,7 @@ set(dataflow_rendering_sources Nodes/Sinks/DisplaySinkNode.cpp Nodes/Sources/TextureSourceNode.cpp Nodes/RenderNodes/ClearColorNode.cpp + Nodes/RenderNodes/EmissivityRenderNode.cpp Nodes/RenderNodes/GeometryAovsNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp ) @@ -18,6 +19,7 @@ set(dataflow_rendering_headers Nodes/RenderingBuiltInsNodes.hpp Nodes/RenderingNode.hpp Nodes/RenderNodes/ClearColorNode.hpp + Nodes/RenderNodes/EmissivityRenderNode.hpp Nodes/RenderNodes/GeometryAovsNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp Nodes/Sinks/DisplaySinkNode.hpp From 3ff3dbb482c878989758b7ac278be51d7026f690 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 6 Nov 2022 18:04:04 +0100 Subject: [PATCH 193/239] [gui] automatically loads plugins from the directory specified on the command line --- src/Gui/BaseApplication.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Gui/BaseApplication.cpp b/src/Gui/BaseApplication.cpp index 8681c07ab92..a1e09c0a812 100644 --- a/src/Gui/BaseApplication.cpp +++ b/src/Gui/BaseApplication.cpp @@ -309,6 +309,13 @@ void BaseApplication::initialize( const WindowFactory& factory, pluginsPath, parser.values( "loadPlugin" ), parser.values( "ignorePlugin" ) ) ) { LOG( logDEBUG ) << "No plugin found in default path " << pluginsPath; } + // if a plugin path was set on the command line, load plugins from this path + if ( !m_pluginPath.empty() ) { + if ( !loadPlugins( + m_pluginPath, parser.values( "loadPlugin" ), parser.values( "ignorePlugin" ) ) ) { + LOG( logDEBUG ) << "No plugin found in command line given plugin path " << m_pluginPath; + } + } // load supplemental plugins { QSettings settings; From 8fe7af4e141bae4e71a2288e7a3e6c5e27c074bb Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 6 Nov 2022 18:19:08 +0100 Subject: [PATCH 194/239] [engine] add sphere sampler, spherical random point sets mapped to a texture. --- src/Engine/Data/SphereSampler.cpp | 76 +++++++++++++++++++++++++++++++ src/Engine/Data/SphereSampler.hpp | 52 +++++++++++++++++++++ src/Engine/filelist.cmake | 2 + 3 files changed, 130 insertions(+) create mode 100644 src/Engine/Data/SphereSampler.cpp create mode 100644 src/Engine/Data/SphereSampler.hpp diff --git a/src/Engine/Data/SphereSampler.cpp b/src/Engine/Data/SphereSampler.cpp new file mode 100644 index 00000000000..7a9725357cf --- /dev/null +++ b/src/Engine/Data/SphereSampler.cpp @@ -0,0 +1,76 @@ +#include +#include + +namespace Ra { +namespace Engine { +namespace Data { + +using namespace Core::Random; + +SphereSampler::SphereSampler( SamplingMethod method, int level ) { + std::function generator; + switch ( method ) { + case SamplingMethod::FIBONACCI: { + auto g = SphericalPointSet( level ); + m_nbPoints = g.range(); + generator = std::move( g ); + } break; + case SamplingMethod::HAMMERSLEY: { + auto g = SphericalPointSet( level ); + m_nbPoints = g.range(); + generator = std::move( g ); + } break; + case SamplingMethod::RANDOM: { + auto g = SphericalPointSet( level ); + m_nbPoints = g.range(); + generator = std::move( g ); + } break; + case SamplingMethod::GEODESIC: + // not yet implemented, resolve as default + // default: // for the moment, implement GEODESIC + { + auto g = SphericalPointSet( level ); + m_nbPoints = g.range(); + generator = std::move( g ); + } + break; + } + + std::mt19937 seq( 0 ); + std::uniform_real_distribution random( 0._ra, 1._ra ); + + m_points.reserve( size_t( m_nbPoints ) ); + for ( auto i = 0; i < m_nbPoints; ++i ) { + auto pt = generator( i ); + auto d = random( seq ); + // lerp will only be available on C++20 and after ... + // d = std::lerp(0.1, 1.0, d*d); + d *= d; + d = 0.1_ra + d * 0.9_ra; + m_points.emplace_back( pt[0], pt[1], pt[2], d ); + } +} + +Ra::Engine::Data::Texture* SphereSampler::asTexture() { + using namespace gl; + if ( m_texture != nullptr ) { return m_texture.get(); } + + Ra::Engine::Data::TextureParameters texparams; + texparams.name = "Directional samples"; + texparams.width = size_t( m_nbPoints ); + texparams.height = 1; + texparams.target = GL_TEXTURE_RECTANGLE; + texparams.minFilter = GL_NEAREST; + texparams.magFilter = GL_NEAREST; + texparams.internalFormat = GL_RGBA32F; + texparams.format = GL_RGBA; + texparams.type = GL_FLOAT; + texparams.texels = m_points.data(); + m_texture = std::make_unique( texparams ); + m_texture->initializeGL(); + return m_texture.get(); +} + +} // namespace Data +} // namespace Engine +} // namespace Ra diff --git a/src/Engine/Data/SphereSampler.hpp b/src/Engine/Data/SphereSampler.hpp new file mode 100644 index 00000000000..edcd9dcd78e --- /dev/null +++ b/src/Engine/Data/SphereSampler.hpp @@ -0,0 +1,52 @@ +#pragma once +#include + +#include +#include + +#include + +namespace Ra { +namespace Engine { +namespace Data { + +/// Sample the volume of a sphere as a random displacement of its sampled surface +class RA_ENGINE_API SphereSampler +{ + public: + /// Available samplers + enum class SamplingMethod { + FIBONACCI, ///<- Sample the sphere with a fibonacci sequence + HAMMERSLEY, ///<- Sample the sphere using Hammersley Point Set + RANDOM, ///<-- Sample the sphere using uniform random sequence + GEODESIC, ///<- Sample the sphere by subdivision of an icosahedron + }; + /// Generate a sequence of point distributed on the sphere + /// @param method the sampling method to use (@see SamplingMethod) + /// @param level the number of point to generate + explicit SphereSampler( SamplingMethod method, int level = 0 ); + SphereSampler( const SphereSampler& ) = delete; + SphereSampler& operator=( const SphereSampler& ) = delete; + SphereSampler( SphereSampler&& ) = delete; + SphereSampler& operator=( SphereSampler&& ) = delete; + /// destructor + ~SphereSampler() = default; + + /// Get the sampling scheme as a Radium texture + Ra::Engine::Data::Texture* asTexture(); + + /// Return the number of samples generated + [[nodiscard]] int nbSamples() const { return m_nbPoints; } + + private: + /// Texture generated on demand to be used with an OpenGL shader + std::unique_ptr m_texture { nullptr }; + /// Final number of points + int m_nbPoints; + /// The points on the sphere + Ra::Core::VectorArray m_points; +}; + +} // namespace Data +} // namespace Engine +} // namespace Ra diff --git a/src/Engine/filelist.cmake b/src/Engine/filelist.cmake index 274f82178ba..a0923b050ea 100644 --- a/src/Engine/filelist.cmake +++ b/src/Engine/filelist.cmake @@ -20,6 +20,7 @@ set(engine_sources Data/ShaderProgram.cpp Data/ShaderProgramManager.cpp Data/SimpleMaterial.cpp + Data/SphereSampler.cpp Data/Texture.cpp Data/TextureManager.cpp Data/VolumeObject.cpp @@ -73,6 +74,7 @@ set(engine_headers Data/ShaderProgram.hpp Data/ShaderProgramManager.hpp Data/SimpleMaterial.hpp + Data/SphereSampler.hpp Data/Texture.hpp Data/TextureManager.hpp Data/ViewingParameters.hpp From 10a09b2103d9f7c1b2ca4209646bfe45b6b19bb1 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Sun, 6 Nov 2022 18:23:07 +0100 Subject: [PATCH 195/239] [dataflow][rendering] doc clearcolorNode --- src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp index 28d4f2c31c1..9f519c77702 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp @@ -22,6 +22,10 @@ using namespace Ra::Dataflow::Core; using namespace Ra::Engine::Data; using EnvironmentType = std::shared_ptr; +/** + * \brief Initialize the given image either using a constant color or by rendering a sky box. + * + */ class RA_DATAFLOW_API ClearColorNode : public RenderingNode { public: From 7fbfd1d497fe6b3ebae1aa6dbd5fe3f81d03ddfb Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 7 Nov 2022 13:03:55 +0100 Subject: [PATCH 196/239] [dataflow][qtgui] Add a todo (to be moved in RadiumDataflow PR) --- src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp index cac3d862345..ed089cfcb2b 100644 --- a/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp +++ b/src/Dataflow/QtGui/GraphEditor/WidgetFactory.cpp @@ -69,6 +69,7 @@ bool updateWidget( QWidget* widget, EditableParameterBase* editableParameter ) { } void initializeWidgetFactory() { + // TODO, rewrite this to use Control panel and its widgets !!! /* * Environment map "edition" widget */ From 5919511bee9aed9fb84200b028a4c1ae90ca9002 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 7 Nov 2022 13:05:22 +0100 Subject: [PATCH 197/239] [examples][dataflow] Add geom aov to the basic graph rendering example --- examples/DataflowExamples/GraphRendering/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index a7cb6977177..a4b8b339ffb 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -150,8 +150,8 @@ class DemoWindowFactory : public BaseApplication::WindowFactory /* ----------------------------------------------------------------------------------- */ // Renderer controller #include +#include #include - #include #include @@ -238,6 +238,8 @@ class MyRendererController : public RenderGraphController m_renderGraph->addNode( sceneNode ); auto resultNode = new DisplaySinkNode( "Images" ); m_renderGraph->addNode( resultNode ); + auto geomAovs = new GeometryAovsNode( "Geometry Aovs" ); + m_renderGraph->addNode( geomAovs ); #if 0 auto textureSource = new ColorTextureNode( "Beauty" ); m_renderGraph->addNode( textureSource ); @@ -262,6 +264,11 @@ class MyRendererController : public RenderGraphController m_renderGraph->addLink( simpleRenderNode, "Beauty", resultNode, "Beauty" ); linksOK = linksOK && m_renderGraph->addLink( simpleRenderNode, "Depth AOV", resultNode, "AOV_0" ); + linksOK = + linksOK && m_renderGraph->addLink( geomAovs, "world normal", resultNode, "AOV_1" ); + linksOK = linksOK && m_renderGraph->addLink( sceneNode, "camera", geomAovs, "camera" ); + linksOK = + linksOK && m_renderGraph->addLink( sceneNode, "objects", geomAovs, "objects" ); if ( !linksOK ) { LOG( logERROR ) << "Something went wrong when linking nodes !!! "; } else { From 40d5a70b8827776d3e9944052821af928ba38a2b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 7 Nov 2022 13:06:44 +0100 Subject: [PATCH 198/239] [dataflow][rendering] add ssao node --- .../Nodes/RenderNodes/SsaoRenderNode.cpp | 234 ++++++++++++++++++ .../Nodes/RenderNodes/SsaoRenderNode.hpp | 79 ++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 10 + .../Shaders/SsaoNode/blurao.frag.glsl | 20 ++ .../Rendering/Shaders/SsaoNode/ssao.frag.glsl | 57 +++++ src/Dataflow/Rendering/filelist.cmake | 2 + 6 files changed, 402 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl create mode 100644 src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp new file mode 100644 index 00000000000..26057c4ba6e --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp @@ -0,0 +1,234 @@ +#include + +#include +#include +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +SsaoNode::SsaoNode( const std::string& name ) : RenderingNode( name, getTypename() ) { + addInput( m_inWorldPos ); + m_inWorldPos->mustBeLinked(); + + addInput( m_inWorldNormal ); + m_inWorldNormal->mustBeLinked(); + + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + addInput( m_aoRadius ); + addInput( m_aoSamples ); + + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.name = "ssao"; + m_AO = new Ra::Engine::Data::Texture( texParams ); + + addOutput( m_ssao, m_AO ); + + auto editableRadius = new EditableParameter( "radius", m_editableAORadius ); + editableRadius->addAdditionalData( 0. ); + editableRadius->addAdditionalData( 100. ); + addEditableParameter( editableRadius ); + + auto editableSamples = new EditableParameter( "samples", m_editableSamples ); + editableSamples->addAdditionalData( 0 ); + editableSamples->addAdditionalData( 4096 ); + addEditableParameter( editableSamples ); +} + +void SsaoNode::init() { + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.name = "Raw Ambient Occlusion"; + m_rawAO = new Ra::Engine::Data::Texture( texParams ); + + Ra::Core::Geometry::TriangleMesh mesh = + Ra::Core::Geometry::makeZNormalQuad( Ra::Core::Vector2( -1.f, 1.f ) ); + auto qm = std::make_unique( "caller" ); + qm->loadGeometry( std::move( mesh ) ); + m_quadMesh = std::move( qm ); + m_quadMesh->updateGL(); + + // TODO make the sampling method an editable parameter + m_sphereSampler = + std::make_unique( SphereSampler::SamplingMethod::HAMMERSLEY, 64 ); + + m_blurFramebuffer = new globjects::Framebuffer(); + m_framebuffer = new globjects::Framebuffer(); +} + +void SsaoNode::destroy() { + delete m_rawAO; + delete m_AO; + delete m_blurFramebuffer; + delete m_framebuffer; +} + +void SsaoNode::resize( uint32_t width, uint32_t height ) { + m_rawAO->resize( width, height ); + m_AO->resize( width, height ); + + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_rawAO->texture() ); + + m_blurFramebuffer->bind(); + m_blurFramebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_AO->texture() ); + + globjects::Framebuffer::unbind(); +} + +bool SsaoNode::execute() { + auto aabb = Ra::Engine::RadiumEngine::getInstance()->computeSceneAabb(); + if ( aabb.isEmpty() ) { m_sceneDiag = 1_ra; } + else { + m_sceneDiag = aabb.diagonal().norm(); + } + + Ra::Engine::Data::RenderParameters inPassParams; + // Positions + auto posTexture = &m_inWorldPos->getData(); + // Normals + auto normalTexture = &m_inWorldNormal->getData(); + + // AO Radius + auto aoRadius = m_editableAORadius; + if ( m_aoRadius->isLinked() ) { aoRadius = m_aoRadius->getData(); } + + // AO Samples + unsigned int samples = m_editableSamples; + if ( m_aoSamples->isLinked() ) { samples = m_aoSamples->getData(); } + if ( m_currentSamples != samples ) { + m_currentSamples = samples; + m_sphereSampler = std::make_unique( + SphereSampler::SamplingMethod::HAMMERSLEY, m_currentSamples ); + } + + // Cameras + auto& camera = m_inCamera->getData(); + + m_framebuffer->bind(); + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + float clearWhite[4] = { 1.0f, 1.0f, 1.0f, 0.0f }; + gl::glClearBufferfv( gl::GL_COLOR, 0, clearWhite ); + gl::glDisable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_FALSE ); + + m_shader->bind(); + Ra::Core::Matrix4 viewProj = camera.projMatrix * camera.viewMatrix; + + m_shader->setUniform( "transform.mvp", viewProj ); + m_shader->setUniform( "transform.proj", camera.projMatrix ); + m_shader->setUniform( "transform.view", camera.viewMatrix ); + + m_shader->setUniform( "normal_sampler", normalTexture, 0 ); + m_shader->setUniform( "position_sampler", posTexture, 1 ); + m_shader->setUniform( "dir_sampler", m_sphereSampler->asTexture(), 2 ); + m_shader->setUniform( "ssdoRadius", aoRadius / 100_ra * m_sceneDiag ); + + m_quadMesh->render( m_shader ); + gl::glEnable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_TRUE ); + m_framebuffer->unbind(); + + m_blurFramebuffer->bind(); + m_blurShader->bind(); + gl::glDrawBuffers( 1, buffers ); + gl::glClearBufferfv( gl::GL_COLOR, 0, clearWhite ); + gl::glDisable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_FALSE ); + + m_shader->setUniform( "transform.mvp", viewProj ); + m_shader->setUniform( "transform.proj", camera.projMatrix ); + m_shader->setUniform( "transform.view", camera.viewMatrix ); + m_shader->setUniform( "ao_sampler", m_rawAO, 0 ); + + m_quadMesh->render( m_shader ); + gl::glEnable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_TRUE ); + m_blurFramebuffer->unbind(); + + return true; +} + +void SsaoNode::toJsonInternal( nlohmann::json& data ) const { + if ( m_currentSamples != AO_DefaultSamples ) { + // do not write default value + data["samples"] = m_currentSamples; + } + if ( m_editableAORadius != AO_DefaultRadius ) { + // do not write default value + data["radius"] = m_editableAORadius; + } +} + +bool SsaoNode::fromJsonInternal( const nlohmann::json& data ) { + if ( data.contains( "radius" ) ) { m_editableAORadius = data["radius"]; } + if ( data.contains( "samples" ) ) { + m_currentSamples = data["samples"]; + m_editableSamples = Scalar( m_currentSamples ); + } + return true; +} + +bool SsaoNode::initInternalShaders() { + if ( !m_hasShaders ) { + const std::string vertexShaderSource { "layout (location = 0) in vec3 in_position;\n" + "out vec2 varTexcoord;\n" + "void main(void) {\n" + " gl_Position = vec4(in_position.xyz, 1.0);\n" + " varTexcoord = (in_position.xy + 1.0) / 2.0;\n" + "}" }; + std::string resourcesRootDir = m_resourceDir + "Shaders/SsaoNode/"; + + Ra::Engine::Data::ShaderConfiguration ssdoConfig { "SSDO" }; + ssdoConfig.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, + vertexShaderSource ); + ssdoConfig.addShader( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, + resourcesRootDir + "ssao.frag.glsl" ); + auto added = m_shaderMngr->addShaderProgram( ssdoConfig ); + if ( added ) { m_shader = added.value(); } + else { + std::cout << "AO: Could not add shader." << std::endl; + return false; + } + + Ra::Engine::Data::ShaderConfiguration blurssdoConfig { "blurSSDO" }; + blurssdoConfig.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, + vertexShaderSource ); + blurssdoConfig.addShader( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, + resourcesRootDir + "blurao.frag.glsl" ); + added = m_shaderMngr->addShaderProgram( blurssdoConfig ); + if ( added ) { m_blurShader = added.value(); } + else { + std::cout << "AO: Could not add shader." << std::endl; + return false; + } + m_hasShaders = true; + } + return m_hasShaders; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp new file mode 100644 index 00000000000..4cafd2b0dcd --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp @@ -0,0 +1,79 @@ +#pragma once +#include + +#include + +#include +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +using namespace Ra::Engine::Data; + +/** + * \brief compute ssao in image space from world space geometry buffer. + * + */ +class RA_DATAFLOW_API SsaoNode : public RenderingNode +{ + public: + explicit SsaoNode( const std::string& name ); + + void init() override; + bool execute() override; + void destroy() override; + void resize( uint32_t width, uint32_t height ) override; + bool initInternalShaders() override; + + // SSao does not need rendertechnique + bool hasRenderTechnique() override { return false; } + + static const std::string getTypename() { return "SSAO Node"; } + + protected: + void toJsonInternal( nlohmann::json& data ) const override; + bool fromJsonInternal( const nlohmann::json& data ) override; + + private: + bool m_hasShaders { false }; + + static constexpr Scalar AO_DefaultRadius { 5_ra }; + static constexpr int AO_DefaultSamples { 64 }; + + TextureType* m_rawAO { nullptr }; + TextureType* m_AO { nullptr }; + + Scalar m_editableAORadius { AO_DefaultRadius }; + int m_editableSamples { AO_DefaultSamples }; + + int m_currentSamples { AO_DefaultSamples }; + Scalar m_sceneDiag { 1.0 }; + + std::unique_ptr m_quadMesh { nullptr }; + std::unique_ptr m_sphereSampler { nullptr }; + + const Ra::Engine::Data::ShaderProgram* m_shader { nullptr }; + const Ra::Engine::Data::ShaderProgram* m_blurShader { nullptr }; + + globjects::Framebuffer* m_blurFramebuffer { nullptr }; + globjects::Framebuffer* m_framebuffer { nullptr }; + + PortIn* m_inWorldPos { new PortIn( "worldPosition", this ) }; + PortIn* m_inWorldNormal { new PortIn( "worldNormal", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortIn* m_aoRadius { new PortIn( "radius", this ) }; + PortIn* m_aoSamples { new PortIn( "samples", this ) }; + + PortOut* m_ssao { new PortOut( "ssao", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 262fbdfe299..10204d61e01 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -10,6 +10,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include +#include #include @@ -90,6 +91,15 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new SsaoNode( "Ssao_" + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl b/src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl new file mode 100644 index 00000000000..cd317e59b9e --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/SsaoNode/blurao.frag.glsl @@ -0,0 +1,20 @@ +layout (location = 0) out vec4 out_ssao; + +uniform sampler2D ao_sampler; +in vec2 varTexcoord; + +const int half_width = 2; + +void main() { + vec2 texelSize = 1.0 / vec2(textureSize(ao_sampler, 0)); + float result = 0.0; + for (int x = -half_width; x < half_width; ++x) + { + for (int y = -half_width; y < half_width; ++y) + { + vec2 offset = vec2(float(x), float(y)) * texelSize; + result += texture(ao_sampler, varTexcoord + offset).r; + } + } + out_ssao = vec4(vec3(result / (4 * half_width * half_width)), 1); +} diff --git a/src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl b/src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl new file mode 100644 index 00000000000..48eeb582e78 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/SsaoNode/ssao.frag.glsl @@ -0,0 +1,57 @@ +#include "TransformStructs.glsl" + +layout (location = 0) out vec4 out_ssao; + +uniform sampler2D normal_sampler; +uniform sampler2D position_sampler; +uniform sampler2DRect dir_sampler; + +uniform Transform transform; +uniform float ssdoRadius; + +// TODO compute a bias "a la" glPolygonOffset so that it adapts to z dynamic range. +const float bias = 0.001; + +void main() { + // Convert screen space position to world space position + vec2 size = vec2(textureSize(position_sampler, 0)); + + // The point to shade + vec4 ptInWorld = texelFetch(position_sampler, ivec2(gl_FragCoord.xy), 0); + float ssdo = 0; + if (ptInWorld.w != 0) { + int nbSamples = textureSize(dir_sampler).x; + // The point normal (0 if the fragment must be discarded) + vec3 nrmInWorld = texelFetch(normal_sampler, ivec2(gl_FragCoord.xy), 0).xyz; + float fragDepth = (transform.view * vec4(ptInWorld.xyz, 1)).z; + nrmInWorld = nrmInWorld * 2. -1.; + + for (int i=0;i 0) { + offset *= offset.w * ssdoRadius; + // theSample is the world position, displaced and reprojected. + // It only serve to access the fragment for whihc the depth must be compared. + vec4 theSample = vec4(ptInWorld.xyz + offset.xyz, 1); + + theSample = transform.mvp * theSample; + theSample.xyz /= theSample.w; + theSample.xyz = theSample.xyz * 0.5 + 0.5; + + vec4 sampledPoint = texture(position_sampler, theSample.st); + if (sampledPoint.w != 0) { + float sampleDepth = (transform.view * sampledPoint).z; + + float rangeCheck = smoothstep(0.0, 1.0, ssdoRadius / abs(fragDepth - sampleDepth)); + ssdo += (sampleDepth >= fragDepth + bias ? 1.0 : 0.0) * rangeCheck; + } + } + } + ssdo = 1 - ssdo/nbSamples; + //ssdo *= ssdo; + ssdo = smoothstep(0, 1, ssdo); + } + out_ssao = vec4(vec3(ssdo), 1); +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 18a4d0a61ff..65b26e742e3 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -13,6 +13,7 @@ set(dataflow_rendering_sources Nodes/RenderNodes/EmissivityRenderNode.cpp Nodes/RenderNodes/GeometryAovsNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp + Nodes/RenderNodes/SsaoRenderNode.cpp ) set(dataflow_rendering_headers @@ -22,6 +23,7 @@ set(dataflow_rendering_headers Nodes/RenderNodes/EmissivityRenderNode.hpp Nodes/RenderNodes/GeometryAovsNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp + Nodes/RenderNodes/SsaoRenderNode.hpp Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/Scene.hpp Nodes/Sources/EnvMapSourceNode.hpp From 6fadce54dd3b77222963332817d160acbc692242 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 18:43:12 +0100 Subject: [PATCH 199/239] [dataflow][rendering] update wrt dataflow core and remove .inl files --- src/Dataflow/Rendering/CMakeLists.txt | 5 ++-- src/Dataflow/Rendering/RenderingGraph.cpp | 10 +++---- src/Dataflow/Rendering/RenderingGraph.hpp | 36 ++++++++++------------- 3 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/Dataflow/Rendering/CMakeLists.txt b/src/Dataflow/Rendering/CMakeLists.txt index 830b876b24c..f678dc9431f 100644 --- a/src/Dataflow/Rendering/CMakeLists.txt +++ b/src/Dataflow/Rendering/CMakeLists.txt @@ -7,9 +7,8 @@ include(filelist.cmake) # configure library add_library( - ${ra_dataflowrendering_target} SHARED ${dataflow_rendering_sources} - ${dataflow_rendering_headers} - ${dataflow_rendering_resources} + ${ra_dataflowrendering_target} SHARED + ${dataflow_rendering_sources} ${dataflow_rendering_headers} ${dataflow_rendering_resources} ) # This one should be extracted directly from parent project properties. diff --git a/src/Dataflow/Rendering/RenderingGraph.cpp b/src/Dataflow/Rendering/RenderingGraph.cpp index 9773d52f07a..209241e7bf8 100644 --- a/src/Dataflow/Rendering/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/RenderingGraph.cpp @@ -15,18 +15,18 @@ void RenderingGraph::init() { DataflowGraph::init(); } -bool RenderingGraph::addNode( Node* newNode ) { - auto added = DataflowGraph::addNode( newNode ); +std::pair RenderingGraph::addNode( std::unique_ptr newNode ) { + auto [added, node] = DataflowGraph::addNode( std::move( newNode ) ); if ( added ) { // Todo : is there something to do ? } - return added; + return { added, node }; } -bool RenderingGraph::removeNode( Node* node ) { +bool RenderingGraph::removeNode( Node*& node ) { auto removed = DataflowGraph::removeNode( node ); if ( removed ) { - // Todo : is there something to do ? + // Todo : is there something to do ? (node is already deleted by DataflowGraph::removeNode) } return removed; } diff --git a/src/Dataflow/Rendering/RenderingGraph.hpp b/src/Dataflow/Rendering/RenderingGraph.hpp index 8b662902be2..13fad0f6f54 100644 --- a/src/Dataflow/Rendering/RenderingGraph.hpp +++ b/src/Dataflow/Rendering/RenderingGraph.hpp @@ -20,8 +20,8 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph // Remove the 3 following methods if there is no need to specialize void init() override; - bool addNode( Node* newNode ) override; - bool removeNode( Node* node ) override; + std::pair addNode( std::unique_ptr newNode ) override; + bool removeNode( Node*& node ) override; // These methods are specialized to identify rendering nodes (and those who needs // rendertechnique) @@ -53,8 +53,14 @@ class RA_DATAFLOW_API RenderingGraph : public DataflowGraph std::vector m_rtIndexedNodes; // associate an index and buildRenderTechnique }; +// ----------------------------------------------------------------- +// ---------------------- inline methods --------------------------- + inline RenderingGraph::RenderingGraph( const std::string& name ) : - DataflowGraph( name, getTypename() ) {} + DataflowGraph( name, getTypename() ) { + // A rendering graph always use the builtin RenderingNodes factory + addFactory( NodeFactoriesManager::getFactory( "RenderingNodes" ) ); +} inline const std::string& RenderingGraph::getTypename() { static std::string demangledTypeName { "Rendering Graph" }; @@ -67,27 +73,17 @@ inline void RenderingGraph::resize( uint32_t width, uint32_t height ) { } } -inline void RenderingGraph::setDataSources( std::vector* ros, - std::vector* lights ) { -#if 0 - for(auto sn : m_dataProviders) { - sn->setScene(ros, lights); - } -#endif +inline bool RenderingGraph::fromJsonInternal( const nlohmann::json& data ) { + auto r = DataflowGraph::fromJsonInternal( data ); + // todo, extract RenderingGraph specific data + return r; } -inline void RenderingGraph::setCameras( std::vector* cameras ) { -#if 0 - for(auto sn : m_dataProviders) { - sn->setCameras(cameras); - } -#endif +inline void RenderingGraph::toJsonInternal( nlohmann::json& data ) const { + DataflowGraph::toJsonInternal( data ); + // todo, add RenderingGraph specific data } -inline void RenderingGraph::fromJsonInternal( const nlohmann::json& ) {} -inline void RenderingGraph::toJsonInternal( nlohmann::json& ) const {} - -} // namespace Renderer } // namespace Rendering } // namespace Dataflow } // namespace Ra From e4dfbf17e93ff2d2cddbfb32280bebe683f00512 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 31 Jan 2023 18:43:33 +0100 Subject: [PATCH 200/239] [dataflow][examples][rendering] update wrt dataflow core --- examples/DataflowExamples/GraphRendering/main.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index a4b8b339ffb..595a6b5303b 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -235,16 +235,16 @@ class MyRendererController : public RenderGraphController m_renderGraph = std::make_unique( "Demonstration graph" ); m_renderGraph->setShaderProgramManager( m_shaderMngr ); auto sceneNode = new SceneNode( "Scene" ); - m_renderGraph->addNode( sceneNode ); + m_renderGraph->addNode( std::unique_ptr( sceneNode ) ); auto resultNode = new DisplaySinkNode( "Images" ); - m_renderGraph->addNode( resultNode ); + m_renderGraph->addNode( std::unique_ptr( resultNode ) ); auto geomAovs = new GeometryAovsNode( "Geometry Aovs" ); - m_renderGraph->addNode( geomAovs ); + m_renderGraph->addNode( std::unique_ptr( geomAovs ) ); #if 0 auto textureSource = new ColorTextureNode( "Beauty" ); - m_renderGraph->addNode( textureSource ); + m_renderGraph->addNode( std::unique_ptr( textureSource ) ); auto clearNode = new ClearColorNode( " Clear" ); - m_renderGraph->addNode( clearNode ); + m_renderGraph->addNode( std::unique_ptr( clearNode ) ); bool linksOK = true; linksOK = m_renderGraph->addLink( @@ -252,7 +252,7 @@ class MyRendererController : public RenderGraphController linksOK = linksOK && m_renderGraph->addLink( clearNode, "image", resultNode, "Beauty" ); #endif auto simpleRenderNode = new SimpleRenderNode( "renderOperator" ); - m_renderGraph->addNode( simpleRenderNode ); + m_renderGraph->addNode( std::unique_ptr( simpleRenderNode ) ); bool linksOK = true; linksOK = linksOK && m_renderGraph->addLink( sceneNode, "objects", simpleRenderNode, "objects" ); From d6763bf9ebeeb55d065fe0a27616564765100b49 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 08:41:04 +0100 Subject: [PATCH 201/239] [dataflow][rendering] remove warnings and add missing header (windows) --- src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp | 2 +- src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp | 2 +- src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp index 9f519c77702..aa74b0e888a 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp @@ -47,7 +47,7 @@ class RA_DATAFLOW_API ClearColorNode : public RenderingNode Ra::Engine::Data::Texture* m_colorTexture { nullptr }; globjects::Framebuffer* m_framebuffer { nullptr }; - ColorType m_editableClearColor { ColorType::Grey( 0.12 ) }; + ColorType m_editableClearColor { ColorType::Grey( 0.12_ra ) }; PortIn* m_portInColorTex { new PortIn( "colorTextureToClear", this ) }; diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp index 26057c4ba6e..ae42e952291 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp @@ -114,7 +114,7 @@ bool SsaoNode::execute() { if ( m_aoRadius->isLinked() ) { aoRadius = m_aoRadius->getData(); } // AO Samples - unsigned int samples = m_editableSamples; + auto samples = m_editableSamples; if ( m_aoSamples->isLinked() ) { samples = m_aoSamples->getData(); } if ( m_currentSamples != samples ) { m_currentSamples = samples; diff --git a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp index 569edd4b726..2f378b0b513 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp +++ b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp @@ -4,11 +4,11 @@ #include +#include #include namespace Ra { namespace Dataflow { -using namespace Ra::Dataflow; namespace Rendering { using namespace Ra::Engine::Data; From ccbb23ac96998f625b6d8df8d27f46fa13c2073e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 10:11:11 +0100 Subject: [PATCH 202/239] [dataflow][rendering] add environment lighting node --- .../RenderNodes/EnvironmentLightingNode.cpp | 162 ++++++++++++++++++ .../RenderNodes/EnvironmentLightingNode.hpp | 67 ++++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 11 ++ .../Shaders/EnvLightNode/shader.frag.glsl | 122 +++++++++++++ src/Dataflow/Rendering/filelist.cmake | 2 + 5 files changed, 364 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp new file mode 100644 index 00000000000..0c799960fcd --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp @@ -0,0 +1,162 @@ +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +EnvironmentLightingNode::EnvironmentLightingNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + addInput( m_inEnvironment ); + // if no envmap available, the node will be pass_through + + // TODO, allow to not provide the two following inputs, then using internal textures ? + addInput( m_inColor ); + m_inColor->mustBeLinked(); + addInput( m_inDepth ); + m_inDepth->mustBeLinked(); + + addInput( m_inAo ); + addOutput( m_outColor, m_colorTexture ); +} + +void EnvironmentLightingNode::init() { + m_framebuffer = new globjects::Framebuffer(); + + float blankAO[4] = { 1.f, 1.f, 1.f, 1.f }; + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.width = 1; + texParams.height = 1; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.minFilter = gl::GL_NEAREST; + texParams.magFilter = gl::GL_NEAREST; + texParams.name = "Blank AO"; + texParams.texels = &blankAO; + m_blankAO = new Ra::Engine::Data::Texture( texParams ); + m_blankAO->initializeGL(); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthFunc( gl::GL_LEQUAL ); + m_nodeState->depthMask( gl::GL_FALSE ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->blendFunc( gl::GL_ONE, gl::GL_ONE ); + m_nodeState->enable( gl::GL_BLEND ); +} + +void EnvironmentLightingNode::destroy() { + delete m_framebuffer; + delete m_blankAO; + + delete m_nodeState; +} + +void EnvironmentLightingNode::resize( uint32_t, uint32_t ) {} + +void EnvironmentLightingNode::buildRenderTechnique( + const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not (yet) managed by EnvironmentLightingNode + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "EnvironmentLightingNode::" + mat->getMaterialName() } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + std::string nodeShaderDir = m_resourceDir + "Shaders/EnvLightNode/"; + // Build the shader configuration + Ra::Engine::Data::ShaderConfiguration theConfig { + { "EnvironmentLightingNode::" + mat->getMaterialName() }, + m_resourceDir + "Shaders/shader_nolight.vert.glsl", + nodeShaderDir + "shader.frag.glsl" }; + // add the material interface to the fragment shader + theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"", + Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechnique + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool EnvironmentLightingNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + + // Color tex + m_colorTexture = &m_inColor->getData(); + m_outColor->setData( m_colorTexture ); + + // Depth buffer + auto& depthBuffer = m_inDepth->getData(); + + // SSAO texture + auto aoTexture = m_inAo->isLinked() ? &m_inAo->getData() : m_blankAO; + Ra::Engine::Data::RenderParameters inPassParams; + inPassParams.addParameter( "amb_occ_sampler", aoTexture ); + + // Environment map + auto envmap = m_inEnvironment->isLinked() ? m_inEnvironment->getData().get() : nullptr; + if ( !envmap ) { return true; } + + // TODO, verify why this is needed .... + envmap->updateGL(); + + inPassParams.addParameter( "redShCoeffs", envmap->getShMatrix( 0 ) ); + inPassParams.addParameter( "greenShCoeffs", envmap->getShMatrix( 1 ) ); + inPassParams.addParameter( "blueShCoeffs", envmap->getShMatrix( 2 ) ); + inPassParams.addParameter( "envTexture", envmap->getEnvironmentTexture() ); + inPassParams.addParameter( "envStrength", envmap->getStrength() ); + int numLod = std::log2( std::min( envmap->getEnvironmentTexture()->width(), + envmap->getEnvironmentTexture()->height() ) ); + inPassParams.addParameter( "numLod", numLod ); + + // Compute the result + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + + currentState->apply(); + + m_framebuffer->unbind(); + + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.hpp new file mode 100644 index 00000000000..39e68859a61 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.hpp @@ -0,0 +1,67 @@ +#pragma once +#include + +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +using namespace Ra::Engine::Data; + +// This should be made unique instead of duplicated in EnvMapSource +using EnvmapPtrType = std::shared_ptr; + +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Render a scene using an environment map as light source. + * + */ +class RA_DATAFLOW_API EnvironmentLightingNode : public RenderingNode +{ + public: + explicit EnvironmentLightingNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Env Lighting Node"; } + + private: + Ra::Engine::Data::Texture* m_colorTexture; + Ra::Engine::Data::Texture* m_blankAO { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + + PortIn* m_inColor { new PortIn( "color", this ) }; + PortIn* m_inDepth { new PortIn( "depth", this ) }; + PortIn* m_inAo { new PortIn( "AO", this ) }; + PortIn* m_inEnvironment { new PortIn( "envmap", this ) }; + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outColor { new PortOut( "Beauty", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 10204d61e01..40f9cfb34f0 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -8,6 +8,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include +#include #include #include #include @@ -91,6 +92,16 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new EnvironmentLightingNode( + "EnLighting_" + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + renderingFactory->registerNodeCreator( [resourcesPath, renderingFactory]( const nlohmann::json& data ) { auto node = new SsaoNode( "Ssao_" + std::to_string( renderingFactory->nextNodeId() ) ); diff --git a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl new file mode 100644 index 00000000000..165dd5b9635 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl @@ -0,0 +1,122 @@ +/* +the functions toDiscard and getNormals are given by the implementation of the Material Interface. +The appropriate code will be added at runtime by the shader composer or the render pass initialiser. +*/ + +// Include the VertexAttribInterface so that all materials might be OK. +#include "VertexAttribInterface.frag.glsl" +const float OneOverPi = 0.3183098862; + +layout( location = 5 ) in vec3 in_viewVector; + +uniform sampler2D amb_occ_sampler; + +uniform samplerCube envTexture; +uniform int numLod; + +uniform mat4 redShCoeffs; +uniform mat4 greenShCoeffs; +uniform mat4 blueShCoeffs; + +layout( location = 0 ) out vec4 out_color; + +// For debug, to be removed once finalized. +uniform float envStrength; + +//------------------- main --------------------- +// TODO : verify the computations as there is huge differences between this rendering and GLTF +// see +// https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/master/source/Renderer/shaders/ibl.glsl + +// sample implementation +void main() { + vec3 tc = getPerVertexTexCoord(); + // discard non opaque fragment + vec4 bc = getBaseColor( material, tc ); + if ( toDiscard( material, bc ) ) discard; + +#ifdef GLTF_MATERIAL_INTERFACE + // Experiment on a new GLSL/Material interface allowing more efficient PBR and composition + + // Compute the normal closure. Do normal map and capture state about normal and tangent space + NormalInfo nrm_info = getNormalInfo( material.baseMaterial, tc ); + vec3 view = normalize( in_viewVector ); + + MaterialInfo bsdf_params; + BsdfInfo layers; + + if ( getSeparateBSDFComponent( material, + tc, + view, + nrm_info, + bsdf_params, + layers ) == 1 ) { + + vec2 size = textureSize( amb_occ_sampler, 0 ).xy; + vec3 ao = texture( amb_occ_sampler, gl_FragCoord.xy / size ).rgb; + + float r = GGXroughness( bsdf_params ); + // diffuse irradiance map + vec4 nrm = vec4( nrm_info.n, 1 ); + vec3 irradiance = vec3( + dot( nrm, redShCoeffs * nrm ), + dot( nrm, greenShCoeffs * nrm ), + dot( nrm, blueShCoeffs * nrm ) + ); + layers.f_diffuse *= irradiance; + + // Specular envmap + vec3 rfl = reflect( -view, nrm_info.n ); + layers.f_specular *= textureLod( envTexture, rfl, r * numLod ).rgb ; + +#ifdef CLEARCOAT_LAYER + // clearcoat envmap + r = pow(bsdf_params.clearcoat.intrough.y, 0.5); + layers.f_clearcoat *= textureLod( envTexture, rfl, r * numLod ).rgb ; +#endif + +#ifdef SHEEN_LAYER + r = pow(bsdf_params.sheen.sheenColorRough.a, 0.5); + layers.f_sheen *= textureLod( envTexture, rfl, r * numLod ).rgb ; +#endif + + bc.rgb = combineLayers( bsdf_params, layers, nrm_info, view ); + + bc.rgb *= ao * envStrength; + } else { + bc.rgb = vec3(0); + } + +#else + vec4 normalWorld = vec4( + getNormal( + material, tc, getWorldSpaceNormal(), getWorldSpaceTangent(), getWorldSpaceBiTangent() ), + 1 ); + + vec3 diffuse; + vec3 specular; + vec3 view = normalize( in_viewVector ); + vec3 rfl = reflect( -view, normalWorld.xyz ); + + if ( getSeparateBSDFComponent( + material, getPerVertexTexCoord(), rfl, view, normalWorld.xyz, diffuse, specular ) == + 1 ) + { + vec2 size = textureSize( amb_occ_sampler, 0 ).xy; + vec3 ao = texture( amb_occ_sampler, gl_FragCoord.xy / size ).rgb; + bc.rgb = diffuse; + bc.r *= dot( normalWorld, redShCoeffs * normalWorld ); + bc.g *= dot( normalWorld, greenShCoeffs * normalWorld ); + bc.b *= dot( normalWorld, blueShCoeffs * normalWorld ); + // Specular envmap + float cosTi = clamp( dot( rfl, normalWorld.xyz ), 0.001, 1. ); + vec3 spec = clamp( specular * cosTi * OneOverPi * 0.5, 0.001, 1. ); + float r = getGGXRoughness( material, getPerVertexTexCoord() ) * numLod; + bc.rgb += textureLod( envTexture, rfl, r ).rgb * spec; + bc.rgb *= ao * envStrength; + } + else + { bc.rgb = vec3( 0 ); } +#endif + out_color = vec4( bc.rgb, 1 ); +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 65b26e742e3..636bcba3d20 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -11,6 +11,7 @@ set(dataflow_rendering_sources Nodes/Sources/TextureSourceNode.cpp Nodes/RenderNodes/ClearColorNode.cpp Nodes/RenderNodes/EmissivityRenderNode.cpp + Nodes/RenderNodes/EnvironmentLightingNode.cpp Nodes/RenderNodes/GeometryAovsNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp Nodes/RenderNodes/SsaoRenderNode.cpp @@ -21,6 +22,7 @@ set(dataflow_rendering_headers Nodes/RenderingNode.hpp Nodes/RenderNodes/ClearColorNode.hpp Nodes/RenderNodes/EmissivityRenderNode.hpp + Nodes/RenderNodes/EnvironmentLightingNode.hpp Nodes/RenderNodes/GeometryAovsNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp Nodes/RenderNodes/SsaoRenderNode.hpp From eab8d2869a7b3a087cbed74a44a4d3522a54a984 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 10:55:04 +0100 Subject: [PATCH 203/239] [dataflow][rendering] add local lighting node --- .../Nodes/RenderNodes/LocalLightingNode.cpp | 150 ++++++++++++++++++ .../Nodes/RenderNodes/LocalLightingNode.hpp | 64 ++++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 11 ++ src/Dataflow/Rendering/filelist.cmake | 2 + 4 files changed, 227 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.hpp diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp new file mode 100644 index 00000000000..5c521fdbba5 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +LocalLightingNode::LocalLightingNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inLights ); + m_inLights->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + // TODO, allow to not provide the two following inputs, then using internal textures ? + addInput( m_inColor ); + m_inColor->mustBeLinked(); + addInput( m_inDepth ); + m_inDepth->mustBeLinked(); + + addInput( m_inAo ); + addOutput( m_outColor, m_colorTexture ); +} + +void LocalLightingNode::init() { + m_framebuffer = new globjects::Framebuffer(); + + float blankAO[4] = { 1.f, 1.f, 1.f, 1.f }; + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.width = 1; + texParams.height = 1; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.minFilter = gl::GL_NEAREST; + texParams.magFilter = gl::GL_NEAREST; + texParams.name = "Blank AO"; + texParams.texels = &blankAO; + m_blankAO = new Ra::Engine::Data::Texture( texParams ); + m_blankAO->initializeGL(); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthFunc( gl::GL_LEQUAL ); + m_nodeState->depthMask( gl::GL_FALSE ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->blendFunc( gl::GL_ONE, gl::GL_ONE ); + m_nodeState->enable( gl::GL_BLEND ); +} + +void LocalLightingNode::destroy() { + delete m_framebuffer; + delete m_blankAO; + + delete m_nodeState; +} + +void LocalLightingNode::resize( uint32_t width, uint32_t height ) {} + +void LocalLightingNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not used in EnvLightPass + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "LocalLightNode::" + mat->getMaterialName() } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + std::string resourcesRootDir = m_resourceDir + "Shaders/LocalLightNode/"; + // Build the shader configuration + Ra::Engine::Data::ShaderConfiguration theConfig { + { "LocalLightNode::" + mat->getMaterialName() }, + m_resourceDir + "Shaders/shader.vert.glsl", + resourcesRootDir + "shader.frag.glsl" }; + // add the material interface to the fragment shader + theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"", + Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechnique + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool LocalLightingNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + // Lights + auto& lights = m_inLights->getData(); + + // Color tex + m_colorTexture = &m_inColor->getData(); + m_outColor->setData( m_colorTexture ); + + // Depth buffer + auto& depthBuffer = m_inDepth->getData(); + + // Ambiant occlusion buffer + auto aoTexture = m_inAo->isLinked() ? &m_inAo->getData() : m_blankAO; + + // Compute the result + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + + if ( lights.size() > 0 ) { + for ( const auto& l : lights ) { + Ra::Engine::Data::RenderParameters inPassParams; + inPassParams.addParameter( "amb_occ_sampler", m_blankAO ); + l->getRenderParameters( inPassParams ); + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + } + } + currentState->apply(); + m_framebuffer->unbind(); + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.hpp new file mode 100644 index 00000000000..a5f73a2de94 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.hpp @@ -0,0 +1,64 @@ +#pragma once +#include + +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Simple render node : render only opaque objects with direct lighting. + * + */ +class RA_DATAFLOW_API LocalLightingNode : public RenderingNode +{ + public: + explicit LocalLightingNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Local Lighting Node"; } + + private: + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + + Ra::Engine::Data::Texture* m_blankAO { nullptr }; + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + + PortIn* m_inColor { new PortIn( "color", this ) }; + PortIn* m_inDepth { new PortIn( "depth", this ) }; + PortIn* m_inAo { new PortIn( "AO", this ) }; + + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn>* m_inLights { + new PortIn>( "lights", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outColor { new PortOut( "Beauty", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 40f9cfb34f0..46a1fcd7894 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -10,6 +10,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include +#include #include #include @@ -102,6 +103,16 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new LocalLightingNode( "LocalLighting_" + + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + renderingFactory->registerNodeCreator( [resourcesPath, renderingFactory]( const nlohmann::json& data ) { auto node = new SsaoNode( "Ssao_" + std::to_string( renderingFactory->nextNodeId() ) ); diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 636bcba3d20..7eb943cd6b6 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -13,6 +13,7 @@ set(dataflow_rendering_sources Nodes/RenderNodes/EmissivityRenderNode.cpp Nodes/RenderNodes/EnvironmentLightingNode.cpp Nodes/RenderNodes/GeometryAovsNode.cpp + Nodes/RenderNodes/LocalLightingNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp Nodes/RenderNodes/SsaoRenderNode.cpp ) @@ -24,6 +25,7 @@ set(dataflow_rendering_headers Nodes/RenderNodes/EmissivityRenderNode.hpp Nodes/RenderNodes/EnvironmentLightingNode.hpp Nodes/RenderNodes/GeometryAovsNode.hpp + Nodes/RenderNodes/LocalLightingNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp Nodes/RenderNodes/SsaoRenderNode.hpp Nodes/Sinks/DisplaySinkNode.hpp From 4febaafd168bb011469e73a372ee0e8894b70750 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 12:30:26 +0100 Subject: [PATCH 204/239] [dataflow][rendering] fix window compilation. --- src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp index 5fe5874dfbe..73791188bc0 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp +++ b/src/Dataflow/Rendering/Nodes/Sources/TextureSourceNode.cpp @@ -18,8 +18,8 @@ TextureSourceNode::TextureSourceNode( const std::string& instanceName, } bool TextureSourceNode::execute() { - auto interface = static_cast*>( m_interface[0] ); - if ( interface->isLinked() ) { m_texture = &interface->getData(); } + auto interfacePort = static_cast*>( m_interface[0] ); + if ( interfacePort->isLinked() ) { m_texture = &interfacePort->getData(); } m_portOut->setData( m_texture ); return true; } From e178faa286f6dc8e7d65544032402dbaaad01fcc Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 15:42:20 +0100 Subject: [PATCH 205/239] [dataflow][rendering] add TransparencyNode. --- .../TransparentLocalLightingNode.cpp | 234 ++++++++++++++++++ .../TransparentLocalLightingNode.hpp | 83 +++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 11 + .../shader.frag.glsl | 108 ++++++++ src/Dataflow/Rendering/filelist.cmake | 2 + 5 files changed, 438 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/TransparencyLocalLightNode/shader.frag.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.cpp new file mode 100644 index 00000000000..bc895f3fab2 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.cpp @@ -0,0 +1,234 @@ +#include + +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +TransparentLocalLightingNode::TransparentLocalLightingNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inLights ); + m_inLights->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + // TODO, allow to not provide the two following inputs, then using internal textures ? + addInput( m_inColor ); + m_inColor->mustBeLinked(); + addInput( m_inDepth ); + m_inDepth->mustBeLinked(); + + addOutput( m_outColor, m_colorTexture ); + addOutput( m_outRevealage, m_revealageTexture ); + addOutput( m_outAccumulation, m_accumulationTexture ); +} + +void TransparentLocalLightingNode::init() { + m_framebuffer = new globjects::Framebuffer(); + m_oitFramebuffer = new globjects::Framebuffer(); + + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.name = "Accumulation"; + m_accumulationTexture = new Ra::Engine::Data::Texture( texParams ); + texParams.name = "Revealage"; + m_revealageTexture = new Ra::Engine::Data::Texture( texParams ); + + m_outRevealage->setData( m_revealageTexture ); + m_outAccumulation->setData( m_accumulationTexture ); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthFunc( gl::GL_LEQUAL ); + m_nodeState->depthMask( gl::GL_FALSE ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + // no blendEquation in globjects::State + // m_nodeState->blendEquation(gl::GL_FUNC_ADD); + // no blendFunci in globjects::State + // m_nodeState->blendFunci( 0, gl::GL_ONE, gl::GL_ONE ); + // m_nodeState->blendFunci( 1, gl::GL_ZERO, gl::GL_ONE_MINUS_SRC_ALPHA ); + m_nodeState->enable( gl::GL_BLEND ); + + m_composeState = new globjects::State( globjects::State::DeferredMode ); + m_composeState->disable( gl::GL_DEPTH_TEST ); + m_composeState->depthMask( gl::GL_FALSE ); + m_composeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_composeState->blendFunc( gl::GL_ONE_MINUS_SRC_ALPHA, gl::GL_SRC_ALPHA ); + m_composeState->enable( gl::GL_BLEND ); +} + +bool TransparentLocalLightingNode::initInternalShaders() { + if ( !m_hasShaders ) { + Ra::Core::Geometry::TriangleMesh mesh = + Ra::Core::Geometry::makeZNormalQuad( Ra::Core::Vector2( -1.f, 1.f ) ); + auto qm = std::make_unique( "caller" ); + qm->loadGeometry( std::move( mesh ) ); + m_quadMesh = std::move( qm ); + m_quadMesh->updateGL(); + + const std::string composeVertexShader { "layout (location = 0) in vec3 in_position;\n" + "out vec2 varTexcoord;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(in_position, 1.0);\n" + " varTexcoord = (in_position.xy + 1.0) * 0.5;\n" + "}\n" }; + const std::string composeFragmentShader { + "in vec2 varTexcoord;\n" + "out vec4 f_Color;\n" + "uniform sampler2D u_OITSumColor;\n" + "uniform sampler2D u_OITSumWeight;\n" + "void main() {\n" + " float r = texture( u_OITSumWeight, varTexcoord ).r;\n" + " if ( r >= 1.0 ) { discard; }\n" + " vec4 accum = texture( u_OITSumColor, varTexcoord );\n" + " vec3 avg_color = accum.rgb / max( accum.a, 0.00001 );\n" + " f_Color = vec4( avg_color, r );\n" + "}" }; + + Ra::Engine::Data::ShaderConfiguration config { "TransparencyNode::ComposeTransparency" }; + config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, + composeVertexShader ); + config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, + composeFragmentShader ); + if ( m_shader == nullptr ) { + auto added = m_shaderMngr->addShaderProgram( config ); + if ( added ) { + m_shader = added.value(); + m_hasShaders = true; + } + } + } + return m_hasShaders; +} +void TransparentLocalLightingNode::destroy() { + delete m_framebuffer; + delete m_oitFramebuffer; + delete m_accumulationTexture; + delete m_revealageTexture; + + delete m_nodeState; + delete m_composeState; +} + +void TransparentLocalLightingNode::resize( uint32_t width, uint32_t height ) { + m_accumulationTexture->resize( width, height ); + m_revealageTexture->resize( width, height ); + m_oitFramebuffer->bind(); + m_oitFramebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_accumulationTexture->texture() ); + m_oitFramebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT1, m_revealageTexture->texture() ); + m_oitFramebuffer->unbind(); +} + +void TransparentLocalLightingNode::buildRenderTechnique( + const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not used in EnvLightPass + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "TransparencyLocalLightNode::" + mat->getMaterialName() } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + std::string resourcesRootDir = m_resourceDir + "Shaders/TransparencyLocalLightNode/"; + // Build the shader configuration + Ra::Engine::Data::ShaderConfiguration theConfig { + { "TransparencyLocalLightNode::" + mat->getMaterialName() }, + m_resourceDir + "Shaders/shader.vert.glsl", + resourcesRootDir + "shader.frag.glsl" }; + // add the material interface to the fragment shader + theConfig.addInclude( "\"" + mat->getMaterialName() + ".glsl\"", + Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechnique + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool TransparentLocalLightingNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + // Lights + auto& lights = m_inLights->getData(); + + // Color tex + m_colorTexture = &m_inColor->getData(); + m_outColor->setData( m_colorTexture ); + + // Depth buffer + auto& depthBuffer = m_inDepth->getData(); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + Ra::Engine::Data::RenderParameters inPassParams; + + // Render transparent objects + static const float clearZeros[4] = { 0.0, 0.0, 0.0, 1.0 }; + static const float clearOnes[4] = { 1.0, 1.0, 1.0, 1.0 }; + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0, gl::GL_COLOR_ATTACHMENT1 }; + m_oitFramebuffer->bind(); + m_oitFramebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + gl::glDrawBuffers( 2, buffers ); + gl::glClearBufferfv( gl::GL_COLOR, 0, clearZeros ); + gl::glClearBufferfv( gl::GL_COLOR, 1, clearOnes ); + gl::glBlendEquation( gl::GL_FUNC_ADD ); + gl::glBlendFunci( 0, gl::GL_ONE, gl::GL_ONE ); + gl::glBlendFunci( 1, gl::GL_ZERO, gl::GL_ONE_MINUS_SRC_ALPHA ); + if ( lights.size() > 0 ) { + for ( const auto& l : lights ) { + l->getRenderParameters( inPassParams ); + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + } + } + m_oitFramebuffer->detach( gl::GL_DEPTH_ATTACHMENT ); + + // Compute the result + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + gl::glDrawBuffers( 1, buffers ); + m_composeState->apply(); + + m_shader->bind(); + m_shader->setUniform( "u_OITSumColor", m_accumulationTexture, 0 ); + m_shader->setUniform( "u_OITSumWeight", m_revealageTexture, 1 ); + m_quadMesh->render( m_shader ); + + currentState->apply(); + m_framebuffer->unbind(); + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.hpp new file mode 100644 index 00000000000..7c2bb2669e3 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/TransparentLocalLightingNode.hpp @@ -0,0 +1,83 @@ +#pragma once +#include + +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Simple render node : render only opaque objects with direct lighting. + * + */ +class RA_DATAFLOW_API TransparentLocalLightingNode : public RenderingNode +{ + public: + explicit TransparentLocalLightingNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + bool initInternalShaders() override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Transparency Local Lighting Node"; } + + private: + bool m_hasShaders { false }; + /// The accumulation texture + Ra::Engine::Data::Texture* m_accumulationTexture { nullptr }; + + /// The revealage texture + Ra::Engine::Data::Texture* m_revealageTexture { nullptr }; + + /// The framebuffer used to draw the transparent objects + globjects::Framebuffer* m_oitFramebuffer { nullptr }; + + /// The composite shader, owned by the shader manager + const Ra::Engine::Data::ShaderProgram* m_shader { nullptr }; + + /// The fullscreen quad to draw + std::unique_ptr m_quadMesh { nullptr }; + + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + globjects::State* m_composeState; + + PortIn* m_inColor { new PortIn( "color", this ) }; + PortIn* m_inDepth { new PortIn( "depth", this ) }; + + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn>* m_inLights { + new PortIn>( "lights", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outColor { new PortOut( "Beauty", this ) }; + PortOut* m_outRevealage { new PortOut( "revealage", this ) }; + PortOut* m_outAccumulation { new PortOut( "accum", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 46a1fcd7894..b9c3a9fcd8b 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -13,6 +13,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include +#include #include @@ -113,6 +114,16 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new TransparentLocalLightingNode( + "TransparentLocalLighting_" + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + renderingFactory->registerNodeCreator( [resourcesPath, renderingFactory]( const nlohmann::json& data ) { auto node = new SsaoNode( "Ssao_" + std::to_string( renderingFactory->nextNodeId() ) ); diff --git a/src/Dataflow/Rendering/Shaders/TransparencyLocalLightNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/TransparencyLocalLightNode/shader.frag.glsl new file mode 100644 index 00000000000..137f5267093 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/TransparencyLocalLightNode/shader.frag.glsl @@ -0,0 +1,108 @@ +/* +the functions toDiscard and getNormals are given by the implementation of the Material Interface. +The appropriate code will be added at runtime by the shader composer or the render pass initialiser. +*/ +#include "DefaultLight.glsl" +#include "VertexAttribInterface.frag.glsl" + +layout( location = 0 ) out vec4 f_Accumulation; +layout( location = 1 ) out vec4 f_Revealage; + +layout( location = 5 ) in vec3 in_viewVector; +layout( location = 6 ) in vec3 in_lightVector; + +// uniform sampler2D amb_occ_sampler; + +// implementation of weight functions of the paper +// Weighted Blended Order-Independent Transparency +// Morgan McGuire, Louis Bavoil - NVIDIA +// Journal of Computer Graphics Techniques (JCGT), vol. 2, no. 2, 122-141, 2013 +// http://jcgt.org/published/0002/02/09/ + +// remark : manage only non colored transmission. Direct implementation of the above paper without +// the suggested extension : +// ... non-refractive colored transmission can be implemented as a simple extension by processing a +// separate coverage +// value per color channel + +// Note, z range from 0 at the camera to +infinity far away ... + +float weight( float z, float alpha ) { + + // pow(alpha, colorResistance) : increase colorResistance if foreground transparent are + // affecting background transparent color clamp(adjust / f(z), min, max) : + // adjust : Range adjustment to avoid saturating at the clamp bounds + // clamp bounds : to be tuned to avoid over or underflow of the reveleage texture. + // f(z) = 1e-5 + pow(z/depthRange, orederingStrength) + // defRange : Depth range over which significant ordering discrimination is required. Here, + // 10 camera space units. + // Decrease if high-opacity surfaces seem “too transparent”, + // increase if distant transparents are blending together too much. + // orderingStrength : Ordering strength. Increase if background is showing through + // foreground too much. + // 1e-5 + ... : avoid dividing by zero ! + + return pow( alpha, 0.5 ) * clamp( 10 / ( 1e-5 + pow( z / 10, 6 ) ), 1e-2, 3 * 1e3 ); +} + +void main() { + vec3 tc = getPerVertexTexCoord(); + // only render non opaque fragments and not fully transparent fragments + vec4 bc = getBaseColor( material, tc ); + // compute the transparency factor + float a = bc.a; + if ( !toDiscard( material, bc ) || a < 0.001 ) discard; + +#ifdef GLTF_MATERIAL_INTERFACE + // Compute the normal closure. Do normal map and capture state about normal and tangent space + NormalInfo nrm_info = getNormalInfo( material.baseMaterial, tc ); + + // Compute the BSDF closure. Compte all view and lighting independant propserties on the + // material + MaterialInfo bsdf_params; + extractBSDFParameters( material, tc, nrm_info, bsdf_params ); + + // get dynamic lighting informations : wiew and light properties + vec3 wo = normalize( in_viewVector ); // outgoing direction + + // Per local light computation + vec3 wi = normalize( in_lightVector ); // incident direction + vec3 lighting = lightContributionFrom( light, getWorldSpacePosition().xyz ); + + // evaluate the BSDF : get a set of layers representing several optical effects + BsdfInfo layers = evaluateBSDF( material, + bsdf_params, + nrm_info, + wi, + wo, + lightContributionFrom( light, getWorldSpacePosition().xyz ) ); + + // Final evaluation of BSDF/layers closure + vec3 color = combineLayers( bsdf_params, layers, nrm_info, wo ); + +#else + // all vectors are in world space + vec3 binormal = getWorldSpaceBiTangent(); + vec3 normalWorld = + getNormal( material, tc, getWorldSpaceNormal(), getWorldSpaceTangent(), binormal ); + vec3 binormalWorld = normalize( cross( normalWorld, getWorldSpaceTangent() ) ); + vec3 tangentWorld = cross( binormalWorld, normalWorld ); + + // A material is always evaluated in the fragment local Frame + // compute matrix from World to local Frame + mat3 world2local; + world2local[0] = vec3( tangentWorld.x, binormalWorld.x, normalWorld.x ); + world2local[1] = vec3( tangentWorld.y, binormalWorld.y, normalWorld.y ); + world2local[2] = vec3( tangentWorld.z, binormalWorld.z, normalWorld.z ); + // transform all vectors in local frame so that N = (0, 0, 1); + vec3 wi = world2local * normalize( in_lightVector ); // incident direction + vec3 wo = world2local * normalize( in_viewVector ); // outgoing direction + + vec3 color = evaluateBSDF( material, tc, wi, wo ) * + lightContributionFrom( light, getWorldSpacePosition().xyz ); +#endif + + float w = weight( gl_FragCoord.z, a ); + f_Accumulation = vec4( color * a, a ) * w; + f_Revealage = vec4( a ); +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 7eb943cd6b6..d8087ceede2 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -16,6 +16,7 @@ set(dataflow_rendering_sources Nodes/RenderNodes/LocalLightingNode.cpp Nodes/RenderNodes/SimpleRenderNode.cpp Nodes/RenderNodes/SsaoRenderNode.cpp + Nodes/RenderNodes/TransparentLocalLightingNode.cpp ) set(dataflow_rendering_headers @@ -28,6 +29,7 @@ set(dataflow_rendering_headers Nodes/RenderNodes/LocalLightingNode.hpp Nodes/RenderNodes/SimpleRenderNode.hpp Nodes/RenderNodes/SsaoRenderNode.hpp + Nodes/RenderNodes/TransparentLocalLightingNode.hpp Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/Scene.hpp Nodes/Sources/EnvMapSourceNode.hpp From 4975c48ef7cba1e1fd7377df551f2a642bd08f1d Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 16:31:41 +0100 Subject: [PATCH 206/239] [dataflow][rendering] add VolumeNode. --- .../RenderNodes/VolumeLocalLightingNode.cpp | 195 ++++++++++++++++++ .../RenderNodes/VolumeLocalLightingNode.hpp | 79 +++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 11 + src/Dataflow/Rendering/filelist.cmake | 2 + 4 files changed, 287 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.hpp diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp new file mode 100644 index 00000000000..814360ed09a --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp @@ -0,0 +1,195 @@ +#include + +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +VolumeLocalLightingNode::VolumeLocalLightingNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inLights ); + m_inLights->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + // TODO, allow to not provide the two following inputs, then using internal textures ? + addInput( m_inColor ); + m_inColor->mustBeLinked(); + addInput( m_inDepth ); + m_inDepth->mustBeLinked(); + + addOutput( m_outColor, m_colorTexture ); +} + +void VolumeLocalLightingNode::init() { + m_framebuffer = new globjects::Framebuffer(); + m_volumeFramebuffer = new globjects::Framebuffer(); + + Ra::Engine::Data::TextureParameters texParams; + texParams.target = gl::GL_TEXTURE_2D; + texParams.minFilter = gl::GL_LINEAR; + texParams.magFilter = gl::GL_LINEAR; + texParams.internalFormat = gl::GL_RGBA32F; + texParams.format = gl::GL_RGBA; + texParams.type = gl::GL_FLOAT; + texParams.name = "Volume"; + m_volumeTexture = new Ra::Engine::Data::Texture( texParams ); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthFunc( gl::GL_LEQUAL ); + m_nodeState->depthMask( gl::GL_FALSE ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->disable( gl::GL_BLEND ); + + m_composeState = new globjects::State( globjects::State::DeferredMode ); + m_composeState->disable( gl::GL_DEPTH_TEST ); + m_composeState->depthMask( gl::GL_FALSE ); + m_composeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_composeState->blendFunc( gl::GL_ONE_MINUS_SRC_ALPHA, gl::GL_SRC_ALPHA ); + m_composeState->enable( gl::GL_BLEND ); +} + +bool VolumeLocalLightingNode::initInternalShaders() { + if ( m_shader == nullptr ) { + Ra::Core::Geometry::TriangleMesh mesh = + Ra::Core::Geometry::makeZNormalQuad( Ra::Core::Vector2( -1.f, 1.f ) ); + auto qm = std::make_unique( "caller" ); + qm->loadGeometry( std::move( mesh ) ); + m_quadMesh = std::move( qm ); + m_quadMesh->updateGL(); + + const std::string vrtxSrc { "layout (location = 0) in vec3 in_position;\n" + "out vec2 varTexcoord;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(in_position, 1.0);\n" + " varTexcoord = (in_position.xy + 1.0) / 2.0;\n" + "}\n" }; + const std::string frgSrc { + "out vec4 fragColor;\n" + "in vec2 varTexcoord;\n" + "uniform sampler2D volumeImage;\n" + "void main()\n" + "{\n" + " vec2 size = vec2(textureSize(volumeImage, 0));\n" + " vec4 volColor = texelFetch(volumeImage, ivec2(varTexcoord.xy * size), 0);\n" + " if (volColor.a < 1)\n" + " discard;\n" + " fragColor = vec4(volColor.rgb, 0);\n" + "}\n" }; + Ra::Engine::Data::ShaderConfiguration config { "ComposeVolume" }; + config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, vrtxSrc ); + config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, frgSrc ); + auto added = m_shaderMngr->addShaderProgram( config ); + if ( added ) { + m_shader = added.value(); + m_hasShaders = true; + } + } + return m_shader != nullptr; +} +void VolumeLocalLightingNode::destroy() { + delete m_framebuffer; + delete m_volumeFramebuffer; + delete m_volumeTexture; + + delete m_nodeState; + delete m_composeState; +} + +void VolumeLocalLightingNode::resize( uint32_t width, uint32_t height ) { + m_volumeTexture->resize( width, height ); + + m_volumeFramebuffer->bind(); + m_volumeFramebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_volumeTexture->texture() ); + m_volumeFramebuffer->unbind(); +} + +void VolumeLocalLightingNode::buildRenderTechnique( + const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + auto mat = const_cast( ro )->getMaterial(); + // Only volumes are used by this pass + if ( mat->getMaterialAspect() != Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + // use the standard Radium shaders + auto passconfig = + Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( "Volumetric" ); + rt.setConfiguration( *passconfig, m_idx ); + rt.setParametersProvider( mat, m_idx ); +} + +bool VolumeLocalLightingNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + // Lights + auto& lights = m_inLights->getData(); + + // Color tex + m_colorTexture = &m_inColor->getData(); + m_outColor->setData( m_colorTexture ); + + // Depth buffer + auto& depthBuffer = m_inDepth->getData(); + + auto currentState = globjects::State::currentState(); + + // Render transparent objects + static const float clearZeros[4] = { 0.0, 0.0, 0.0, 0.0 }; + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + + m_volumeFramebuffer->bind(); + m_nodeState->apply(); + m_volumeFramebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + + gl::glDrawBuffers( 1, buffers ); + gl::glClearBufferfv( gl::GL_COLOR, 0, clearZeros ); + Ra::Engine::Data::RenderParameters inPassParams; + inPassParams.addParameter( "imageColor", m_colorTexture ); + inPassParams.addParameter( "imageDepth", depthBuffer.texture() ); + if ( lights.size() > 0 ) { + for ( const auto& l : lights ) { + l->getRenderParameters( inPassParams ); + for ( const auto& ro : renderObjects ) { + ro->render( inPassParams, camera, m_idx ); + } + } + } + // m_volumeFramebuffer->detach( gl::GL_DEPTH_ATTACHMENT ); + + // Compute the result + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + gl::glDrawBuffers( 1, buffers ); + m_composeState->apply(); + + m_shader->bind(); + m_shader->setUniform( "volumeImage", m_volumeTexture, 0 ); + m_quadMesh->render( m_shader ); + + currentState->apply(); + m_framebuffer->unbind(); + // Set output port + // here, they are already set by constructor + return true; +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.hpp new file mode 100644 index 00000000000..e73c53bb94c --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.hpp @@ -0,0 +1,79 @@ +#pragma once +#include + +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Simple render node : render only opaque objects with direct lighting. + * + */ +class RA_DATAFLOW_API VolumeLocalLightingNode : public RenderingNode +{ + public: + explicit VolumeLocalLightingNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + bool initInternalShaders() override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Volume Local Lighting Node"; } + + private: + bool m_hasShaders { false }; + + /// The texture to draw the volume on + Ra::Engine::Data::Texture* m_volumeTexture { nullptr }; + + /// The framebuffer used to draw the volume + globjects::Framebuffer* m_volumeFramebuffer { nullptr }; + + /// The composite shader, owned by the shader manager + const Ra::Engine::Data::ShaderProgram* m_shader { nullptr }; + + /// The fullscreen quad to draw + std::unique_ptr m_quadMesh { nullptr }; + + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + globjects::State* m_composeState; + + PortIn* m_inColor { new PortIn( "color", this ) }; + PortIn* m_inDepth { new PortIn( "depth", this ) }; + + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn>* m_inLights { + new PortIn>( "lights", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outColor { new PortOut( "Beauty", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index b9c3a9fcd8b..90078af927d 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -14,6 +14,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include +#include #include @@ -114,6 +115,16 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new VolumeLocalLightingNode( + "VolumeLocalLighting_" + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + renderingFactory->registerNodeCreator( [resourcesPath, renderingFactory]( const nlohmann::json& data ) { auto node = new TransparentLocalLightingNode( diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index d8087ceede2..4d7632c4bf6 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -17,6 +17,7 @@ set(dataflow_rendering_sources Nodes/RenderNodes/SimpleRenderNode.cpp Nodes/RenderNodes/SsaoRenderNode.cpp Nodes/RenderNodes/TransparentLocalLightingNode.cpp + Nodes/RenderNodes/VolumeLocalLightingNode.cpp ) set(dataflow_rendering_headers @@ -30,6 +31,7 @@ set(dataflow_rendering_headers Nodes/RenderNodes/SimpleRenderNode.hpp Nodes/RenderNodes/SsaoRenderNode.hpp Nodes/RenderNodes/TransparentLocalLightingNode.hpp + Nodes/RenderNodes/VolumeLocalLightingNode.hpp Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/Scene.hpp Nodes/Sources/EnvMapSourceNode.hpp From 6ee973e696bfeca6df8bc41fbee71b6558550b92 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 17:40:27 +0100 Subject: [PATCH 207/239] [dataflow][rendering] add WireframeNode. --- .../RenderNodes/WireframeRenderingNode.cpp | 263 ++++++++++++++++++ .../RenderNodes/WireframeRenderingNode.hpp | 82 ++++++ .../Nodes/RenderingBuiltInsNodes.cpp | 11 + .../Shaders/WireframeNode/Wireframe.frag.glsl | 20 ++ .../Shaders/WireframeNode/Wireframe.geom.glsl | 67 +++++ .../Shaders/WireframeNode/Wireframe.vert.glsl | 13 + src/Dataflow/Rendering/filelist.cmake | 2 + 7 files changed, 458 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp create mode 100644 src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.frag.glsl create mode 100644 src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.geom.glsl create mode 100644 src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.vert.glsl diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp new file mode 100644 index 00000000000..8c8a9a6cf1d --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp @@ -0,0 +1,263 @@ + + +#include + +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +// Warning : this will be removed when index layers will be available on Radium +namespace WireframeNodeInternal { + +template +void computeIndices( Ra::Core::Geometry::LineMesh::IndexContainerType& indices, + IndexContainerType& other ) { + + for ( const auto& index : other ) { + auto s = index.size(); + for ( unsigned int i = 0; i < s; ++i ) { + int i1 = index[i]; + int i2 = index[( i + 1 ) % s]; + if ( i1 > i2 ) std::swap( i1, i2 ); + indices.emplace_back( i1, i2 ); + } + } + + std::sort( indices.begin(), + indices.end(), + []( const Ra::Core::Geometry::LineMesh::IndexType& a, + const Ra::Core::Geometry::LineMesh::IndexType& b ) { + return a[0] < b[0] || ( a[0] == b[0] && a[1] < b[1] ); + } ); + indices.erase( std::unique( indices.begin(), indices.end() ), indices.end() ); +} + +// store LineMesh and Core, define the observer functor to update data one core update for wireframe +// linemesh +template +class VerticesUpdater +{ + public: + VerticesUpdater( std::shared_ptr disp, CoreGeometry& core ) : + m_disp { disp }, m_core { core } {}; + + void operator()() { m_disp->getCoreGeometry().setVertices( m_core.vertices() ); } + std::shared_ptr m_disp; + CoreGeometry& m_core; +}; + +template +class IndicesUpdater +{ + public: + IndicesUpdater( std::shared_ptr disp, CoreGeometry& core ) : + m_disp { disp }, m_core { core } {}; + + void operator()() { + auto lineIndices = m_disp->getCoreGeometry().getIndicesWithLock(); + computeIndices( lineIndices, m_core.getIndices() ); + m_disp->getCoreGeometry().indicesUnlock(); + } + std::shared_ptr m_disp; + CoreGeometry& m_core; +}; + +// create a linemesh to draw wireframe given a core mesh +template +void setupLineMesh( std::shared_ptr& disp, CoreGeometry& core ) { + Ra::Core::Geometry::LineMesh lines; + Ra::Core::Geometry::LineMesh::IndexContainerType indices; + lines.setVertices( core.vertices() ); + computeIndices( indices, core.getIndices() ); + if ( indices.size() > 0 ) { + lines.setIndices( std::move( indices ) ); + disp = std::make_shared( std::string( "wireframe" ), + std::move( lines ) ); + disp->updateGL(); + // add observer + auto handle = core.template getAttribHandle( + Ra::Core::Geometry::getAttribName( Ra::Core::Geometry::VERTEX_POSITION ) ); + + core.vertexAttribs().getAttrib( handle ).attach( VerticesUpdater( disp, core ) ); + core.attach( IndicesUpdater( disp, core ) ); + } + else { + disp.reset(); + } +} + +template +void processLineMesh( const std::shared_ptr& m, + std::shared_ptr& r ) { + if ( m->getRenderMode() == + Ra::Engine::Data::AttribArrayDisplayable::MeshRenderMode::RM_TRIANGLES ) { + setupLineMesh( r, m->getCoreGeometry() ); + } +} + +} // namespace WireframeNodeInternal + +WireframeRenderingNode::WireframeRenderingNode( const std::string& name ) : + RenderingNode( name, getTypename() ) { + + addInput( m_inObjects ); + m_inObjects->mustBeLinked(); + addInput( m_inCamera ); + m_inCamera->mustBeLinked(); + + // TODO, allow to not provide the two following inputs, then using internal textures ? + addInput( m_inColor ); + m_inColor->mustBeLinked(); + addInput( m_inDepth ); + m_inDepth->mustBeLinked(); + + addInput( m_inActivated ); + + addOutput( m_outColor, m_colorTexture ); + addEditableParameter( m_editableActivate ); +} + +void WireframeRenderingNode::init() { + m_framebuffer = new globjects::Framebuffer(); + + m_nodeState = new globjects::State( globjects::State::DeferredMode ); + m_nodeState->enable( gl::GL_DEPTH_TEST ); + m_nodeState->depthFunc( gl::GL_LESS ); + m_nodeState->depthMask( gl::GL_FALSE ); + m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + m_nodeState->blendFuncSeparate( + gl::GL_SRC_ALPHA, gl::GL_ONE_MINUS_SRC_ALPHA, gl::GL_ONE, gl::GL_ZERO ); + // no glBlendEquationSeparate in globjects::State + m_nodeState->enable( gl::GL_BLEND ); + m_nodeState->polygonOffset( -1.1f, -1.0f ); + m_nodeState->enable( gl::GL_POLYGON_OFFSET_FILL ); +} + +void WireframeRenderingNode::destroy() { + delete m_framebuffer; + + delete m_nodeState; +} + +void WireframeRenderingNode::resize( uint32_t width, uint32_t height ) { + m_wireframeWidth = width; + m_wireframeHeight = height; +} + +void WireframeRenderingNode::buildRenderTechnique( + const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const { + // TODO, use the Radium shader instead of the specific one --> wait for PR "small fixes" (#1035) + // before that + std::string resourcesRootDir = m_resourceDir + "Shaders/WireframeNode/"; + auto mat = const_cast( ro )->getMaterial(); + // Volumes are not used in WireframePass + if ( mat->getMaterialAspect() == Ra::Engine::Data::Material::MaterialAspect::MAT_DENSITY ) { + return; + } + if ( auto cfg = Ra::Engine::Data::ShaderConfigurationFactory::getConfiguration( + { "WireframeNode::WireframeRendering" } ) ) { + rt.setConfiguration( *cfg, m_idx ); + } + else { + Ra::Engine::Data::ShaderConfiguration theConfig { { "WireframeNode::WireframeRendering" }, + resourcesRootDir + "Wireframe.vert.glsl", + resourcesRootDir + + "Wireframe.frag.glsl" }; + theConfig.addShader( Ra::Engine::Data::ShaderType_GEOMETRY, + resourcesRootDir + "Wireframe.geom.glsl" ); + // Add to the ShaderConfigManager + Ra::Engine::Data::ShaderConfigurationFactory::addConfiguration( theConfig ); + // Add to the RenderTechnique + rt.setConfiguration( theConfig, m_idx ); + } + rt.setParametersProvider( mat, m_idx ); +} + +bool WireframeRenderingNode::execute() { + // Get parameters + // Render objects + auto& renderObjects = m_inObjects->getData(); + // Cameras + auto& camera = m_inCamera->getData(); + + // Color tex + m_colorTexture = &m_inColor->getData(); + m_outColor->setData( m_colorTexture ); + + // Depth buffer + auto& depthBuffer = m_inDepth->getData(); + + // activation : + auto activated = m_inActivated->isLinked() ? m_inActivated->getData() : m_activate; + + // Compute the result + if ( activated ) { + if ( m_wireframes.size() > renderObjects.size() ) { m_wireframes.clear(); } + + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_DEPTH_ATTACHMENT, depthBuffer.texture() ); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + + auto currentState = globjects::State::currentState(); + m_nodeState->apply(); + gl::glBlendEquationSeparate( gl::GL_FUNC_ADD, gl::GL_FUNC_ADD ); + + for ( const auto& ro : renderObjects ) { + if ( ro->isVisible() ) { + std::shared_ptr wro; + + auto it = m_wireframes.find( ro.get() ); + if ( it == m_wireframes.end() ) { + using trimesh = + Ra::Engine::Data::IndexedGeometry; + using polymesh = + Ra::Engine::Data::IndexedGeometry; + // TODO : add here quadmesh as in #925 + std::shared_ptr disp; + auto displayable = ro->getMesh(); + auto tm = std::dynamic_pointer_cast( displayable ); + auto tp = std::dynamic_pointer_cast( displayable ); + if ( tm ) { WireframeNodeInternal::processLineMesh( tm, disp ); } + if ( tp ) { WireframeNodeInternal::processLineMesh( tp, disp ); } + m_wireframes[ro.get()] = disp; + wro = disp; + } + else { + wro = it->second; + } + + auto shader = m_shaderMngr->getShaderProgram( "WireframeNode::WireframeRendering" ); + if ( wro && shader ) { + wro->updateGL(); + shader->bind(); + Ra::Core::Matrix4 modelMatrix = ro->getTransformAsMatrix(); + shader->setUniform( "transform.proj", camera.projMatrix ); + shader->setUniform( "transform.view", camera.viewMatrix ); + shader->setUniform( "transform.model", modelMatrix ); + shader->setUniform( "viewport", + Ra::Core::Vector2 { m_wireframeWidth, m_wireframeHeight } ); + wro->render( shader ); + } + } + } + + currentState->apply(); + m_framebuffer->unbind(); + } + return true; +} +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp new file mode 100644 index 00000000000..afd6eb6a736 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp @@ -0,0 +1,82 @@ +#pragma once +#include + +#include + +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { + +namespace Nodes { +using namespace Ra::Dataflow::Core; +/** + * \brief Render a scene using an environment map as light source. + * + */ +class RA_DATAFLOW_API WireframeRenderingNode : public RenderingNode +{ + public: + explicit WireframeRenderingNode( const std::string& name ); + + void init() override; + + bool execute() override; + + void destroy() override; + + void resize( uint32_t width, uint32_t height ) override; + + void buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, + Ra::Engine::Rendering::RenderTechnique& rt ) const override; + + bool hasRenderTechnique() override { return true; } + + static const std::string getTypename() { return "Wireframe Node"; } + + protected: + void toJsonInternal( nlohmann::json& data ) const override { data["activated"] = m_activate; } + + bool fromJsonInternal( const nlohmann::json& data ) override { + if ( data.contains( "activated" ) ) { m_activate = data["activated"]; } + else { + m_activate = false; + } + return true; + } + + private: + Ra::Engine::Data::Texture* m_colorTexture; + + globjects::Framebuffer* m_framebuffer { nullptr }; + + globjects::State* m_nodeState; + + using WireMap = std::map>; + mutable WireMap m_wireframes; + + uint m_wireframeWidth { 0 }; + uint m_wireframeHeight { 0 }; + + bool m_activate { false }; + + EditableParameter* m_editableActivate { + new EditableParameter( "activated", m_activate ) }; + + PortIn* m_inColor { new PortIn( "color", this ) }; + PortIn* m_inDepth { new PortIn( "depth", this ) }; + PortIn* m_inActivated { new PortIn( "activate", this ) }; + PortIn>* m_inObjects { + new PortIn>( "objects", this ) }; + PortIn* m_inCamera { new PortIn( "camera", this ) }; + + PortOut* m_outColor { new PortOut( "Beauty", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 90078af927d..c237ff4843f 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -15,6 +15,7 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include #include +#include #include @@ -135,6 +136,16 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& data ) { + auto node = new WireframeRenderingNode( + "WirframeNode_" + std::to_string( renderingFactory->nextNodeId() ) ); + node->fromJson( data ); + node->setResourcesDir( resourcesPath ); + return node; + }, + "Render" ); + renderingFactory->registerNodeCreator( [resourcesPath, renderingFactory]( const nlohmann::json& data ) { auto node = new SsaoNode( "Ssao_" + std::to_string( renderingFactory->nextNodeId() ) ); diff --git a/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.frag.glsl b/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.frag.glsl new file mode 100644 index 00000000000..242120d6541 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.frag.glsl @@ -0,0 +1,20 @@ +in vec4 gColor; +out vec4 fragColor; +in float pixelWidthDiv2; + +const float PI = 3.1415926535897932384626433832795; + +// AA is one pixel wide after pixelWidth of fully filled. + +float aa( in float dist ) { + float R = sqrt( 2. * .5 * .5 ); + float s = sign( dist ); + float d = max( min( dist - pixelWidthDiv2 + s * R, R ), -R ); + + float theta = 2. * acos( max( min( d / R, 1. ), -1 ) ); + return clamp( R * R / 2. * ( theta - sin( theta ) ) / ( R * R * PI ), 0., 1. ); +} +void main() { + float a = aa( gColor.a ) * aa( -gColor.a ); + fragColor = vec4( vec3( .8, .9, 1. ), a ); +} diff --git a/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.geom.glsl b/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.geom.glsl new file mode 100644 index 00000000000..3933f78bcdf --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.geom.glsl @@ -0,0 +1,67 @@ +layout( lines ) in; +layout( triangle_strip, max_vertices = 4 ) out; + +in gl_PerVertex { + vec4 gl_Position; + float gl_PointSize; + float gl_ClipDistance[]; +} +gl_in[]; + +out gl_PerVertex { + vec4 gl_Position; + float gl_PointSize; + float gl_ClipDistance[]; +}; + +in vec4 vPosition[2]; +// in vec4 vColor[2]; + +out vec4 gColor; +out float pixelWidthDiv2; +uniform vec2 viewport; +vec4 vColor[2]; + +void main() { + vec3 vp = vec3( viewport, 1. ); + + // clip space + vec4 css0 = gl_in[0].gl_Position; + vec4 css1 = gl_in[1].gl_Position; + + // coherent clip space + vec4 scss0 = css0 * css1.w; + vec4 scss1 = css1 * css0.w; + + vec3 dir = ( scss1 - scss0 ).xyz; + float pixelWidth = 1.8; + const float border = 4.; + vec3 slope = normalize( vec3( -dir.y, dir.x, 0 ) ); + vec4 n = vec4( vec3( pixelWidth + border ) / vp * slope, 0 ); + + vec4 a = vec4( ( scss0 + n * scss0.w ) ); + vec4 b = vec4( ( scss0 - n * scss0.w ) ); + vec4 c = vec4( ( scss1 + n * scss0.w ) ); + vec4 d = vec4( ( scss1 - n * scss0.w ) ); + pixelWidthDiv2 = pixelWidth / 2.; + vColor[0] = vec4( vec3( .7 ), -pixelWidthDiv2 - border / 2. ); + vColor[1] = vec4( vec3( .7 ), +pixelWidthDiv2 + border / 2. ); + + gColor = vColor[0]; + gl_Position = a; + EmitVertex(); + + gColor = vColor[1]; + gl_Position = b; + EmitVertex(); + + gColor = vColor[0]; + gl_Position = c; + EmitVertex(); + + gColor = vColor[1]; + gl_Position = d; + EmitVertex(); + + EndPrimitive(); +} diff --git a/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.vert.glsl b/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.vert.glsl new file mode 100644 index 00000000000..c68593cb2a2 --- /dev/null +++ b/src/Dataflow/Rendering/Shaders/WireframeNode/Wireframe.vert.glsl @@ -0,0 +1,13 @@ +#include "TransformStructs.glsl" + +layout( location = 0 ) in vec3 in_position; +out vec4 vPosition; + +uniform Transform transform; + +void main() { + mat4 mvp = transform.proj * transform.view * transform.model; + vec4 pos = mvp * vec4( in_position.xyz, 1.0 ); + gl_Position = pos; + vPosition = pos; +} diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 4d7632c4bf6..101f13eeb5a 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -18,6 +18,7 @@ set(dataflow_rendering_sources Nodes/RenderNodes/SsaoRenderNode.cpp Nodes/RenderNodes/TransparentLocalLightingNode.cpp Nodes/RenderNodes/VolumeLocalLightingNode.cpp + Nodes/RenderNodes/WireframeRenderingNode.cpp ) set(dataflow_rendering_headers @@ -32,6 +33,7 @@ set(dataflow_rendering_headers Nodes/RenderNodes/SsaoRenderNode.hpp Nodes/RenderNodes/TransparentLocalLightingNode.hpp Nodes/RenderNodes/VolumeLocalLightingNode.hpp + Nodes/RenderNodes/WireframeRenderingNode.hpp Nodes/Sinks/DisplaySinkNode.hpp Nodes/Sources/Scene.hpp Nodes/Sources/EnvMapSourceNode.hpp From bc41fb0285c5401e5adf96b06d71a5724f8d7f1b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 18:40:55 +0100 Subject: [PATCH 208/239] [dataflow][rendering] fix envmap shader for non PBR materials. --- src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl index 165dd5b9635..d43c2e7d021 100644 --- a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl +++ b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl @@ -113,7 +113,7 @@ void main() { vec3 spec = clamp( specular * cosTi * OneOverPi * 0.5, 0.001, 1. ); float r = getGGXRoughness( material, getPerVertexTexCoord() ) * numLod; bc.rgb += textureLod( envTexture, rfl, r ).rgb * spec; - bc.rgb *= ao * envStrength; + bc.rgb *= ao * envStrength * OneOverPi * 0.5 ; } else { bc.rgb = vec3( 0 ); } From c372a895a92bbbbad511863d1c536624ca1f2403 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 20:48:04 +0100 Subject: [PATCH 209/239] [dataflow][rendering] improve node and ports naming --- src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp index aa74b0e888a..41868bfc36a 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp @@ -49,11 +49,10 @@ class RA_DATAFLOW_API ClearColorNode : public RenderingNode ColorType m_editableClearColor { ColorType::Grey( 0.12_ra ) }; - PortIn* m_portInColorTex { - new PortIn( "colorTextureToClear", this ) }; + PortIn* m_portInColorTex { new PortIn( "textureToClear", this ) }; PortIn* m_portInClearColor { new PortIn( "clearColor", this ) }; PortIn* m_portInEnvmap { new PortIn( "environment", this ) }; - PortIn* m_portInCamera { new PortIn( "cameras", this ) }; + PortIn* m_portInCamera { new PortIn( "camera", this ) }; PortOut* m_portOutColorTex { new PortOut( "image", this ) }; }; From 3657d6cdaaaebfaa04abb1c03a46769607cbefae Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 22:32:24 +0100 Subject: [PATCH 210/239] [dataflow][rendering] update to editable constraint --- .../Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp index ae42e952291..de469fba06d 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp @@ -39,14 +39,14 @@ SsaoNode::SsaoNode( const std::string& name ) : RenderingNode( name, getTypename addOutput( m_ssao, m_AO ); - auto editableRadius = new EditableParameter( "radius", m_editableAORadius ); - editableRadius->addAdditionalData( 0. ); - editableRadius->addAdditionalData( 100. ); + auto editableRadius = new EditableParameter( "radius", m_editableAORadius ); + nlohmann::json radiusConstraints = { { "min", 0. }, { "max", 100. } }; + editableRadius->setConstraints( radiusConstraints ); addEditableParameter( editableRadius ); - auto editableSamples = new EditableParameter( "samples", m_editableSamples ); - editableSamples->addAdditionalData( 0 ); - editableSamples->addAdditionalData( 4096 ); + auto editableSamples = new EditableParameter( "samples", m_editableSamples ); + nlohmann::json sampleConstraints = { { "min", 0 }, { "max", 4096 } }; + editableSamples->setConstraints( sampleConstraints ); addEditableParameter( editableSamples ); } From b470e631ae28cb4e8bcb93fceedc516e81bb83a1 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 22:47:29 +0100 Subject: [PATCH 211/239] [dataflow][rendering] add constraints on ssao node parameters --- .../Rendering/Nodes/RenderNodes/LocalLightingNode.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp index 5c521fdbba5..c753e0bbb4e 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp @@ -64,7 +64,7 @@ void LocalLightingNode::destroy() { delete m_nodeState; } -void LocalLightingNode::resize( uint32_t width, uint32_t height ) {} +void LocalLightingNode::resize( uint32_t, uint32_t ) {} void LocalLightingNode::buildRenderTechnique( const Ra::Engine::Rendering::RenderObject* ro, Ra::Engine::Rendering::RenderTechnique& rt ) const { @@ -130,7 +130,8 @@ bool LocalLightingNode::execute() { if ( lights.size() > 0 ) { for ( const auto& l : lights ) { Ra::Engine::Data::RenderParameters inPassParams; - inPassParams.addParameter( "amb_occ_sampler", m_blankAO ); + // Ambient occlusion is not really meaningful for local lighting + inPassParams.addParameter( "amb_occ_sampler", /* aoTexture->texture() */ m_blankAO ); l->getRenderParameters( inPassParams ); for ( const auto& ro : renderObjects ) { ro->render( inPassParams, camera, m_idx ); From df96d3c6c42b3c6469d1872ccbca114de7ec349e Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 23:47:39 +0100 Subject: [PATCH 212/239] [dataflow][rendering] add antialiasing --- .../Rendering/Nodes/AntiAliasing/FxaaNode.cpp | 174 ++++++++++++++++++ .../Rendering/Nodes/AntiAliasing/FxaaNode.hpp | 46 +++++ .../Nodes/RenderingBuiltInsNodes.cpp | 10 + src/Dataflow/Rendering/filelist.cmake | 2 + 4 files changed, 232 insertions(+) create mode 100644 src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.cpp create mode 100644 src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.hpp diff --git a/src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.cpp b/src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.cpp new file mode 100644 index 00000000000..f904bd05398 --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.cpp @@ -0,0 +1,174 @@ +#include + +#include +#include +#include +#include +#include +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { + +using namespace Ra::Engine::Data; + +FxaaNode::FxaaNode( const std::string& name ) : RenderingNode( name, getTypename() ) { + addInput( m_inColor ); + m_inColor->mustBeLinked(); + + Ra::Engine::Data::TextureParameters colorTexParams = { "FXAA image", + gl::GL_TEXTURE_2D, + 1, + 1, + 1, + gl::GL_RGBA, + gl::GL_RGBA32F, + gl::GL_FLOAT, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_LINEAR, + gl::GL_LINEAR, + nullptr }; + m_colorTexture = new Ra::Engine::Data::Texture( colorTexParams ); + + addOutput( m_outColor, m_colorTexture ); +} + +void FxaaNode::init() { + m_framebuffer = new globjects::Framebuffer(); +} + +void FxaaNode::destroy() { + delete m_framebuffer; + delete m_colorTexture; +} + +void FxaaNode::resize( uint32_t width, uint32_t height ) { + m_colorTexture->resize( width, height ); +} + +bool FxaaNode::execute() { + m_framebuffer->bind(); + m_framebuffer->attachTexture( gl::GL_COLOR_ATTACHMENT0, m_colorTexture->texture() ); + const gl::GLenum buffers[] = { gl::GL_COLOR_ATTACHMENT0 }; + gl::glDrawBuffers( 1, buffers ); + gl::glDisable( gl::GL_DEPTH_TEST ); + gl::glDepthMask( gl::GL_FALSE ); + gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); + gl::glDisable( gl::GL_BLEND ); + + float clearBlack[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + gl::glClearBufferfv( gl::GL_COLOR, 0, clearBlack ); + + m_shader->bind(); + m_shader->setUniform( "tex1_sampler", &m_inColor->getData(), 0 ); + m_quadMesh->render( m_shader ); + m_framebuffer->unbind(); + return true; +} + +bool FxaaNode::initInternalShaders() { + if ( m_quadMesh == nullptr ) { + Ra::Core::Geometry::TriangleMesh mesh = + Ra::Core::Geometry::makeZNormalQuad( Ra::Core::Vector2( -1.f, 1.f ) ); + auto qm = std::make_unique( "caller" ); + qm->loadGeometry( std::move( mesh ) ); + m_quadMesh = std::move( qm ); + m_quadMesh->updateGL(); + } + + if ( m_shader == nullptr ) { + const std::string composeVertexShader { "layout (location = 0) in vec3 in_position;\n" + "out vec2 varTexcoord;\n" + "void main()\n" + "{\n" + " gl_Position = vec4(in_position, 1.0);\n" + " varTexcoord = (in_position.xy + 1.0) * 0.5;\n" + "}\n" }; + const std::string composeFragmentShader { + "layout (location = 0) out vec4 out_tex;\n" + "uniform sampler2D tex1_sampler;\n" + "in vec2 varTexcoord;\n" + "const float THRESHOLD = 0.0312;\n" + "const float RELATIVE_THRESHOLD = 0.125;\n" + "void main() {\n" + "vec2 texSize = vec2(textureSize(tex1_sampler, 0));\n" + "vec2 texelSize = 1.0 / texSize;\n" + "vec3 n = texture(tex1_sampler, varTexcoord + (vec2(0.0, -1.0) * texelSize)).rgb;\n" + "vec3 s = texture(tex1_sampler, varTexcoord + (vec2(0.0, 1.0) * texelSize)).rgb;\n" + "vec3 e = texture(tex1_sampler, varTexcoord + (vec2(1.0, 0.0) * texelSize)).rgb;\n" + "vec3 w = texture(tex1_sampler, varTexcoord + (vec2(-1.0, 0.0) * texelSize)).rgb;\n" + "vec3 m = texture(tex1_sampler, varTexcoord).rgb;\n" + "vec3 brightnessCoefficients = vec3(0.2126, 0.7152, 0.0722);\n" + "float brightnessN = dot(n, brightnessCoefficients);\n" + "float brightnessS = dot(s, brightnessCoefficients);\n" + "float brightnessE = dot(e, brightnessCoefficients);\n" + "float brightnessW = dot(w, brightnessCoefficients);\n" + "float brightnessM = dot(m, brightnessCoefficients);\n" + "float brightnessMin = min(brightnessM, min(min(brightnessN, brightnessS), " + "min(brightnessE, brightnessW)));\n" + "float brightnessMax = max(brightnessM, max(max(brightnessN, brightnessS), " + "max(brightnessE, brightnessW)));\n" + "float contrast = brightnessMax - brightnessMin;\n" + "float threshold = max(THRESHOLD, RELATIVE_THRESHOLD * brightnessMax);\n" + "if (contrast < threshold) {\n" + "out_tex = vec4(m, 1.0);\n" + "}\n" + "else {\n" + "vec3 nw = texture(tex1_sampler, varTexcoord + (vec2(-1.0, -1.0) * texelSize)).rgb;\n" + "vec3 ne = texture(tex1_sampler, varTexcoord + (vec2(1.0, -1.0) * texelSize)).rgb;\n" + "vec3 sw = texture(tex1_sampler, varTexcoord + (vec2(-1.0, 1.0) * texelSize)).rgb;\n" + "vec3 se = texture(tex1_sampler, varTexcoord + (vec2(1.0, 1.0) * texelSize)).rgb;\n" + "float brightnessNW = dot(nw, brightnessCoefficients);\n" + "float brightnessNE = dot(ne, brightnessCoefficients);\n" + "float brightnessSW = dot(sw, brightnessCoefficients);\n" + "float brightnessSE = dot(se, brightnessCoefficients);\n" + "float factor = 2 * (brightnessN + brightnessS + brightnessE + brightnessW);\n" + "factor += (brightnessNW + brightnessNE + brightnessSW + brightnessSE);\n" + "factor *= (1.0 / 12.0);\n" + "factor = abs(factor - brightnessM);\n" + "factor = clamp(factor / contrast, 0.0, 1.0);\n" + "factor = smoothstep(0.0, 1.0, factor);\n" + "factor = factor * factor;\n" + "float horizontal = abs(brightnessN + brightnessS - (2 * brightnessM)) * 2 +\n" + "abs(brightnessNE + brightnessSE - (2 * brightnessE)) +\n" + "abs(brightnessNW + brightnessSW - (2 * brightnessW));\n" + "float vertical = abs(brightnessE + brightnessW - (2 * brightnessM)) * 2 +\n" + "abs(brightnessNE + brightnessSE - (2 * brightnessN)) +\n" + "abs(brightnessNW + brightnessSW - (2 * brightnessS));\n" + "bool isHorizontal = horizontal > vertical;\n" + "float pixelStep = isHorizontal ? texelSize.y : texelSize.x;\n" + "float posBrightness = isHorizontal ? brightnessS : brightnessE;\n" + "float negBrightness = isHorizontal ? brightnessN : brightnessW;\n" + "float posGradient = abs(posBrightness - brightnessM);\n" + "float negGradient = abs(negBrightness - brightnessM);\n" + "pixelStep *= (posGradient < negGradient) ? -1 : 1;\n" + "vec2 blendUV = varTexcoord;\n" + "if (isHorizontal) {\n" + "blendUV.y = varTexcoord.y + (pixelStep * factor);\n" + "}\n" + "else {\n" + "blendUV.x = varTexcoord.x + (pixelStep * factor); \n" + "}\n" + "out_tex = texture(tex1_sampler, blendUV);\n" + "}\n" + "}" }; + + Ra::Engine::Data::ShaderConfiguration config { "ComposeMax" }; + config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, + composeVertexShader ); + config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_FRAGMENT, + composeFragmentShader ); + auto added = m_shaderMngr->addShaderProgram( config ); + if ( added ) { m_shader = added.value(); } + } + return ( m_shader != nullptr && m_quadMesh != nullptr ); +} + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.hpp b/src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.hpp new file mode 100644 index 00000000000..f6ecb13778f --- /dev/null +++ b/src/Dataflow/Rendering/Nodes/AntiAliasing/FxaaNode.hpp @@ -0,0 +1,46 @@ +#pragma once +#include + +#include + +#include + +namespace Ra { +namespace Dataflow { +namespace Rendering { +namespace Nodes { +using namespace Ra::Dataflow::Core; +using namespace Ra::Engine::Data; + +/** + * \brief compute antialiasing in image space from color buffer. + * + */ +class RA_DATAFLOW_API FxaaNode : public RenderingNode +{ + public: + explicit FxaaNode( const std::string& name ); + + void init() override; + bool execute() override; + void destroy() override; + void resize( uint32_t width, uint32_t height ) override; + bool initInternalShaders() override; + + static const std::string getTypename() { return "FXAA Node"; } + + private: + Ra::Engine::Data::Texture* m_colorTexture { nullptr }; + + globjects::Framebuffer* m_framebuffer { nullptr }; + std::unique_ptr m_quadMesh { nullptr }; + const Ra::Engine::Data::ShaderProgram* m_shader { nullptr }; + + PortIn* m_inColor { new PortIn( "color", this ) }; + PortOut* m_outColor { new PortOut( "Beauty", this ) }; +}; + +} // namespace Nodes +} // namespace Rendering +} // namespace Dataflow +} // namespace Ra diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index c237ff4843f..77cf8688c90 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -17,6 +17,8 @@ DATAFLOW_LIBRARY_INITIALIZER_DECL( RenderingNodes ); #include #include +#include + #include #include @@ -155,6 +157,14 @@ std::string registerRenderingNodesFactories() { }, "Render" ); + /* --- Image processing --- */ + renderingFactory->registerNodeCreator( + [resourcesPath, renderingFactory]( const nlohmann::json& ) { + auto node = new FxaaNode( "Fxaa_" + std::to_string( renderingFactory->nextNodeId() ) ); + return node; + }, + "Image processing" ); + /* --- Graphs --- */ renderingFactory->registerNodeCreator( RenderingGraph::getTypename() + "_", "Graph" ); diff --git a/src/Dataflow/Rendering/filelist.cmake b/src/Dataflow/Rendering/filelist.cmake index 101f13eeb5a..c277bfa8f09 100644 --- a/src/Dataflow/Rendering/filelist.cmake +++ b/src/Dataflow/Rendering/filelist.cmake @@ -6,6 +6,7 @@ set(dataflow_rendering_sources Renderer/ControllableRenderer.cpp Renderer/RenderGraphController.cpp RenderingGraph.cpp + Nodes/AntiAliasing/FxaaNode.cpp Nodes/RenderingBuiltInsNodes.cpp Nodes/Sinks/DisplaySinkNode.cpp Nodes/Sources/TextureSourceNode.cpp @@ -24,6 +25,7 @@ set(dataflow_rendering_sources set(dataflow_rendering_headers Nodes/RenderingBuiltInsNodes.hpp Nodes/RenderingNode.hpp + Nodes/AntiAliasing/FxaaNode.hpp Nodes/RenderNodes/ClearColorNode.hpp Nodes/RenderNodes/EmissivityRenderNode.hpp Nodes/RenderNodes/EnvironmentLightingNode.hpp From 37107c6b1a2eb657fc2113aadd2aa7938ecb1945 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 1 Feb 2023 23:48:04 +0100 Subject: [PATCH 213/239] [dataflow][rendering] remove redundant method --- src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp index 4cafd2b0dcd..f673e11f073 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.hpp @@ -30,9 +30,6 @@ class RA_DATAFLOW_API SsaoNode : public RenderingNode void resize( uint32_t width, uint32_t height ) override; bool initInternalShaders() override; - // SSao does not need rendertechnique - bool hasRenderTechnique() override { return false; } - static const std::string getTypename() { return "SSAO Node"; } protected: From 64fece25740883e39526ff2e2309bd00a8585ba6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 09:29:14 +0100 Subject: [PATCH 214/239] [dataflow][engine] update to factories manager interface --- src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp index 77cf8688c90..c2145d5f39a 100644 --- a/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderingBuiltInsNodes.cpp @@ -46,8 +46,7 @@ std::string registerRenderingNodesFactories() { } auto resourcesPath { *resourcesCheck }; - Core::NodeFactorySet::mapped_type renderingFactory { - new Core::NodeFactorySet::mapped_type::element_type( "RenderingNodes" ) }; + auto renderingFactory = Core::NodeFactoriesManager::createFactory( "RenderingNodes" ); /* --- Sources --- */ renderingFactory->registerNodeCreator( Nodes::SceneNode::getTypename() + "_", From 9444c72e532c65c385dc89738289fcc35f4a0fd4 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 09:52:11 +0100 Subject: [PATCH 215/239] [dataflow][engine] homogenize port naming --- src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp | 6 +++--- .../Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp | 4 ++-- .../Rendering/Nodes/RenderNodes/LocalLightingNode.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp index 41868bfc36a..4bbda2864c5 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp @@ -49,11 +49,11 @@ class RA_DATAFLOW_API ClearColorNode : public RenderingNode ColorType m_editableClearColor { ColorType::Grey( 0.12_ra ) }; - PortIn* m_portInColorTex { new PortIn( "textureToClear", this ) }; - PortIn* m_portInClearColor { new PortIn( "clearColor", this ) }; + PortIn* m_portInColorTex { new PortIn( "texture", this ) }; + PortIn* m_portInClearColor { new PortIn( "clear color", this ) }; PortIn* m_portInEnvmap { new PortIn( "environment", this ) }; PortIn* m_portInCamera { new PortIn( "camera", this ) }; - PortOut* m_portOutColorTex { new PortOut( "image", this ) }; + PortOut* m_portOutColorTex { new PortOut( "Beauty", this ) }; }; } // namespace Nodes diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp index f3fba6a6fd0..6a5027df477 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.hpp @@ -56,9 +56,9 @@ class RA_DATAFLOW_API EmissivityNode : public RenderingNode PortIn* m_inDepth { new PortIn( "depth", this ) }; PortIn* m_inColor { new PortIn( "color", this ) }; - PortIn* m_inAo { new PortIn( "ssao", this ) }; + PortIn* m_inAo { new PortIn( "AO", this ) }; - PortOut* m_outColor { new PortOut( "beauty", this ) }; + PortOut* m_outColor { new PortOut( "Beauty", this ) }; }; } // namespace Nodes diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp index c753e0bbb4e..22a42198728 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp @@ -131,7 +131,7 @@ bool LocalLightingNode::execute() { for ( const auto& l : lights ) { Ra::Engine::Data::RenderParameters inPassParams; // Ambient occlusion is not really meaningful for local lighting - inPassParams.addParameter( "amb_occ_sampler", /* aoTexture->texture() */ m_blankAO ); + inPassParams.addParameter( "amb_occ_sampler", aoTexture ); l->getRenderParameters( inPassParams ); for ( const auto& ro : renderObjects ) { ro->render( inPassParams, camera, m_idx ); From e5f71cc504aea96539fdc2f088e7bc044225769b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 21:20:01 +0100 Subject: [PATCH 216/239] [dataflow][rendering] fix crash when executing GraphRendering app on Linux --- .../Rendering/Nodes/RenderNodes/ClearColorNode.cpp | 2 +- .../Rendering/Nodes/Sources/EnvMapSourceNode.hpp | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp index 6b1c7018560..87ce945f856 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp @@ -77,7 +77,7 @@ bool ClearColorNode::execute() { void ClearColorNode::toJsonInternal( nlohmann::json& data ) const { auto c = ColorType ::linearRGBTosRGB( m_editableClearColor ); - std::array color { c.x(), c.y(), c.z() }; + std::array color { { c.x(), c.y(), c.z() } }; data["clearColor"] = color; } diff --git a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp index 2f378b0b513..3e5f6a57100 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp +++ b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp @@ -40,9 +40,9 @@ template <> inline void SingleDataSourceNode::toJsonInternal( nlohmann::json& data ) const { auto envTex = getData(); - if ( envTex ) { - data["files"] = ( *envTex )->getImageName().c_str(); - data["strength"] = ( *envTex )->getStrength() * 100.; + if ( envTex->get() ) { + data["files"] = envTex->get()->getImageName().c_str(); + data["strength"] = envTex->get()->getStrength() * 100.; } } @@ -69,11 +69,13 @@ SingleDataSourceNode::fromJsonInternal( const nlohmann setData( envmp ); } else { - setData( nullptr ); + Rendering::EnvmapPtrType nullEnvMap { nullptr }; + setData( nullEnvMap ); } } else { - setData( nullptr ); + Rendering::EnvmapPtrType nullEnvMap { nullptr }; + setData( nullEnvMap ); } return true; } From d243e5c1c630e0262572f1daae0cf0fcdf34ee79 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Thu, 2 Feb 2023 21:20:10 +0100 Subject: [PATCH 217/239] [dataflow][qtgui] fix crash when executing GraphRendering app on Linux --- src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp index 261b0a3d60e..a22d004cebe 100644 --- a/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp +++ b/src/Dataflow/QtGui/GraphEditor/GraphEditorWindow.cpp @@ -238,6 +238,7 @@ void GraphEditorWindow::loadFile( const QString& fileName ) { loaded = ( m_graph != nullptr ); } else { + m_graphEdit->editGraph( nullptr ); m_graph->destroy(); loaded = m_graph->loadFromJson( fileName.toStdString() ); } From 858e470c303b5b633132ed17730957e287146aee Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 3 Feb 2023 08:14:04 +0100 Subject: [PATCH 218/239] [examples][dataflow] fix codacy warning --- .../DataflowExamples/GraphRendering/main.cpp | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 595a6b5303b..3a76e1e3310 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -195,28 +195,33 @@ class MyRendererController : public RenderGraphController } // Nodes by level after the compilation - auto c = g.compile(); - auto cn = g.getNodesByLevel(); - std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { - std::cout << "\tLevel " << i << " :\n"; - for ( const auto n : ( *cn )[i] ) { - std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + auto c = g.compile(); + if ( c ) { + auto cn = g.getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto n : ( *cn )[i] ) { + std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + } } - } - // describe the graph interface : inputs and outputs port of the whole graph (not of the - // nodes) - std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; - auto inputs = g.getAllDataSetters(); - std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : inputs ) { - std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + // describe the graph interface : inputs and outputs port of the whole graph (not of the + // nodes) + std::cout << "Inputs and output nodes of the graph " << g.getInstanceName() << " :\n"; + auto inputs = g.getAllDataSetters(); + std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + } + auto outputs = g.getAllDataGetters(); + std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + } } - auto outputs = g.getAllDataGetters(); - std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; - for ( auto& [ptrPort, portName, portType] : outputs ) { - std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + else { + std::cerr << "Unable to compile the graph " << g.getInstanceName() << "\n"; } } From ca90f7b84d7af57f66d405030a6613c1974c0493 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 3 Feb 2023 13:17:46 +0100 Subject: [PATCH 219/239] [unittests][dataflow] add rendering graph unittests --- tests/unittest/CMakeLists.txt | 1 + tests/unittest/Dataflow/renderinggraph.cpp | 83 +++ .../data/Dataflow/fullRenderingGraph.json | 484 ++++++++++++++++++ 3 files changed, 568 insertions(+) create mode 100644 tests/unittest/Dataflow/renderinggraph.cpp create mode 100644 tests/unittest/data/Dataflow/fullRenderingGraph.json diff --git a/tests/unittest/CMakeLists.txt b/tests/unittest/CMakeLists.txt index a538a8a6b8e..062c06f2969 100644 --- a/tests/unittest/CMakeLists.txt +++ b/tests/unittest/CMakeLists.txt @@ -34,6 +34,7 @@ set(test_src Dataflow/customnodes.cpp Dataflow/graph.cpp Dataflow/nodes.cpp + Dataflow/renderinggraph.cpp Dataflow/serialization.cpp Dataflow/sourcesandsinks.cpp Engine/environmentmap.cpp diff --git a/tests/unittest/Dataflow/renderinggraph.cpp b/tests/unittest/Dataflow/renderinggraph.cpp new file mode 100644 index 00000000000..af2f8bac1af --- /dev/null +++ b/tests/unittest/Dataflow/renderinggraph.cpp @@ -0,0 +1,83 @@ +#include + +#include + +#include + +using namespace Ra::Dataflow::Core; +using namespace Ra::Dataflow::Rendering; + +TEST_CASE( "Dataflow/Rendering/RenderingGraph", "[Dataflow][Rendering][RenderingGraph]" ) { + + SECTION( "Loads and inspect a rendering graph graph" ) { + auto g = DataflowGraph::loadGraphFromJsonFile( "data/Dataflow/fullRenderingGraph.json" ); + + // Factories used by the graph + auto factories = g->getNodeFactories(); + REQUIRE( factories != nullptr ); + std::cout << "Used factories by the graph \"" << g->getInstanceName() << "\" with type \"" + << g->getTypeName() << "\" :\n"; + for ( const auto& f : *( factories.get() ) ) { + std::cout << "\t" << f.first << "\n"; + } + + // Nodes of the graph + auto nodes = g->getNodes(); + REQUIRE( nodes != nullptr ); + std::cout << "Nodes of the graph " << g->getInstanceName() << " (" << nodes->size() + << " nodes) " << g->getNodesCount() << ":\n"; + + REQUIRE( nodes->size() == g->getNodesCount() ); + + for ( const auto& n : *( nodes ) ) { + std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() + << "\"\n"; + // Inspect input, output and interfaces of the node + std::cout << "\t\tInput ports :\n"; + for ( const auto& p : n->getInputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tOutput ports :\n"; + for ( const auto& p : n->getOutputs() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + std::cout << "\t\tInterface ports :\n"; + for ( const auto& p : n->getInterfaces() ) { + std::cout << "\t\t\t\"" << p->getName() << "\" with type " << p->getTypeName() + << "\n"; + } + } + + // describe the graph interface : inputs and outputs port of the whole graph (not of the + // nodes) + std::cout << "Inputs and output nodes of the graph " << g->getInstanceName() << " :\n"; + auto inputs = g->getAllDataSetters(); + std::cout << "\tInput ports (" << inputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : inputs ) { + std::cout << "\t\t\"" << portName << "\" accepting type \"" << portType << "\"\n"; + } + auto outputs = g->getAllDataGetters(); + std::cout << "\tOutput ports (" << outputs.size() << ") are :\n"; + for ( auto& [ptrPort, portName, portType] : outputs ) { + std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; + } + +// Can't compile the graph without OpenGL context (it's a rendering graph, compilation needs openGL +// context active) +#if 0 + // Nodes by level after the compilation + auto c = g->compile(); + REQUIRE( c == true ); + auto cn = g->getNodesByLevel(); + std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; + for ( size_t i = 0; i < cn->size(); ++i ) { + std::cout << "\tLevel " << i << " :\n"; + for ( const auto n : ( *cn )[i] ) { + std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; + } + } +#endif + } +} diff --git a/tests/unittest/data/Dataflow/fullRenderingGraph.json b/tests/unittest/data/Dataflow/fullRenderingGraph.json new file mode 100644 index 00000000000..cb10b9e9c4c --- /dev/null +++ b/tests/unittest/data/Dataflow/fullRenderingGraph.json @@ -0,0 +1,484 @@ +{ + "id": "{3e0a0063-f7f9-4713-a32b-51d0b65de0c3}", + "model": { + "graph": { + "connections": [ + { + "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "in_index": 0, + "out_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "out_index": 0 + }, + { + "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "in_index": 1, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 2 + }, + { + "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "in_index": 2, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 1 + }, + { + "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "in_index": 3, + "out_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "out_index": 1 + }, + { + "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "in_index": 4, + "out_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "out_index": 2 + }, + { + "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "in_index": 5, + "out_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "out_index": 0 + }, + { + "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "in_index": 0, + "out_id": "{378b6823-55c8-4052-ab91-98336c5a59b5}", + "out_index": 0 + }, + { + "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "in_index": 1, + "out_id": "{c27ba749-f756-47a4-8205-c3c62201694b}", + "out_index": 0 + }, + { + "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "in_index": 2, + "out_id": "{0a9935e7-e7df-4f91-bd19-56ddc25ba0ec}", + "out_index": 0 + }, + { + "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "in_index": 3, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 2 + }, + { + "in_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "in_index": 0, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 0 + }, + { + "in_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "in_index": 1, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 2 + }, + { + "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "in_index": 0, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 0 + }, + { + "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "in_index": 1, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 2 + }, + { + "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "in_index": 2, + "out_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "out_index": 0 + }, + { + "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "in_index": 3, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 0 + }, + { + "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "in_index": 4, + "out_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "out_index": 0 + }, + { + "in_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "in_index": 0, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 1 + }, + { + "in_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "in_index": 1, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 2 + }, + { + "in_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "in_index": 2, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 2 + }, + { + "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "in_index": 0, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 0 + }, + { + "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "in_index": 1, + "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "out_index": 2 + }, + { + "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "in_index": 2, + "out_id": "{0a9935e7-e7df-4f91-bd19-56ddc25ba0ec}", + "out_index": 0 + }, + { + "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "in_index": 3, + "out_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "out_index": 0 + }, + { + "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "in_index": 4, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 0 + }, + { + "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "in_index": 0, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 0 + }, + { + "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "in_index": 1, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 1 + }, + { + "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "in_index": 2, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 2 + }, + { + "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "in_index": 3, + "out_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "out_index": 0 + }, + { + "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "in_index": 4, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 0 + }, + { + "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "in_index": 5, + "out_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "out_index": 0 + }, + { + "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "in_index": 0, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 0 + }, + { + "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "in_index": 1, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 1 + }, + { + "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "in_index": 2, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 2 + }, + { + "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "in_index": 3, + "out_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "out_index": 0 + }, + { + "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "in_index": 4, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 0 + }, + { + "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "in_index": 0, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 0 + }, + { + "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "in_index": 1, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 1 + }, + { + "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "in_index": 2, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 2 + }, + { + "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "in_index": 3, + "out_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "out_index": 0 + }, + { + "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "in_index": 4, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 0 + }, + { + "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "in_index": 0, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 0 + }, + { + "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "in_index": 1, + "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "out_index": 2 + }, + { + "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "in_index": 2, + "out_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "out_index": 0 + }, + { + "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "in_index": 3, + "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "out_index": 0 + }, + { + "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "in_index": 4, + "out_id": "{c6109613-71d9-4bad-89e7-298baf21290a}", + "out_index": 0 + } + ], + "factories": [ + "RenderingNodes" + ], + "nodes": [ + { + "id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "model": { + "instance": "scene_6", + "name": "Scene data provider" + }, + "position": { + "x": 247.2943878173828, + "y": 323.0783996582031 + } + }, + { + "id": "{c27ba749-f756-47a4-8205-c3c62201694b}", + "model": { + "color": [ + 1.7104668586398475e-05, + 1.7104668586398475e-05, + 1.7104668586398475e-05, + 1.0 + ], + "instance": "Source_1", + "name": "Source" + }, + "position": { + "x": 232.92669677734375, + "y": 11.072956085205078 + } + }, + { + "id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "model": { + "instance": "scene_10", + "name": "Scene data provider" + }, + "position": { + "x": 1360.0, + "y": 829.5999755859375 + } + }, + { + "id": "{c6109613-71d9-4bad-89e7-298baf21290a}", + "model": { + "boolean": false, + "instance": "Source_2", + "name": "Source" + }, + "position": { + "x": 2300.37158203125, + "y": 933.0 + } + }, + { + "id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "model": { + "instance": "sink_7", + "name": "Display Sink" + }, + "position": { + "x": 2938.12109375, + "y": 690.5317993164063 + } + }, + { + "id": "{378b6823-55c8-4052-ab91-98336c5a59b5}", + "model": { + "instance": "colorTex_8", + "name": "Color Texture" + }, + "position": { + "x": 350.04608154296875, + "y": -71.95873260498047 + } + }, + { + "id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "model": { + "clearColor": [ + 0.45098042488098145, + 0.45098042488098145, + 0.45098042488098145 + ], + "instance": "clearFrame_9", + "name": "Clear Color Pass" + }, + "position": { + "x": 541.1845703125, + "y": 60.45983123779297 + } + }, + { + "id": "{0a9935e7-e7df-4f91-bd19-56ddc25ba0ec}", + "model": { + "files": "data/studio_garden_2k.exr", + "instance": "envmap_10", + "name": "Source", + "strength": 100.0 + }, + "position": { + "x": 51.3619384765625, + "y": 477.6835021972656 + } + }, + { + "id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "model": { + "instance": "geometryAovs_6", + "name": "Geometry AOVs" + }, + "position": { + "x": 639.5306396484375, + "y": 562.627197265625 + } + }, + { + "id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "model": { + "instance": "emissivity_7", + "name": "Emissivity Render Node" + }, + "position": { + "x": 1031.2784423828125, + "y": 155.7407989501953 + } + }, + { + "id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "model": { + "instance": "ssao_12", + "name": "SSAO Node", + "radius": 10.0 + }, + "position": { + "x": 802.4400024414063, + "y": 758.280029296875 + } + }, + { + "id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "model": { + "instance": "envlight_8", + "name": "Env Lighting Node" + }, + "position": { + "x": 1358.296630859375, + "y": 230.74176025390625 + } + }, + { + "id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "model": { + "instance": "localLight_9", + "name": "Local Lighting Node" + }, + "position": { + "x": 1661.003173828125, + "y": 308.92669677734375 + } + }, + { + "id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "model": { + "instance": "transparency_11", + "name": "Transparency Local Lighting Node" + }, + "position": { + "x": 1962.969970703125, + "y": 383.0400085449219 + } + }, + { + "id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "model": { + "instance": "volumeLighting_13", + "name": "Volume Local Lighting Node" + }, + "position": { + "x": 2292.723388671875, + "y": 455.3833312988281 + } + }, + { + "id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "model": { + "activated": false, + "instance": "wireframe_14", + "name": "Wireframe Node" + }, + "position": { + "x": 2602.789794921875, + "y": 590.2335815429688 + } + } + ] + }, + "instance": "fullGraph", + "name": "Rendering Graph" + } +} From 35cb1ec009a29be846c3295f26c39c4142104f56 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 3 Feb 2023 18:03:53 +0100 Subject: [PATCH 220/239] [unittests][dataflowrendering] fix segfault on Linux --- tests/unittest/Dataflow/renderinggraph.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/unittest/Dataflow/renderinggraph.cpp b/tests/unittest/Dataflow/renderinggraph.cpp index af2f8bac1af..fe7eb3b8f74 100644 --- a/tests/unittest/Dataflow/renderinggraph.cpp +++ b/tests/unittest/Dataflow/renderinggraph.cpp @@ -8,7 +8,17 @@ using namespace Ra::Dataflow::Core; using namespace Ra::Dataflow::Rendering; TEST_CASE( "Dataflow/Rendering/RenderingGraph", "[Dataflow][Rendering][RenderingGraph]" ) { - +#if defined( OS_LINUX ) + { + // This is required on Linux to force loading the libDataflowRendering.so so that the + // node factory for Rendering is initialized. + // If not present, as no symbols are explicitly used from this lib, the linker + // optimize out the lib. + // All the test below use only the general interface of a DataflowGraph, the dependency to + // symbols exported by the rendering lib is only managed by the node factory. + RenderingGraph gr( "Forcing libDataflowRendering.so to be loaded" ); + } +#endif SECTION( "Loads and inspect a rendering graph graph" ) { auto g = DataflowGraph::loadGraphFromJsonFile( "data/Dataflow/fullRenderingGraph.json" ); From 67ba8f2c41f094b7d08dadf98953e5b010b34811 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 18:56:17 +0100 Subject: [PATCH 221/239] [dataflow][rendering] update to dataflow core evolution --- examples/DataflowExamples/GraphRendering/main.cpp | 2 +- src/Dataflow/Rendering/Renderer/RenderGraphController.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 3a76e1e3310..9c308a23f8e 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -283,7 +283,7 @@ class MyRendererController : public RenderGraphController inspectGraph( *m_renderGraph ); // force recompilation and introspection of the graph by the renderer - m_renderGraph->m_ready = false; + m_renderGraph->isCompiled() = false; notify(); } }; diff --git a/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp b/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp index 17246993998..6bfc161d967 100644 --- a/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp +++ b/src/Dataflow/Rendering/Renderer/RenderGraphController.cpp @@ -38,7 +38,7 @@ void RenderGraphController::resize( int w, int h ) { } void RenderGraphController::compile( bool notifyObservers ) const { - if ( !m_renderGraph->m_ready ) { + if ( !m_renderGraph->isCompiled() ) { // compile the model m_renderGraph->compile(); // notify the view the model changes @@ -80,7 +80,7 @@ RenderGraphController::render( std::vector* ros, bool status = false; - if ( m_renderGraph && m_renderGraph->m_ready ) { + if ( m_renderGraph && m_renderGraph->isCompiled() ) { // set input data for ( const auto& [ptr, name, type] : m_renderGraphInputs ) { if ( type == simplifiedDemangledType( *ros ) ) { From a968f6d4bcd6bc7f8428f9c8294f885cb59c4405 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 21:25:32 +0100 Subject: [PATCH 222/239] [dataflow][rendering] update rendering example --- examples/DataflowExamples/GraphRendering/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 9c308a23f8e..5deaba1a9b3 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -283,7 +283,7 @@ class MyRendererController : public RenderGraphController inspectGraph( *m_renderGraph ); // force recompilation and introspection of the graph by the renderer - m_renderGraph->isCompiled() = false; + m_renderGraph->needsRecompile(); notify(); } }; From 2eb84ca377850f58b937f68e7da75c141f8c16c6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 10 Feb 2023 21:25:38 +0100 Subject: [PATCH 223/239] [dataflow][rendering] update rendering example --- .../data/Dataflow/fullRenderingGraph.json | 425 +++++++++--------- 1 file changed, 203 insertions(+), 222 deletions(-) diff --git a/tests/unittest/data/Dataflow/fullRenderingGraph.json b/tests/unittest/data/Dataflow/fullRenderingGraph.json index cb10b9e9c4c..df5d47e8c63 100644 --- a/tests/unittest/data/Dataflow/fullRenderingGraph.json +++ b/tests/unittest/data/Dataflow/fullRenderingGraph.json @@ -1,283 +1,283 @@ { - "id": "{3e0a0063-f7f9-4713-a32b-51d0b65de0c3}", + "instance": "fullRenderingGraph", "model": { "graph": { "connections": [ { - "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", - "in_index": 0, - "out_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", - "out_index": 0 + "in_node": "Display Node", + "in_port": "Beauty", + "out_node": "wireframe_14", + "out_port": "Beauty" }, { - "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", - "in_index": 1, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 2 + "in_node": "Display Node", + "in_port": "AOV_0", + "out_node": "geometryAovs_6", + "out_port": "world normal" }, { - "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", - "in_index": 2, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 1 + "in_node": "Display Node", + "in_port": "AOV_1", + "out_node": "geometryAovs_6", + "out_port": "world pos" }, { - "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", - "in_index": 3, - "out_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "out_index": 1 + "in_node": "Display Node", + "in_port": "AOV_2", + "out_node": "transparency_11", + "out_port": "revealage" }, { - "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", - "in_index": 4, - "out_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "out_index": 2 + "in_node": "Display Node", + "in_port": "AOV_3", + "out_node": "transparency_11", + "out_port": "accum" }, { - "in_id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", - "in_index": 5, - "out_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", - "out_index": 0 + "in_node": "Display Node", + "in_port": "AOV_4", + "out_node": "ssao_12", + "out_port": "ssao" }, { - "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", - "in_index": 0, - "out_id": "{378b6823-55c8-4052-ab91-98336c5a59b5}", - "out_index": 0 + "in_node": "clearFrame_9", + "in_port": "texture", + "out_node": "colorTex_8", + "out_port": "texture" }, { - "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", - "in_index": 1, - "out_id": "{c27ba749-f756-47a4-8205-c3c62201694b}", - "out_index": 0 + "in_node": "clearFrame_9", + "in_port": "clear color", + "out_node": "Source_1", + "out_port": "to" }, { - "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", - "in_index": 2, - "out_id": "{0a9935e7-e7df-4f91-bd19-56ddc25ba0ec}", - "out_index": 0 + "in_node": "clearFrame_9", + "in_port": "environment", + "out_node": "envmap_10", + "out_port": "to" }, { - "in_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", - "in_index": 3, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 2 + "in_node": "clearFrame_9", + "in_port": "camera", + "out_node": "scene_6", + "out_port": "camera" }, { - "in_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "in_index": 0, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 0 + "in_node": "geometryAovs_6", + "in_port": "objects", + "out_node": "scene_6", + "out_port": "objects" }, { - "in_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "in_index": 1, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 2 + "in_node": "geometryAovs_6", + "in_port": "camera", + "out_node": "scene_6", + "out_port": "camera" }, { - "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", - "in_index": 0, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 0 + "in_node": "emissivity_7", + "in_port": "objects", + "out_node": "scene_6", + "out_port": "objects" }, { - "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", - "in_index": 1, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 2 + "in_node": "emissivity_7", + "in_port": "camera", + "out_node": "scene_6", + "out_port": "camera" }, { - "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", - "in_index": 2, - "out_id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", - "out_index": 0 + "in_node": "emissivity_7", + "in_port": "color", + "out_node": "clearFrame_9", + "out_port": "Beauty" }, { - "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", - "in_index": 3, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 0 + "in_node": "emissivity_7", + "in_port": "depth", + "out_node": "geometryAovs_6", + "out_port": "depth" }, { - "in_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", - "in_index": 4, - "out_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", - "out_index": 0 + "in_node": "emissivity_7", + "in_port": "AO", + "out_node": "ssao_12", + "out_port": "ssao" }, { - "in_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", - "in_index": 0, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 1 + "in_node": "ssao_12", + "in_port": "worldPosition", + "out_node": "geometryAovs_6", + "out_port": "world pos" }, { - "in_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", - "in_index": 1, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 2 + "in_node": "ssao_12", + "in_port": "worldNormal", + "out_node": "geometryAovs_6", + "out_port": "world normal" }, { - "in_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", - "in_index": 2, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 2 + "in_node": "ssao_12", + "in_port": "camera", + "out_node": "scene_6", + "out_port": "camera" }, { - "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", - "in_index": 0, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 0 + "in_node": "envlight_8", + "in_port": "objects", + "out_node": "scene_6", + "out_port": "objects" }, { - "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", - "in_index": 1, - "out_id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", - "out_index": 2 + "in_node": "envlight_8", + "in_port": "camera", + "out_node": "scene_6", + "out_port": "camera" }, { - "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", - "in_index": 2, - "out_id": "{0a9935e7-e7df-4f91-bd19-56ddc25ba0ec}", - "out_index": 0 + "in_node": "envlight_8", + "in_port": "envmap", + "out_node": "envmap_10", + "out_port": "to" }, { - "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", - "in_index": 3, - "out_id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", - "out_index": 0 + "in_node": "envlight_8", + "in_port": "color", + "out_node": "emissivity_7", + "out_port": "Beauty" }, { - "in_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", - "in_index": 4, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 0 + "in_node": "envlight_8", + "in_port": "depth", + "out_node": "geometryAovs_6", + "out_port": "depth" }, { - "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "in_index": 0, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 0 + "in_node": "localLight_9", + "in_port": "objects", + "out_node": "scene_10", + "out_port": "objects" }, { - "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "in_index": 1, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 1 + "in_node": "localLight_9", + "in_port": "lights", + "out_node": "scene_10", + "out_port": "lights" }, { - "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "in_index": 2, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 2 + "in_node": "localLight_9", + "in_port": "camera", + "out_node": "scene_10", + "out_port": "camera" }, { - "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "in_index": 3, - "out_id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", - "out_index": 0 + "in_node": "localLight_9", + "in_port": "color", + "out_node": "envlight_8", + "out_port": "Beauty" }, { - "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "in_index": 4, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 0 + "in_node": "localLight_9", + "in_port": "depth", + "out_node": "geometryAovs_6", + "out_port": "depth" }, { - "in_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "in_index": 5, - "out_id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", - "out_index": 0 + "in_node": "localLight_9", + "in_port": "AO", + "out_node": "ssao_12", + "out_port": "ssao" }, { - "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "in_index": 0, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 0 + "in_node": "transparency_11", + "in_port": "objects", + "out_node": "scene_10", + "out_port": "objects" }, { - "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "in_index": 1, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 1 + "in_node": "transparency_11", + "in_port": "lights", + "out_node": "scene_10", + "out_port": "lights" }, { - "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "in_index": 2, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 2 + "in_node": "transparency_11", + "in_port": "camera", + "out_node": "scene_10", + "out_port": "camera" }, { - "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "in_index": 3, - "out_id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", - "out_index": 0 + "in_node": "transparency_11", + "in_port": "color", + "out_node": "localLight_9", + "out_port": "Beauty" }, { - "in_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "in_index": 4, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 0 + "in_node": "transparency_11", + "in_port": "depth", + "out_node": "geometryAovs_6", + "out_port": "depth" }, { - "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", - "in_index": 0, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 0 + "in_node": "volumeLighting_13", + "in_port": "objects", + "out_node": "scene_10", + "out_port": "objects" }, { - "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", - "in_index": 1, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 1 + "in_node": "volumeLighting_13", + "in_port": "lights", + "out_node": "scene_10", + "out_port": "lights" }, { - "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", - "in_index": 2, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 2 + "in_node": "volumeLighting_13", + "in_port": "camera", + "out_node": "scene_10", + "out_port": "camera" }, { - "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", - "in_index": 3, - "out_id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", - "out_index": 0 + "in_node": "volumeLighting_13", + "in_port": "color", + "out_node": "transparency_11", + "out_port": "Beauty" }, { - "in_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", - "in_index": 4, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 0 + "in_node": "volumeLighting_13", + "in_port": "depth", + "out_node": "geometryAovs_6", + "out_port": "depth" }, { - "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", - "in_index": 0, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 0 + "in_node": "wireframe_14", + "in_port": "objects", + "out_node": "scene_10", + "out_port": "objects" }, { - "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", - "in_index": 1, - "out_id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", - "out_index": 2 + "in_node": "wireframe_14", + "in_port": "camera", + "out_node": "scene_10", + "out_port": "camera" }, { - "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", - "in_index": 2, - "out_id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", - "out_index": 0 + "in_node": "wireframe_14", + "in_port": "color", + "out_node": "volumeLighting_13", + "out_port": "Beauty" }, { - "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", - "in_index": 3, - "out_id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", - "out_index": 0 + "in_node": "wireframe_14", + "in_port": "depth", + "out_node": "geometryAovs_6", + "out_port": "depth" }, { - "in_id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", - "in_index": 4, - "out_id": "{c6109613-71d9-4bad-89e7-298baf21290a}", - "out_index": 0 + "in_node": "wireframe_14", + "in_port": "activate", + "out_node": "Source_2", + "out_port": "to" } ], "factories": [ @@ -285,9 +285,8 @@ ], "nodes": [ { - "id": "{c18f9b5c-a685-4964-b5a6-1d41b4036bcc}", + "instance": "scene_6", "model": { - "instance": "scene_6", "name": "Scene data provider" }, "position": { @@ -296,7 +295,7 @@ } }, { - "id": "{c27ba749-f756-47a4-8205-c3c62201694b}", + "instance": "Source_1", "model": { "color": [ 1.7104668586398475e-05, @@ -304,7 +303,6 @@ 1.7104668586398475e-05, 1.0 ], - "instance": "Source_1", "name": "Source" }, "position": { @@ -313,9 +311,8 @@ } }, { - "id": "{fa8ffab8-45f4-4a2d-aa55-176d62a2c18a}", + "instance": "scene_10", "model": { - "instance": "scene_10", "name": "Scene data provider" }, "position": { @@ -324,10 +321,9 @@ } }, { - "id": "{c6109613-71d9-4bad-89e7-298baf21290a}", + "instance": "Source_2", "model": { "boolean": false, - "instance": "Source_2", "name": "Source" }, "position": { @@ -336,9 +332,8 @@ } }, { - "id": "{58e9c197-5eeb-42ba-adac-039dcbffe421}", + "instance": "Display Node", "model": { - "instance": "sink_7", "name": "Display Sink" }, "position": { @@ -347,9 +342,8 @@ } }, { - "id": "{378b6823-55c8-4052-ab91-98336c5a59b5}", + "instance": "colorTex_8", "model": { - "instance": "colorTex_8", "name": "Color Texture" }, "position": { @@ -358,14 +352,13 @@ } }, { - "id": "{c64a1c27-477d-46bb-9174-3e2072cf7557}", + "instance": "clearFrame_9", "model": { "clearColor": [ 0.45098042488098145, 0.45098042488098145, 0.45098042488098145 ], - "instance": "clearFrame_9", "name": "Clear Color Pass" }, "position": { @@ -374,12 +367,9 @@ } }, { - "id": "{0a9935e7-e7df-4f91-bd19-56ddc25ba0ec}", + "instance": "envmap_10", "model": { - "files": "data/studio_garden_2k.exr", - "instance": "envmap_10", - "name": "Source", - "strength": 100.0 + "name": "Source" }, "position": { "x": 51.3619384765625, @@ -387,9 +377,8 @@ } }, { - "id": "{48a14abf-2bb4-46b8-8a40-5bac6db698b6}", + "instance": "geometryAovs_6", "model": { - "instance": "geometryAovs_6", "name": "Geometry AOVs" }, "position": { @@ -398,9 +387,8 @@ } }, { - "id": "{37b719c8-5ab1-4b6b-b7ad-293f292be419}", + "instance": "emissivity_7", "model": { - "instance": "emissivity_7", "name": "Emissivity Render Node" }, "position": { @@ -409,11 +397,10 @@ } }, { - "id": "{24cb2152-909b-4bf9-a381-d49115fb2bb4}", + "instance": "ssao_12", "model": { - "instance": "ssao_12", "name": "SSAO Node", - "radius": 10.0 + "radius": 0.0 }, "position": { "x": 802.4400024414063, @@ -421,9 +408,8 @@ } }, { - "id": "{b6e84af0-b6ad-4a35-b8ac-c914fe5b4b5d}", + "instance": "envlight_8", "model": { - "instance": "envlight_8", "name": "Env Lighting Node" }, "position": { @@ -432,9 +418,8 @@ } }, { - "id": "{9e1c3de2-e14e-4e0c-b4a4-d5baf1766a26}", + "instance": "localLight_9", "model": { - "instance": "localLight_9", "name": "Local Lighting Node" }, "position": { @@ -443,9 +428,8 @@ } }, { - "id": "{b69b4a4e-17a7-487a-a894-68314a6e0207}", + "instance": "transparency_11", "model": { - "instance": "transparency_11", "name": "Transparency Local Lighting Node" }, "position": { @@ -454,9 +438,8 @@ } }, { - "id": "{a5d6abaf-8f66-496e-8fe4-3ae89e5ac43d}", + "instance": "volumeLighting_13", "model": { - "instance": "volumeLighting_13", "name": "Volume Local Lighting Node" }, "position": { @@ -465,10 +448,9 @@ } }, { - "id": "{29aabf46-1ef2-4811-9959-fb733b1748f5}", + "instance": "wireframe_14", "model": { "activated": false, - "instance": "wireframe_14", "name": "Wireframe Node" }, "position": { @@ -478,7 +460,6 @@ } ] }, - "instance": "fullGraph", "name": "Rendering Graph" } } From 96f167eb3f7fa31b04352bbf0c9228827f01efd6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 17 Mar 2023 18:55:02 +0100 Subject: [PATCH 224/239] [dataflow-rendering] adapt to renderparameter PR --- .../Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp index 814360ed09a..82e4a2f917c 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/VolumeLocalLightingNode.cpp @@ -5,6 +5,8 @@ #include #include +#include + namespace Ra { namespace Dataflow { namespace Rendering { From fab1e450154347b074bb7172ce2789fd85b2e353 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 17 Mar 2023 18:55:42 +0100 Subject: [PATCH 225/239] [examples] add support for libGLTF for graph-base rendering exemple --- .../GraphRendering/CMakeLists.txt | 15 +++++++++++++++ .../DataflowExamples/GraphRendering/main.cpp | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/examples/DataflowExamples/GraphRendering/CMakeLists.txt b/examples/DataflowExamples/GraphRendering/CMakeLists.txt index 7bb6e7d1adf..af156ab5f3c 100644 --- a/examples/DataflowExamples/GraphRendering/CMakeLists.txt +++ b/examples/DataflowExamples/GraphRendering/CMakeLists.txt @@ -55,4 +55,19 @@ target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_17) target_link_libraries(${PROJECT_NAME} PUBLIC ${Qt_LIBRARIES} Radium::Dataflow) +# ------------------------------------------------------------------------------ +# RadiumGlTF is available here https://gitlab.irit.fr/storm/repos/radium/libgltf.git (using the +# right branch). Compile RadiumGlTF, then configure using cmake [your configure args] +# -DRadiumGlTF_DIR="path/to/RadiumGlTF/build-dir/src/RadiumGlTF" -DUSE_RADIUMGLTF=ON to use it for +# this example. +option(USE_RADIUMGLTF "Enable loading/saving files with RadiumGltf extension" OFF) +if(USE_RADIUMGLTF) + message(STATUS "${PROJECT_NAME} uses RadiumGltf extension") + # TODO : find why this find_package is needed (at least on MacOs whe ). + find_package(OpenMP QUIET) + find_package(RadiumGlTF REQUIRED) + target_compile_definitions(${PROJECT_NAME} PUBLIC USE_RADIUMGLTF) + target_link_libraries(${PROJECT_NAME} PUBLIC RadiumGlTF::RadiumGlTF) +endif() + configure_radium_app(NAME ${PROJECT_NAME}) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 5deaba1a9b3..494b5f61fde 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -14,6 +14,13 @@ #include +#ifdef USE_RADIUMGLTF +# include +# include +# include +# include +#endif + // Qt Widgets #include @@ -142,6 +149,12 @@ class DemoWindowFactory : public BaseApplication::WindowFactory inline Ra::Gui::MainWindowInterface* createMainWindow() const override { auto window = new SimpleWindow(); +#ifdef USE_RADIUMGLTF + // register the gltf loader + std::shared_ptr loader = + std::make_shared(); + Ra::Engine::RadiumEngine::getInstance()->registerFileLoader( loader ); +#endif addFileMenu( window ); addRendererMenu( window, m_renderers ); return window; @@ -349,6 +362,12 @@ int main( int argc, char* argv[] ) { app.initialize( DemoWindowFactory( renderers ) ); app.setContinuousUpdate( false ); app.addRadiumMenu(); +#ifdef USE_RADIUMGLTF + app.m_mainWindow->getViewer()->makeCurrent(); + // initialize the use of GLTF library + GLTF::initializeGltf(); + app.m_mainWindow->getViewer()->doneCurrent(); +#endif //! [Initializing the application] return app.exec(); From 9d3860254509938ed5c3495f5405c57b12544ead Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 20 Mar 2023 18:05:47 +0100 Subject: [PATCH 226/239] [dataflow-rendering][tests][examples] fix missing const in dataflow graph node access --- .../DataflowExamples/GraphRendering/main.cpp | 12 ++++++------ src/Dataflow/Rendering/RenderingGraph.cpp | 2 +- tests/unittest/Dataflow/renderinggraph.cpp | 16 +++++++--------- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 494b5f61fde..22c8996138a 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -183,10 +183,10 @@ class MyRendererController : public RenderGraphController } // Nodes of the graph - auto nodes = g.getNodes(); - std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes->size() + const auto& nodes = g.getNodes(); + std::cout << "Nodes of the graph " << g.getInstanceName() << " (" << nodes.size() << ") :\n"; - for ( const auto& n : *( nodes ) ) { + for ( const auto& n : nodes ) { std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() << "\"\n"; // Inspect input, output and interfaces of the node @@ -210,11 +210,11 @@ class MyRendererController : public RenderGraphController // Nodes by level after the compilation auto c = g.compile(); if ( c ) { - auto cn = g.getNodesByLevel(); + const auto& cn = g.getNodesByLevel(); std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { + for ( size_t i = 0; i < cn.size(); ++i ) { std::cout << "\tLevel " << i << " :\n"; - for ( const auto n : ( *cn )[i] ) { + for ( const auto n : cn[i] ) { std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; } } diff --git a/src/Dataflow/Rendering/RenderingGraph.cpp b/src/Dataflow/Rendering/RenderingGraph.cpp index 209241e7bf8..63194bec766 100644 --- a/src/Dataflow/Rendering/RenderingGraph.cpp +++ b/src/Dataflow/Rendering/RenderingGraph.cpp @@ -38,7 +38,7 @@ bool RenderingGraph::compile() { if ( compiled ) { const auto& compiledNodes = getNodesByLevel(); int idx = 1; // The renderTechnique id = 0 is reserved for ui/debug objects - for ( const auto& lvl : *compiledNodes ) { + for ( const auto& lvl : compiledNodes ) { for ( auto n : lvl ) { auto renderNode = dynamic_cast( n ); if ( renderNode != nullptr ) { diff --git a/tests/unittest/Dataflow/renderinggraph.cpp b/tests/unittest/Dataflow/renderinggraph.cpp index fe7eb3b8f74..60f18375036 100644 --- a/tests/unittest/Dataflow/renderinggraph.cpp +++ b/tests/unittest/Dataflow/renderinggraph.cpp @@ -32,14 +32,12 @@ TEST_CASE( "Dataflow/Rendering/RenderingGraph", "[Dataflow][Rendering][Rendering } // Nodes of the graph - auto nodes = g->getNodes(); - REQUIRE( nodes != nullptr ); - std::cout << "Nodes of the graph " << g->getInstanceName() << " (" << nodes->size() + const auto& nodes = g->getNodes(); + REQUIRE( nodes.size() == g->getNodesCount() ); + std::cout << "Nodes of the graph " << g->getInstanceName() << " (" << nodes.size() << " nodes) " << g->getNodesCount() << ":\n"; - REQUIRE( nodes->size() == g->getNodesCount() ); - - for ( const auto& n : *( nodes ) ) { + for ( const auto& n : nodes ) { std::cout << "\t\"" << n->getInstanceName() << "\" of type \"" << n->getTypeName() << "\"\n"; // Inspect input, output and interfaces of the node @@ -80,11 +78,11 @@ TEST_CASE( "Dataflow/Rendering/RenderingGraph", "[Dataflow][Rendering][Rendering // Nodes by level after the compilation auto c = g->compile(); REQUIRE( c == true ); - auto cn = g->getNodesByLevel(); + auto& cn = g->getNodesByLevel(); std::cout << "Nodes of the graph, sorted by level when compiling the graph :\n"; - for ( size_t i = 0; i < cn->size(); ++i ) { + for ( size_t i = 0; i < cn.size(); ++i ) { std::cout << "\tLevel " << i << " :\n"; - for ( const auto n : ( *cn )[i] ) { + for ( const auto n : cn[i] ) { std::cout << "\t\t\"" << n->getInstanceName() << "\"\n"; } } From 603f5416286baa656d30fc3b09ad5050f98629d9 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 22 Mar 2023 11:57:15 +0100 Subject: [PATCH 227/239] [engine] allow to set alpha to 0 or 1 (the default) when drawing skybox --- src/Engine/Data/EnvironmentTexture.cpp | 10 ++++++++-- src/Engine/Data/EnvironmentTexture.hpp | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Engine/Data/EnvironmentTexture.cpp b/src/Engine/Data/EnvironmentTexture.cpp index d542b2ceb6a..d915c53054e 100644 --- a/src/Engine/Data/EnvironmentTexture.cpp +++ b/src/Engine/Data/EnvironmentTexture.cpp @@ -665,10 +665,11 @@ void EnvironmentTexture::updateGL() { "in vec3 incidentDirection;\n" "uniform samplerCube skyTexture;\n" "uniform float strength;\n" + "uniform float alpha;\n" "void main(void)\n" "{\n" " vec3 envColor = texture(skyTexture, normalize(incidentDirection)).rgb;\n" - " outColor =vec4(strength*envColor, 1);\n" + " outColor =vec4(strength*envColor, alpha); \n" "}\n" }; Ra::Engine::Data::ShaderConfiguration config { "EnvironmentTexture::Builtin SkyBox" }; config.addShaderSource( Ra::Engine::Data::ShaderType::ShaderType_VERTEX, @@ -689,7 +690,8 @@ void EnvironmentTexture::updateGL() { } } -void EnvironmentTexture::render( const Ra::Engine::Data::ViewingParameters& viewParams ) { +void EnvironmentTexture::render( const Ra::Engine::Data::ViewingParameters& viewParams, + bool asOpaque ) { if ( m_isSkyBox ) { // put this in a initializeGL method ? if ( !m_glReady ) { updateGL(); } @@ -710,6 +712,10 @@ void EnvironmentTexture::render( const Ra::Engine::Data::ViewingParameters& view m_skyTexture->bind( 0 ); m_skyShader->setUniform( "skytexture", 0 ); m_skyShader->setUniform( "strength", m_environmentStrength ); + if ( asOpaque ) { m_skyShader->setUniform( "alpha", float( 1 ) ); } + else { + m_skyShader->setUniform( "alpha", float( 0 ) ); + } GLboolean depthEnabled; glGetBooleanv( GL_DEPTH_WRITEMASK, &depthEnabled ); glDepthMask( GL_FALSE ); diff --git a/src/Engine/Data/EnvironmentTexture.hpp b/src/Engine/Data/EnvironmentTexture.hpp index 9f71f1a242c..af81738d542 100644 --- a/src/Engine/Data/EnvironmentTexture.hpp +++ b/src/Engine/Data/EnvironmentTexture.hpp @@ -106,7 +106,7 @@ class RA_ENGINE_API EnvironmentTexture * \brief Render the envmap as a textured cube. This method does nothing if the envmap is not a * skybox \param viewParams The viewing parameter used to draw the scene */ - void render( const Ra::Engine::Data::ViewingParameters& viewParams ); + void render( const Ra::Engine::Data::ViewingParameters& viewParams, bool asOpaque = true ); /** * \brief Set the state of the skybox From 42844e899da270550721628b83f4e5add4f1ecc3 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 22 Mar 2023 12:02:06 +0100 Subject: [PATCH 228/239] [dataflow-rendering-shaders] add missing ouput in no-light vertex shader --- src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl b/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl index fde2a9a03ac..a7a86f018e0 100644 --- a/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl +++ b/src/Dataflow/Rendering/Shaders/shader_nolight.vert.glsl @@ -14,7 +14,7 @@ layout (location = 1) out vec3 out_normal; layout (location = 2) out vec3 out_texcoord; layout (location = 3) out vec3 out_vertexcolor; layout (location = 4) out vec3 out_tangent; - +layout (location = 5) out vec3 out_viewVector; void main() { @@ -34,4 +34,5 @@ void main() out_normal = normal; out_tangent = tangent; out_vertexcolor = in_color.rgb; + out_viewVector = normalize(eye - pos.xyz); } From 107d7823ab507719518570daad3bcfdd5a4cd86f Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 24 Mar 2023 08:30:51 +0100 Subject: [PATCH 229/239] [dataflow-rendering] update shader code for envlighting --- .../Rendering/Shaders/EnvLightNode/shader.frag.glsl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl index d43c2e7d021..80ec97dc1f4 100644 --- a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl +++ b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl @@ -105,9 +105,12 @@ void main() { vec2 size = textureSize( amb_occ_sampler, 0 ).xy; vec3 ao = texture( amb_occ_sampler, gl_FragCoord.xy / size ).rgb; bc.rgb = diffuse; - bc.r *= dot( normalWorld, redShCoeffs * normalWorld ); - bc.g *= dot( normalWorld, greenShCoeffs * normalWorld ); - bc.b *= dot( normalWorld, blueShCoeffs * normalWorld ); + vec3 irradiance = vec3( + dot( normalWorld, redShCoeffs * normalWorld ), + dot( normalWorld, greenShCoeffs * normalWorld ), + dot( normalWorld, blueShCoeffs * normalWorld ) + ); + bc.rgb *= irradiance / OneOverPi; // Specular envmap float cosTi = clamp( dot( rfl, normalWorld.xyz ), 0.001, 1. ); vec3 spec = clamp( specular * cosTi * OneOverPi * 0.5, 0.001, 1. ); From 263c35246be947951f049d25c249cec6df56ed0b Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 24 Mar 2023 08:31:36 +0100 Subject: [PATCH 230/239] [dataflow-rendering] allow using rendering node in any order --- .../Rendering/Nodes/RenderNodes/ClearColorNode.cpp | 7 ++++--- .../Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp | 3 ++- .../Nodes/RenderNodes/EnvironmentLightingNode.cpp | 3 ++- .../Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp | 2 +- .../Rendering/Nodes/RenderNodes/LocalLightingNode.cpp | 2 +- .../Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp | 5 ++--- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp index 87ce945f856..9fe71cf2eba 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp @@ -62,11 +62,12 @@ bool ClearColorNode::execute() { gl::glDepthMask( gl::GL_FALSE ); gl::glColorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); gl::glDisable( gl::GL_DEPTH_TEST ); - envmap->render( m_portInCamera->getData() ); + envmap->render( m_portInCamera->getData(), false ); gl::glDepthMask( gl::GL_TRUE ); gl::glEnable( gl::GL_DEPTH_TEST ); } else { + clearColor[3] = 0_ra; gl::glClearBufferfv( gl::GL_COLOR, 0, clearColor ); } @@ -76,7 +77,7 @@ bool ClearColorNode::execute() { } void ClearColorNode::toJsonInternal( nlohmann::json& data ) const { - auto c = ColorType ::linearRGBTosRGB( m_editableClearColor ); + auto c = ColorType::linearRGBTosRGB( m_editableClearColor ); std::array color { { c.x(), c.y(), c.z() } }; data["clearColor"] = color; } @@ -84,7 +85,7 @@ void ClearColorNode::toJsonInternal( nlohmann::json& data ) const { bool ClearColorNode::fromJsonInternal( const nlohmann::json& data ) { if ( data.contains( "clearColor" ) ) { std::array c = data["clearColor"]; - m_editableClearColor = ColorType ::sRGBToLinearRGB( ColorType( c[0], c[1], c[2] ) ); + m_editableClearColor = ColorType::sRGBToLinearRGB( ColorType( c[0], c[1], c[2] ) ); } else { m_editableClearColor = diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp index 1aef6c35b53..0a5252f5870 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EmissivityRenderNode.cpp @@ -50,7 +50,8 @@ void EmissivityNode::init() { m_nodeState->depthFunc( gl::GL_LEQUAL ); m_nodeState->depthMask( gl::GL_FALSE ); m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); - m_nodeState->disable( gl::GL_BLEND ); + m_nodeState->blendFuncSeparate( gl::GL_ONE, gl::GL_DST_ALPHA, gl::GL_ONE, gl::GL_ZERO ); + m_nodeState->enable( gl::GL_BLEND ); } void EmissivityNode::destroy() { diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp index 0c799960fcd..01d15c9b862 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/EnvironmentLightingNode.cpp @@ -54,7 +54,8 @@ void EnvironmentLightingNode::init() { m_nodeState->depthFunc( gl::GL_LEQUAL ); m_nodeState->depthMask( gl::GL_FALSE ); m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); - m_nodeState->blendFunc( gl::GL_ONE, gl::GL_ONE ); + // m_nodeState->blendFunc( gl::GL_ONE, gl::GL_DST_ALPHA ); + m_nodeState->blendFuncSeparate( gl::GL_ONE, gl::GL_DST_ALPHA, gl::GL_ONE, gl::GL_ZERO ); m_nodeState->enable( gl::GL_BLEND ); } diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp index 3b595b8e5f4..edce0331fe3 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/GeometryAovsNode.cpp @@ -43,8 +43,8 @@ GeometryAovsNode::GeometryAovsNode( const std::string& name ) : m_depthTexture = new Ra::Engine::Data::Texture( texParams ); addOutput( m_outDepthTex, m_depthTexture ); - addOutput( m_outPosInWorldTex, m_posInWorldTexture ); addOutput( m_outNormalInWorldTex, m_normalInWorldTexture ); + addOutput( m_outPosInWorldTex, m_posInWorldTexture ); } void GeometryAovsNode::init() { diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp index 22a42198728..93afa01af27 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/LocalLightingNode.cpp @@ -53,7 +53,7 @@ void LocalLightingNode::init() { m_nodeState->depthFunc( gl::GL_LEQUAL ); m_nodeState->depthMask( gl::GL_FALSE ); m_nodeState->colorMask( gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE, gl::GL_TRUE ); - m_nodeState->blendFunc( gl::GL_ONE, gl::GL_ONE ); + m_nodeState->blendFuncSeparate( gl::GL_ONE, gl::GL_DST_ALPHA, gl::GL_ONE, gl::GL_ZERO ); m_nodeState->enable( gl::GL_BLEND ); } diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp index de469fba06d..26d3dbd285b 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp @@ -15,12 +15,11 @@ namespace Nodes { using namespace Ra::Engine::Data; SsaoNode::SsaoNode( const std::string& name ) : RenderingNode( name, getTypename() ) { - addInput( m_inWorldPos ); - m_inWorldPos->mustBeLinked(); addInput( m_inWorldNormal ); m_inWorldNormal->mustBeLinked(); - + addInput( m_inWorldPos ); + m_inWorldPos->mustBeLinked(); addInput( m_inCamera ); m_inCamera->mustBeLinked(); From 5a91f037571340b607acb480c4049395a027a351 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Mon, 3 Apr 2023 18:43:48 +0200 Subject: [PATCH 231/239] [core] fix strange code duplication --- src/Core/Utils/TypesUtils.hpp | 52 ----------------------------------- 1 file changed, 52 deletions(-) diff --git a/src/Core/Utils/TypesUtils.hpp b/src/Core/Utils/TypesUtils.hpp index 19dc4f7f51d..6305cad088f 100644 --- a/src/Core/Utils/TypesUtils.hpp +++ b/src/Core/Utils/TypesUtils.hpp @@ -54,32 +54,6 @@ std::false_type is_container_impl( ... ); template using is_container = decltype( detail::is_container_impl( 0 ) ); -// Check if a type is a container with access to its element type and number -// adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable -namespace detail { - -using std::begin; -using std::end; - -template -auto is_container_impl( int ) - -> decltype( begin( std::declval() ) != - end( std::declval() ), // begin/end and operator != - void(), // Handle evil operator , - std::declval().empty(), - std::declval().size(), - ++std::declval() ) )&>(), // operator ++ - void( *begin( std::declval() ) ), // operator* - std::true_type {} ); - -template -std::false_type is_container_impl( ... ); - -} // namespace detail - -template -using is_container = decltype( detail::is_container_impl( 0 ) ); - // TypeList taken and adapted from // https://github.com/AcademySoftwareFoundation/openvdb/blob/master/openvdb/openvdb/TypeList.h // Only took small part of TypeList utilities @@ -190,32 +164,6 @@ std::string demangleType( const T& ) noexcept { return demangleType(); } -// Check if a type is a container with access to its element type and number -// adapted from https://stackoverflow.com/questions/13830158/check-if-a-variable-type-is-iterable -namespace detail { - -using std::begin; -using std::end; - -template -auto is_container_impl( int ) - -> decltype( begin( std::declval() ) != - end( std::declval() ), // begin/end and operator != - void(), // Handle evil operator , - std::declval().empty(), - std::declval().size(), - ++std::declval() ) )&>(), // operator ++ - void( *begin( std::declval() ) ), // operator* - std::true_type {} ); - -template -std::false_type is_container_impl( ... ); - -} // namespace detail - -template -using is_container = decltype( detail::is_container_impl( 0 ) ); - } // namespace Utils } // namespace Core } // namespace Ra From d7c6d0f699a678fe0731869ab52bbe11d64c4990 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Tue, 4 Apr 2023 19:39:34 +0200 Subject: [PATCH 232/239] [rendering] improve envmap shader for non PBR path --- src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl index 80ec97dc1f4..f56cbf9049d 100644 --- a/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl +++ b/src/Dataflow/Rendering/Shaders/EnvLightNode/shader.frag.glsl @@ -110,13 +110,13 @@ void main() { dot( normalWorld, greenShCoeffs * normalWorld ), dot( normalWorld, blueShCoeffs * normalWorld ) ); - bc.rgb *= irradiance / OneOverPi; + bc.rgb *= irradiance; // Specular envmap float cosTi = clamp( dot( rfl, normalWorld.xyz ), 0.001, 1. ); vec3 spec = clamp( specular * cosTi * OneOverPi * 0.5, 0.001, 1. ); float r = getGGXRoughness( material, getPerVertexTexCoord() ) * numLod; bc.rgb += textureLod( envTexture, rfl, r ).rgb * spec; - bc.rgb *= ao * envStrength * OneOverPi * 0.5 ; + bc.rgb *= ao * envStrength; } else { bc.rgb = vec3( 0 ); } From 37f1fe852715dbe25925a2a62978138791b73f45 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Apr 2023 18:10:33 +0200 Subject: [PATCH 233/239] [rendering] remove unsed file --- src/Dataflow/Rendering/RenderingGraph.inl | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/Dataflow/Rendering/RenderingGraph.inl diff --git a/src/Dataflow/Rendering/RenderingGraph.inl b/src/Dataflow/Rendering/RenderingGraph.inl deleted file mode 100644 index e69de29bb2d..00000000000 From a4a355f0a9aa918d7d8a6eb86950a96b02a545c6 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Apr 2023 18:11:09 +0200 Subject: [PATCH 234/239] [dataflow-rendering] Simplify Display Sink --- .../Rendering/Nodes/Sinks/DisplaySinkNode.cpp | 41 ++++++++----------- .../Rendering/Nodes/Sinks/DisplaySinkNode.hpp | 4 +- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp index 4ee1c7d767d..2fd6e385fb0 100644 --- a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp @@ -1,7 +1,5 @@ #include -#define MAX_DISPLAY_INPUTS 8 - namespace Ra { namespace Dataflow { namespace Rendering { @@ -24,33 +22,27 @@ DisplaySinkNode::~DisplaySinkNode() { } bool DisplaySinkNode::execute() { - // TODO verify the robustness of this (address of port data stored in a vector ....) - if ( m_firstRun ) { - m_firstRun = false; - bool gotData { false }; - for ( size_t i = 0; i < DisplaySinkNode::MaxImages; i++ ) { - if ( m_inputs[i]->isLinked() ) { - auto input = static_cast*>( m_inputs[i].get() ); - m_textures[i] = &( input->getData() ); - gotData = true; - } - else { - m_textures[i] = nullptr; - } + // check if connections have changed on the input port + bool gotData { false }; + for ( size_t i = 0; i < DisplaySinkNode::MaxImages; i++ ) { + if ( m_inputs[i]->isLinked() ) { + auto input = static_cast*>( m_inputs[i].get() ); + m_textures[i] = &( input->getData() ); + gotData = true; } - auto interfacePort = static_cast>*>( m_interface[0] ); - if ( gotData ) { interfacePort->setData( &m_textures ); } else { - interfacePort->setData( nullptr ); + m_textures[i] = nullptr; } - // not sure DisplaySink should be observable - this->notify( m_textures ); } - return true; -} + auto interfacePort = static_cast>*>( m_interface[0] ); + if ( gotData ) { interfacePort->setData( &m_textures ); } + else { + interfacePort->setData( nullptr ); + } + // not sure DisplaySink should be observable + this->notify( m_textures ); -const std::vector& DisplaySinkNode::getTextures() { - return m_textures; + return true; } void DisplaySinkNode::observeConnection( @@ -59,7 +51,6 @@ void DisplaySinkNode::observeConnection( const PortIn& /* port */, bool /*connected*/ ) { // deffer the port management to DisplaySinkNode::execute() - m_firstRun = true; } const std::vector& DisplaySinkNode::buildInterfaces( Node* parent ) { diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp index a587f0c99d0..724f43a8660 100644 --- a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.hpp @@ -29,7 +29,7 @@ class RA_DATAFLOW_API DisplaySinkNode static const std::string getTypename() { return "Display Sink"; } - const std::vector& getTextures(); + const std::vector& getTextures() const { return m_textures; } protected: void toJsonInternal( nlohmann::json& ) const override {} @@ -43,8 +43,6 @@ class RA_DATAFLOW_API DisplaySinkNode // the observer method void observeConnection( const std::string& name, const PortIn& port, bool connected ); - - bool m_firstRun { true }; }; } // namespace Nodes From 52a920dcf0940542ddf464208cca82deb0446f84 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Wed, 5 Apr 2023 18:11:50 +0200 Subject: [PATCH 235/239] [dataflow-rendering] Allows ClearColor to serve its own Beaty texture --- .../Nodes/RenderNodes/ClearColorNode.cpp | 27 +++++++++++++++++-- .../Nodes/RenderNodes/ClearColorNode.hpp | 5 +++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp index 9fe71cf2eba..315ed29c21c 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp @@ -17,7 +17,7 @@ using EnvironmentType = std::shared_ptr; ClearColorNode::ClearColorNode( const std::string& name ) : RenderingNode( name, getTypename() ) { addInput( m_portInColorTex ); - m_portInColorTex->mustBeLinked(); + // m_portInColorTex->mustBeLinked(); addInput( m_portInClearColor ); addInput( m_portInEnvmap ); addInput( m_portInCamera ); @@ -29,17 +29,40 @@ ClearColorNode::ClearColorNode( const std::string& name ) : RenderingNode( name, } void ClearColorNode::init() { + Ra::Engine::Data::TextureParameters texParams { getInstanceName() + " (Color)", + gl::GL_TEXTURE_2D, + 1, + 1, + 1, + gl::GL_RGBA, + gl::GL_RGBA32F, + gl::GL_FLOAT, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_CLAMP_TO_EDGE, + gl::GL_LINEAR, + gl::GL_LINEAR, + nullptr }; + m_texture = new Ra::Engine::Data::Texture( texParams ); m_framebuffer = new globjects::Framebuffer(); } void ClearColorNode::destroy() { + delete m_texture; delete m_framebuffer; } +void ClearColorNode::resize( uint32_t w, uint32_t h ) { + if ( !m_portInColorTex->isLinked() ) { m_texture->resize( w, h ); } +} + bool ClearColorNode::execute() { // Color texture - m_colorTexture = &m_portInColorTex->getData(); + if ( !m_portInColorTex->isLinked() ) { m_colorTexture = m_texture; } + else { + m_colorTexture = &m_portInColorTex->getData(); + } m_portOutColorTex->setData( m_colorTexture ); // Clear color diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp index 4bbda2864c5..345bd2b23c7 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.hpp @@ -35,7 +35,7 @@ class RA_DATAFLOW_API ClearColorNode : public RenderingNode bool execute() override; void destroy() override; - void resize( uint32_t, uint32_t ) override {} + void resize( uint32_t, uint32_t ) override; static const std::string getTypename() { return "Clear Color Pass"; } @@ -44,6 +44,9 @@ class RA_DATAFLOW_API ClearColorNode : public RenderingNode bool fromJsonInternal( const nlohmann::json& data ) override; private: + // This texture will be used if the port m_portInColorTex is not linked + Ra::Engine::Data::Texture* m_texture { nullptr }; + // This texture will be used if the port m_portInColorTex is linked Ra::Engine::Data::Texture* m_colorTexture { nullptr }; globjects::Framebuffer* m_framebuffer { nullptr }; From cdea8867c5fbc37e4279999650cd9e464a966898 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Apr 2023 16:15:36 +0000 Subject: [PATCH 236/239] [general] format with new guidelines --- examples/DataflowExamples/GraphRendering/main.cpp | 12 +++--------- .../Rendering/Nodes/RenderNodes/ClearColorNode.cpp | 4 +--- .../Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp | 4 +--- .../Nodes/RenderNodes/WireframeRenderingNode.cpp | 8 ++------ .../Nodes/RenderNodes/WireframeRenderingNode.hpp | 4 +--- .../Rendering/Nodes/Sinks/DisplaySinkNode.cpp | 8 ++------ .../Rendering/Nodes/Sources/EnvMapSourceNode.hpp | 4 +--- src/Dataflow/Rendering/Nodes/Sources/Scene.hpp | 12 +++--------- .../Rendering/Renderer/ControllableRenderer.cpp | 4 +--- .../Rendering/Renderer/ControllableRenderer.hpp | 8 ++++---- .../Rendering/Renderer/RenderGraphController.hpp | 8 ++++---- src/Engine/Data/EnvironmentTexture.cpp | 4 +--- src/Engine/Data/SphereSampler.hpp | 4 ++-- 13 files changed, 26 insertions(+), 58 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index 22c8996138a..a33c6b3288c 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -233,9 +233,7 @@ class MyRendererController : public RenderGraphController std::cout << "\t\t\"" << portName << "\" generating type \"" << portType << "\"\n"; } } - else { - std::cerr << "Unable to compile the graph " << g.getInstanceName() << "\n"; - } + else { std::cerr << "Unable to compile the graph " << g.getInstanceName() << "\n"; } } public: @@ -289,9 +287,7 @@ class MyRendererController : public RenderGraphController linksOK && m_renderGraph->addLink( sceneNode, "objects", geomAovs, "objects" ); if ( !linksOK ) { LOG( logERROR ) << "Something went wrong when linking nodes !!! "; } - else { - LOG( logINFO ) << "Graph linked successfully!!! "; - } + else { LOG( logINFO ) << "Graph linked successfully!!! "; } inspectGraph( *m_renderGraph ); @@ -346,9 +342,7 @@ int main( int argc, char* argv[] ) { graphOption = parser.value( graphOpt ).toStdString(); std::cout << "Got a graph option : " << *graphOption << std::endl; } - else { - std::cout << "No graph option" << std::endl; - } + else { std::cout << "No graph option" << std::endl; } //! getting graph argument on the command line //! [Initializing the application] diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp index 315ed29c21c..69034bc9147 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/ClearColorNode.cpp @@ -60,9 +60,7 @@ bool ClearColorNode::execute() { // Color texture if ( !m_portInColorTex->isLinked() ) { m_colorTexture = m_texture; } - else { - m_colorTexture = &m_portInColorTex->getData(); - } + else { m_colorTexture = &m_portInColorTex->getData(); } m_portOutColorTex->setData( m_colorTexture ); // Clear color diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp index 26d3dbd285b..343c0c7ee85 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/SsaoRenderNode.cpp @@ -98,9 +98,7 @@ void SsaoNode::resize( uint32_t width, uint32_t height ) { bool SsaoNode::execute() { auto aabb = Ra::Engine::RadiumEngine::getInstance()->computeSceneAabb(); if ( aabb.isEmpty() ) { m_sceneDiag = 1_ra; } - else { - m_sceneDiag = aabb.diagonal().norm(); - } + else { m_sceneDiag = aabb.diagonal().norm(); } Ra::Engine::Data::RenderParameters inPassParams; // Positions diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp index 8c8a9a6cf1d..2bbd825e16d 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.cpp @@ -89,9 +89,7 @@ void setupLineMesh( std::shared_ptr& disp, CoreGeome core.vertexAttribs().getAttrib( handle ).attach( VerticesUpdater( disp, core ) ); core.attach( IndicesUpdater( disp, core ) ); } - else { - disp.reset(); - } + else { disp.reset(); } } template @@ -233,9 +231,7 @@ bool WireframeRenderingNode::execute() { m_wireframes[ro.get()] = disp; wro = disp; } - else { - wro = it->second; - } + else { wro = it->second; } auto shader = m_shaderMngr->getShaderProgram( "WireframeNode::WireframeRendering" ); if ( wro && shader ) { diff --git a/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp index afd6eb6a736..4ca29320445 100644 --- a/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp +++ b/src/Dataflow/Rendering/Nodes/RenderNodes/WireframeRenderingNode.hpp @@ -41,9 +41,7 @@ class RA_DATAFLOW_API WireframeRenderingNode : public RenderingNode bool fromJsonInternal( const nlohmann::json& data ) override { if ( data.contains( "activated" ) ) { m_activate = data["activated"]; } - else { - m_activate = false; - } + else { m_activate = false; } return true; } diff --git a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp index 2fd6e385fb0..b332fd6db5e 100644 --- a/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp +++ b/src/Dataflow/Rendering/Nodes/Sinks/DisplaySinkNode.cpp @@ -30,15 +30,11 @@ bool DisplaySinkNode::execute() { m_textures[i] = &( input->getData() ); gotData = true; } - else { - m_textures[i] = nullptr; - } + else { m_textures[i] = nullptr; } } auto interfacePort = static_cast>*>( m_interface[0] ); if ( gotData ) { interfacePort->setData( &m_textures ); } - else { - interfacePort->setData( nullptr ); - } + else { interfacePort->setData( nullptr ); } // not sure DisplaySink should be observable this->notify( m_textures ); diff --git a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp index 3e5f6a57100..223cdd433a7 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp +++ b/src/Dataflow/Rendering/Nodes/Sources/EnvMapSourceNode.hpp @@ -60,9 +60,7 @@ SingleDataSourceNode::fromJsonInternal( const nlohmann std::string f1 = files.substr( 0, pos - 1 ); envmap_exist = std::filesystem::exists( f1 ); } - else { - envmap_exist = std::filesystem::exists( files ); - } + else { envmap_exist = std::filesystem::exists( files ); } if ( envmap_exist ) { auto envmp = std::make_shared( files, true ); envmp->setStrength( s ); diff --git a/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp b/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp index 17ecae1312a..124be48693d 100644 --- a/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp +++ b/src/Dataflow/Rendering/Nodes/Sources/Scene.hpp @@ -28,25 +28,19 @@ class RA_DATAFLOW_API SceneNode : public Node bool execute() override { auto interfaceRo = static_cast>*>( m_interface[0] ); if ( interfaceRo->isLinked() ) { m_roOut->setData( &( interfaceRo->getData() ) ); } - else { - m_roOut->setData( m_objects ); - } + else { m_roOut->setData( m_objects ); } auto interfaceLights = static_cast>*>( m_interface[1] ); if ( interfaceLights->isLinked() ) { m_lightOut->setData( &( interfaceLights->getData() ) ); } - else { - m_lightOut->setData( m_lights ); - } + else { m_lightOut->setData( m_lights ); } auto interfaceCamera = static_cast*>( m_interface[2] ); if ( interfaceCamera->isLinked() ) { m_cameraOut->setData( &( interfaceCamera->getData() ) ); } - else { - m_cameraOut->setData( m_camera ); - } + else { m_cameraOut->setData( m_camera ); } return true; } diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp index 59b5bdecc2c..161e6d77723 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.cpp @@ -124,9 +124,7 @@ void ControllableRenderer::renderInternal( const Ra::Engine::Data::ViewingParame if ( renderings.size() > 0 ) { m_colorTexture = renderings[0]; // allow to select which image to fetch } - else { - m_colorTexture = nullptr; - } + else { m_colorTexture = nullptr; } } void ControllableRenderer::postProcessInternal( const Ra::Engine::Data::ViewingParameters& ) { diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index be88d19f2a2..7f3f4682399 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -61,10 +61,10 @@ class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer public: RendererController(); - virtual ~RendererController() = default; - RendererController( const RendererController& ) = delete; - RendererController( const RendererController&& ) = delete; - RendererController& operator=( RendererController&& ) = delete; + virtual ~RendererController() = default; + RendererController( const RendererController& ) = delete; + RendererController( const RendererController&& ) = delete; + RendererController& operator=( RendererController&& ) = delete; RendererController& operator=( const RendererController& ) = delete; /// Configuration function. diff --git a/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp b/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp index 7c4f3ff9d06..90a8ec2c1d2 100644 --- a/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp +++ b/src/Dataflow/Rendering/Renderer/RenderGraphController.hpp @@ -16,10 +16,10 @@ class RA_DATAFLOW_API RenderGraphController : public ControllableRenderer::Rende public: RenderGraphController(); - virtual ~RenderGraphController() = default; - RenderGraphController( const RenderGraphController& ) = delete; - RenderGraphController( const RenderGraphController&& ) = delete; - RenderGraphController& operator=( RenderGraphController&& ) = delete; + virtual ~RenderGraphController() = default; + RenderGraphController( const RenderGraphController& ) = delete; + RenderGraphController( const RenderGraphController&& ) = delete; + RenderGraphController& operator=( RenderGraphController&& ) = delete; RenderGraphController& operator=( const RenderGraphController& ) = delete; /// Configuration function. diff --git a/src/Engine/Data/EnvironmentTexture.cpp b/src/Engine/Data/EnvironmentTexture.cpp index d915c53054e..f85f56458da 100644 --- a/src/Engine/Data/EnvironmentTexture.cpp +++ b/src/Engine/Data/EnvironmentTexture.cpp @@ -713,9 +713,7 @@ void EnvironmentTexture::render( const Ra::Engine::Data::ViewingParameters& view m_skyShader->setUniform( "skytexture", 0 ); m_skyShader->setUniform( "strength", m_environmentStrength ); if ( asOpaque ) { m_skyShader->setUniform( "alpha", float( 1 ) ); } - else { - m_skyShader->setUniform( "alpha", float( 0 ) ); - } + else { m_skyShader->setUniform( "alpha", float( 0 ) ); } GLboolean depthEnabled; glGetBooleanv( GL_DEPTH_WRITEMASK, &depthEnabled ); glDepthMask( GL_FALSE ); diff --git a/src/Engine/Data/SphereSampler.hpp b/src/Engine/Data/SphereSampler.hpp index edcd9dcd78e..f39987e777c 100644 --- a/src/Engine/Data/SphereSampler.hpp +++ b/src/Engine/Data/SphereSampler.hpp @@ -25,10 +25,10 @@ class RA_ENGINE_API SphereSampler /// @param method the sampling method to use (@see SamplingMethod) /// @param level the number of point to generate explicit SphereSampler( SamplingMethod method, int level = 0 ); - SphereSampler( const SphereSampler& ) = delete; + SphereSampler( const SphereSampler& ) = delete; SphereSampler& operator=( const SphereSampler& ) = delete; SphereSampler( SphereSampler&& ) = delete; - SphereSampler& operator=( SphereSampler&& ) = delete; + SphereSampler& operator=( SphereSampler&& ) = delete; /// destructor ~SphereSampler() = default; From 6249e73aa2a9a275b50abac9ceeb4572e6898d09 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 7 Apr 2023 16:38:15 +0200 Subject: [PATCH 237/239] [dataflow-rendering] fix merge error --- src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp index 7f3f4682399..f2ede8222fa 100644 --- a/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp +++ b/src/Dataflow/Rendering/Renderer/ControllableRenderer.hpp @@ -49,7 +49,7 @@ namespace Renderer { * * @see rendering.md for description of the renderer */ -class RA_DATAFLOW_API DataflowRenderer : public Ra::Engine::Rendering::Renderer +class RA_DATAFLOW_API ControllableRenderer : public Ra::Engine::Rendering::Renderer { public: From d24cd02c0b6ea866321651acee75f763eeafb110 Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 7 Apr 2023 16:39:20 +0200 Subject: [PATCH 238/239] [examples] improve dataflow-rendering demo --- .../DataflowExamples/GraphRendering/main.cpp | 73 ++++++++++++------- 1 file changed, 45 insertions(+), 28 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/main.cpp b/examples/DataflowExamples/GraphRendering/main.cpp index a33c6b3288c..7237434e0f6 100644 --- a/examples/DataflowExamples/GraphRendering/main.cpp +++ b/examples/DataflowExamples/GraphRendering/main.cpp @@ -3,12 +3,15 @@ #include #include +#include #include +#include #include #include #include +#include #include #include @@ -157,18 +160,58 @@ class DemoWindowFactory : public BaseApplication::WindowFactory #endif addFileMenu( window ); addRendererMenu( window, m_renderers ); + auto processPicking = + [viewer = window->getViewer()]( + const Ra::Engine::Rendering::Renderer::PickingResult& pickingResult ) { + if ( pickingResult.getRoIdx().isValid() ) { + std::cout << "Pick Ro number " << pickingResult.getRoIdx() << "\n"; + auto roManager = + Ra::Engine::RadiumEngine::getInstance()->getRenderObjectManager(); + auto editedRo = roManager->getRenderObject( pickingResult.getRoIdx() ); + auto pickedMaterial = editedRo->getMaterial(); + + // Update the viewer whenever a parameter gets modified + auto on_materialParametersModified = [editedRo, + viewer]( const std::string& nm ) { + if ( editedRo ) { + editedRo->getMaterial()->updateFromParameters(); + editedRo->setTransparent( editedRo->getMaterial()->isTransparent() ); + } + viewer->needUpdate(); + }; + std::cout << "Create materialEditor for Ro " << editedRo->getIndex() << " (" + << editedRo->getName() << ")\n"; + auto matParamsEditor = new Ra::Gui::MaterialParameterEditor; + matParamsEditor->setAttribute( Qt::WA_DeleteOnClose, true ); + QObject::connect( matParamsEditor, + &Ra::Gui::MaterialParameterEditor::materialParametersModified, + on_materialParametersModified ); + /* + QObject::connect( + matParamsEditor, &QObject::destroyed, [matParamsEditor, editedRo]() { + std::cout << "Destroying editor for Ro " << editedRo->getIndex() << " + (" + << editedRo->getName() << ")\n"; + } ); + */ + matParamsEditor->setupFromMaterial( pickedMaterial ); + matParamsEditor->show(); + } + }; + + QObject::connect( + window->getViewer(), &Ra::Gui::Viewer::rightClickPicking, processPicking ); + return window; } }; /* ----------------------------------------------------------------------------------- */ // Renderer controller -#include #include #include #include #include -#include class MyRendererController : public RenderGraphController { @@ -256,17 +299,6 @@ class MyRendererController : public RenderGraphController m_renderGraph->addNode( std::unique_ptr( resultNode ) ); auto geomAovs = new GeometryAovsNode( "Geometry Aovs" ); m_renderGraph->addNode( std::unique_ptr( geomAovs ) ); -#if 0 - auto textureSource = new ColorTextureNode( "Beauty" ); - m_renderGraph->addNode( std::unique_ptr( textureSource ) ); - auto clearNode = new ClearColorNode( " Clear" ); - m_renderGraph->addNode( std::unique_ptr( clearNode ) ); - - bool linksOK = true; - linksOK = m_renderGraph->addLink( - textureSource, "texture", clearNode, "colorTextureToClear" ); - linksOK = linksOK && m_renderGraph->addLink( clearNode, "image", resultNode, "Beauty" ); -#endif auto simpleRenderNode = new SimpleRenderNode( "renderOperator" ); m_renderGraph->addNode( std::unique_ptr( simpleRenderNode ) ); bool linksOK = true; @@ -297,21 +329,6 @@ class MyRendererController : public RenderGraphController } }; -#if 0 - /// Resize function - /// Called each time the renderer is resized - void resize( int w, int h ) override { - LOG( logINFO ) << "MyRendererController::resize"; - RenderGraphController::resize( w, h ); - }; - - /// Update function - /// Called once before each frame to update the internal state of the renderer - void update( const Ra::Engine::Data::ViewingParameters& renderData ) override { - LOG( logINFO ) << "MyRendererController::update"; - RenderGraphController::update( renderData ); - }; -#endif [[nodiscard]] std::string getRendererName() const override { return "Custom Node Renderer"; } }; From 66c36f99640f06aff638c6b25864eb1815bdbcfd Mon Sep 17 00:00:00 2001 From: Mathias Paulin Date: Fri, 30 Jun 2023 11:26:05 +0200 Subject: [PATCH 239/239] [cmake][dataflow] update cmake min version to 3.18 --- examples/DataflowExamples/GraphRendering/CMakeLists.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/DataflowExamples/GraphRendering/CMakeLists.txt b/examples/DataflowExamples/GraphRendering/CMakeLists.txt index af156ab5f3c..7e461459903 100644 --- a/examples/DataflowExamples/GraphRendering/CMakeLists.txt +++ b/examples/DataflowExamples/GraphRendering/CMakeLists.txt @@ -1,7 +1,6 @@ -cmake_minimum_required(VERSION 3.6) -if(${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_GREATER "3.9") - cmake_policy(SET CMP0071 NEW) -endif() +cmake_minimum_required(VERSION 3.18) +cmake_policy(SET CMP0071 NEW) + if(APPLE) cmake_policy(SET CMP0042 NEW) endif(APPLE)