diff --git a/CMakeLists.txt b/CMakeLists.txt index 51399f0..ef8875f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,9 +66,9 @@ set( PRIVATE_EXT_LIBS ) ####### # Boost (1.46 required for filesystem version 3) -list( APPEND BOOST_COMPONENTS chrono date_time filesystem program_options system thread ) +list( APPEND BOOST_COMPONENTS chrono date_time filesystem system ) find_package( Boost 1.46.0 REQUIRED COMPONENTS ${BOOST_COMPONENTS} ) -list( APPEND PUBLIC_EXT_LIBS Boost::boost Boost::chrono Boost::date_time Boost::program_options Boost::thread Boost::filesystem ) +list( APPEND PUBLIC_EXT_LIBS Boost::boost Boost::chrono Boost::date_time Boost::filesystem ) # make sure dynamic linking is assumed for all boost libraries set( Boost_USE_STATIC_LIBS OFF ) set( Boost_USE_STATIC_RUNTIME OFF ) diff --git a/Cpp/Library/Data/Data.cc b/Cpp/Library/Data/Data.cc index 12be720..af7e59a 100644 --- a/Cpp/Library/Data/Data.cc +++ b/Cpp/Library/Data/Data.cc @@ -7,6 +7,8 @@ #include "Data.hh" +#include "typename.hh" + namespace Nymph { Data::Data() @@ -15,4 +17,9 @@ namespace Nymph Data::~Data() {} + std::string Data::GetName() const + { + return scarab::type(*this); + } + } /* namespace Nymph */ diff --git a/Cpp/Library/Data/Data.hh b/Cpp/Library/Data/Data.hh index 590deca..4a90da0 100644 --- a/Cpp/Library/Data/Data.hh +++ b/Cpp/Library/Data/Data.hh @@ -9,6 +9,8 @@ #ifndef NYMPH_DATA_HH_ #define NYMPH_DATA_HH_ +#include + namespace Nymph { @@ -17,6 +19,8 @@ namespace Nymph public: Data(); virtual ~Data(); + + std::string GetName() const; }; } /* namespace Nymph */ diff --git a/Cpp/Library/Data/DataFrame.cc b/Cpp/Library/Data/DataFrame.cc index 1224059..c1bd17b 100644 --- a/Cpp/Library/Data/DataFrame.cc +++ b/Cpp/Library/Data/DataFrame.cc @@ -9,11 +9,95 @@ namespace Nymph { + IndexableData::IndexableData( std::unique_ptr< Data >&& dataPtr, std::type_index&& typeIndex, std::string&& typeName ) : + fDataPtr( std::move(dataPtr) ), + fTypeIndex( std::move(typeIndex) ), + fTypeName( std::move(typeName) ) + {} + + DataFrame::DataFrame() : - fDataObjects() + fDataObjects(), + fDataMapByType( fDataObjects.get() ), + fDataMapByTypeName( fDataObjects.get() ) {} DataFrame::~DataFrame() {} + bool DataFrame::Has( const std::string& typeName ) const + { + return fDataMapByTypeName.count( typeName ) != 0; + } + + Data& DataFrame::Get( const std::string& typeName ) + { + auto iter = fDataMapByTypeName.find( typeName ); + if( iter == fDataMapByTypeName.end() ) + { + THROW_EXCEPT_HERE( DataFrameException() << "Adding event type by Get(string) is not yet implemented" ); +/* // TODO: this doesn't work; needs mechanism to go from string to type + auto result = fDataMapByTypeName.insert( IndexableData::Create() ); + if( result.second ) + { + return static_cast< XDataNoConst& >( *result.first->fDataPtr ); + } + else + { + THROW_EXCEPT_HERE( DataFrameException() << "Data type called <" << typeName << "> could not be added to the data frame" ); + } +*/ } + return *iter->fDataPtr; + } + + const Data& DataFrame::Get( const std::string& typeName ) const + { + auto iter = fDataMapByTypeName.find( typeName ); + if( iter == fDataMapByTypeName.end() ) + { + THROW_EXCEPT_HERE( DataFrameException() << "Data type called <" << typeName << "> is not present when const Get() was called" ); + } + return *iter->fDataPtr; + } + + void DataFrame::Set( const std::string& typeName, Data* ptr ) + { + // Note: takes ownership of ptr + DoSet( IndexableData( std::unique_ptr(ptr), typeid(*ptr), scarab::type(*ptr) ) ); + return; + } + + void DataFrame::Set( const std::string& typeName, std::unique_ptr&& ptr ) + { + // Note: takes ownership of object pointed to by ptr + auto& ref = *ptr.get(); + DoSet( IndexableData( std::move(ptr), typeid(ref), scarab::type(*ptr) ) ); + return; + } + + void DataFrame::DoSet( IndexableData&& indData ) + { + auto iter = fDataMapByType.find( indData.fTypeIndex ); + if( iter == fDataMapByType.end() ) + { + auto result = fDataMapByType.insert( std::move(indData) ); + if( ! result.second ) + { + THROW_EXCEPT_HERE( DataFrameException() << "Attempt to insert new data object failed for data type named <" << indData.fTypeName << ">" ); + } + return; + } + if( ! fDataMapByType.replace( iter, std::move(indData) ) ) + { + THROW_EXCEPT_HERE( DataFrameException() << "Attempt to replace data object failed for data type <" << indData.fTypeName << ">" ); + } + return; + } + + void DataFrame::Remove( const std::string& typeName ) + { + fDataMapByTypeName.erase( typeName ); + return; + } + } /* namespace Nymph */ diff --git a/Cpp/Library/Data/DataFrame.hh b/Cpp/Library/Data/DataFrame.hh index 755f206..442b647 100644 --- a/Cpp/Library/Data/DataFrame.hh +++ b/Cpp/Library/Data/DataFrame.hh @@ -16,10 +16,16 @@ #include "typename.hh" -#include +#include +#include +#include +#include + #include #include +namespace bmi = ::boost::multi_index; + namespace Nymph { class DataFrameException : public scarab::typed_exception< DataFrameException > @@ -29,6 +35,37 @@ namespace Nymph ~DataFrameException() = default; }; + // map< std::string, std::type_index > may give us what we need to go from string to class + // * this would enable DataFrame::Set( std::string, Data* ) and DataFrame::Set( std::string, std::unique_ptr ) + // * would not enable DataFrame::Get(); need to be able to create an instance of the type + + /// Struct to hold individual data pointers and indexing information for the DataFrame + struct IndexableData + { + std::unique_ptr< Data > fDataPtr; + std::type_index fTypeIndex; + std::string fTypeName; + + IndexableData( std::unique_ptr< Data >&& dataPtr, std::type_index&& typeIndex, std::string&& typeName ); + + // Use factory functions so that we can template it with the derived data type + // (wouldn't be able to have the empty constructor beacuse there's nothing to determine the template type by, + // and you can't have a template argument for a constructor) + + /// Create an IndexableData with an empty object of type XData + template< typename XData > + static IndexableData Create(); + /// Create an IndexableData with data (claims ownership of that object) + template< typename XData > + static IndexableData Create( XData* data ); + /// Create an IndexableData with the object pointed to by dataPtr (claims ownership of that object) + template< typename XData > + static IndexableData Create( std::unique_ptr< XData >&& dataPtr ); + /// Create an IndexableData with a copy of the provided object + template< typename XData > + static IndexableData Create( const XData& data ); + }; + // forward declare so we can define DataHandle here class DataFrame; @@ -58,6 +95,10 @@ namespace Nymph /// Returns true if the frame has no data objects bool Empty() const; + //********************** + // Type-based interface + //********************** + /// Returns true if object of type(s) XData exist in the frame; returns false otherwise; Has<>() returns true template< typename... XData > bool Has() const; @@ -92,16 +133,90 @@ namespace Nymph template< typename XData > void Remove(); + + //************************ + // String-based interface + //************************ + + bool Has( const std::string& typeName ) const; + + Data& Get( const std::string& typeName ); + + const Data& Get( const std::string& typeName ) const; + + void Set( const std::string& typeName, Data* ptr ); + + void Set( const std::string& typeName, std::unique_ptr&& ptr ); + + //void Set( const std::string& typeName, const Data& obj ); <-- this probably requires having Clone() functions setup in Data + + void Remove( const std::string& typeName ); + + + //********* + // Storage + //********* + // typedef used to avoid problems with the comma in the MEMVAR macro - typedef std::unordered_map< std::type_index, std::unique_ptr > DataMap; + //typedef std::unordered_map< std::type_index, std::unique_ptr > DataMap; + // tags + struct type_t{}; + struct tname_t{}; + typedef bmi::multi_index_container< + IndexableData, + bmi::indexed_by< + bmi::hashed_unique< bmi::tag, bmi::key< &IndexableData::fTypeIndex > >, + bmi::hashed_unique< bmi::tag, bmi::key< &IndexableData::fTypeName > > + > + > DataMap; + typedef DataMap::index::type DataMapByType; + typedef DataMap::index::type DataMapByTypeName; MEMVAR_REF( DataMap, DataObjects ); protected: template< typename XData > bool HasOneType() const; + + void DoSet( IndexableData&& indData ); + + DataMapByType& fDataMapByType; + DataMapByTypeName& fDataMapByTypeName; }; + //************************* + // IndexableData functions + //************************* + + template< typename XData > + IndexableData IndexableData::Create() + { + return IndexableData{ std::make_unique(), typeid(XData), scarab::type() }; + } + + template< typename XData > + IndexableData IndexableData::Create( XData* data ) + { + return IndexableData{ std::unique_ptr(data), typeid(XData), scarab::type() }; + } + + template< typename XData > + IndexableData IndexableData::Create( std::unique_ptr< XData >&& dataPtr ) + { + return IndexableData{ std::move(dataPtr), typeid(XData), scarab::type() }; + } + + template< typename XData > + IndexableData IndexableData::Create( const XData& data ) + { + return IndexableData{ std::make_unique(data), typeid(XData), scarab::type() }; + } + + + //********************* + // DataFrame functions + //********************* + inline bool DataFrame::Empty() const { return fDataObjects.empty(); @@ -117,53 +232,65 @@ namespace Nymph bool DataFrame::HasOneType() const { typedef std::remove_const_t< XData > XDataNoConst; - if( fDataObjects.count( typeid(XDataNoConst) ) == 0 ) return false; - return true; + return fDataMapByType.count( typeid(XDataNoConst) ) != 0; } template< typename XData > XData& DataFrame::Get() { typedef std::remove_const_t< XData > XDataNoConst; - if( ! Has< XDataNoConst >() ) + auto iter = fDataMapByType.find( typeid(XDataNoConst) ); + if( iter == fDataMapByType.end() ) { - fDataObjects[ typeid(XDataNoConst) ].reset( new XDataNoConst() ); + auto result = fDataMapByType.insert( IndexableData::Create() ); + if( result.second ) + { + return static_cast< XDataNoConst& >( *result.first->fDataPtr ); + } + else + { + THROW_EXCEPT_HERE( DataFrameException() << "Data type <" << scarab::type() << "> could not be added to the data frame" ); + } } - return static_cast< XDataNoConst& >( *fDataObjects[typeid(XDataNoConst)] ); + return static_cast< XDataNoConst& >( *iter->fDataPtr ); } template< typename XData > const XData& DataFrame::Get() const { typedef std::remove_const_t< XData > XDataNoConst; - if( Has< XDataNoConst >() ) + auto iter = fDataMapByType.find( typeid(XDataNoConst) ); + if( iter == fDataMapByType.end() ) { - return static_cast< const XDataNoConst& >( *fDataObjects.at(typeid(XDataNoConst)) ); + THROW_EXCEPT_HERE( DataFrameException() << "Data type <" << scarab::type() << "> is not present when const Get() was called" ); } - THROW_EXCEPT_HERE( DataFrameException() << "Data type <" << scarab::type(XDataNoConst()) << "> is not present when const Get() was called" ); + return static_cast< const XDataNoConst& >( *iter->fDataPtr ); } template< typename XData > void DataFrame::Set( XData* ptr ) { + // Note: takes ownership of ptr typedef std::remove_const_t< XData > XDataNoConst; - fDataObjects[ typeid(XDataNoConst) ].reset( ptr ); // take ownership of ptr + DoSet( IndexableData::Create( ptr ) ); return; } template< typename XData > void DataFrame::Set( std::unique_ptr< XData >&& ptr ) { + // Note: takes ownership of object pointed to by ptr typedef std::remove_const_t< XData > XDataNoConst; - fDataObjects[ typeid(XDataNoConst) ] = std::move(ptr); // take ownership of ptr + DoSet( IndexableData::Create( std::move(ptr) ) ); return; } template< typename XData > void DataFrame::Set( const XData& obj ) { + // Note: makes a copy of obj and takes ownership of the copy typedef std::remove_const_t< XData > XDataNoConst; - fDataObjects[ typeid(XDataNoConst) ].reset( new XDataNoConst(obj) ); // make a copy of obj + DoSet( IndexableData::Create( obj ) ); return; } @@ -171,7 +298,7 @@ namespace Nymph void DataFrame::Remove() { typedef std::remove_const_t< XData > XDataNoConst; - fDataObjects.erase( typeid(XDataNoConst) ); + fDataMapByType.erase( typeid(XDataNoConst) ); return; } diff --git a/Python/Bindings/CMakeLists.txt b/Python/Bindings/CMakeLists.txt index c64a4e2..e141da1 100644 --- a/Python/Bindings/CMakeLists.txt +++ b/Python/Bindings/CMakeLists.txt @@ -16,6 +16,7 @@ include_directories( BEFORE set( PYBINDING_HEADERFILES Data/DataPybind.hh Processor/ProcessorPybind.hh + Processor/ProcessorToolboxPybind.hh Processor/ProcessorRegistrar.hh Processor/ProcessorToolboxPybind.hh Processor/PyProcCreatorPybind.hh diff --git a/Python/Bindings/Data/DataFramePybind.hh b/Python/Bindings/Data/DataFramePybind.hh new file mode 100644 index 0000000..3c210ec --- /dev/null +++ b/Python/Bindings/Data/DataFramePybind.hh @@ -0,0 +1,45 @@ +/* + * DataFramePybind.hh + * + * Created on: Jun 16, 2022 + * Author: N.S. Oblath + */ + +#ifndef PYTHON_BINDINGS_DATA_DATAPFRAMEPYBIND_HH_ +#define PYTHON_BINDINGS_DATA_DATAPFRAMEPYBIND_HH_ + +#include "DataFrame.hh" + +#include "DataPybind.hh" + +#include +#include + +namespace py = pybind11; + +namespace NymphPybind +{ + + void ExportDataFrame( py::module_& nymphData) + { + py::class_< Nymph::DataFrame, std::shared_ptr >( nymphData, "DataFrame" ) + .def( py::init< >() ) + .def( "empty", &Nymph::DataFrame::Empty ) + .def( "has", static_cast< bool (Nymph::DataFrame::*)(const std::string&) const >( &Nymph::DataFrame::Has ) ) + .def( "get", + static_cast< Nymph::Data& (Nymph::DataFrame::*)(const std::string&) >( &Nymph::DataFrame::Get ), + py::return_value_policy::reference ) + // NOTE: per pybind11 docs, python will not give up ownership of an object to the container, so we cannot bind the Set() function + // see, e.g. https://pybind11.readthedocs.io/en/stable/advanced/smart_ptrs.html, where this rule is mentioned + //.def( "set", + // static_cast< void (Nymph::DataFrame::*)(const std::string &, std::unique_ptr&&) >( &Nymph::DataFrame::Set ), + // py::keep_alive<1, 3>() ) + .def( "remove", static_cast< void (Nymph::DataFrame::*)(const std::string&) >( &Nymph::DataFrame::Remove ) ) + ; + + } + +} + +#endif /* PYTHON_BINDINGS_DATA_DATAPFRAMEPYBIND_HH_ */ + diff --git a/Python/Bindings/Data/DataPybind.hh b/Python/Bindings/Data/DataPybind.hh index 5d1b6f3..4b11ac9 100644 --- a/Python/Bindings/Data/DataPybind.hh +++ b/Python/Bindings/Data/DataPybind.hh @@ -25,11 +25,12 @@ namespace NymphPybind }; - void ExportData( py::module_& nymphData) + void ExportData( py::module_& nymphData ) { - py::class_< Nymph::Data, PyData, std::shared_ptr >(nymphData, "_Data") - .def(py::init< >()); - + py::class_< Nymph::Data, PyData >( nymphData, "_Data" ) + .def( py::init< >() ) + .def( "get_name", &Nymph::Data::GetName ) + ; } } diff --git a/Python/Bindings/NymphPybind.cc b/Python/Bindings/NymphPybind.cc index 2a1e721..7ff82e6 100644 --- a/Python/Bindings/NymphPybind.cc +++ b/Python/Bindings/NymphPybind.cc @@ -8,7 +8,7 @@ #include #include "Data/DataPybind.hh" - +#include "Data/DataFramePybind.hh" #include "Processor/ProcessorPybind.hh" #include "Processor/ProcessorToolboxPybind.hh" #include "Processor/PyProcCreatorPybind.hh" @@ -29,7 +29,8 @@ PYBIND11_MODULE(_nymph, nymphPackage) auto nymphData = nymphPackage.def_submodule("data", "Data module"); NymphPybind::ExportData(nymphData); - + NymphPybind::ExportDataFrame(nymphData); + auto nymphImplementation = nymphPackage.def_submodule("implementation", "Implementation module"); auto nymphProcessor = nymphPackage.def_submodule("processor", "Processor module"); @@ -39,6 +40,6 @@ PYBIND11_MODULE(_nymph, nymphPackage) NymphPybind::ExportSlot(nymphProcessor); NymphPybind::ExportSignalBase(nymphProcessor); NymphPybind::ExportSignal(nymphProcessor, "Data"); - + auto nymphUtility = nymphPackage.def_submodule("utility", "Utility module"); } diff --git a/Scarab b/Scarab index ba065f6..cacceed 160000 --- a/Scarab +++ b/Scarab @@ -1 +1 @@ -Subproject commit ba065f66706217eaa748d9ddf82dd32bb17e6abe +Subproject commit cacceed90ee7289ad87bf1715f11690fcf9d112e diff --git a/TestPyNymph.py b/TestPyNymph.py new file mode 100644 index 0000000..8d415f1 --- /dev/null +++ b/TestPyNymph.py @@ -0,0 +1,20 @@ + +import _nymph.processor as nproc + +class TestProcessor(nproc._Processor): + + def configure(self, param_node): + + param_dict = param_node.to_python() + self.x = param_dict['x'] + print('Configuring TestProcessor') + + def say_hi(self): + print('Hi, I\'m Test Processor!') + + +if __name__ == "__main__": + + reg = nproc.register_py_processor( '', 'TestProcessor', 'test-proc' ) + + proc = nproc.create_processor( "test-proc", "myproc" ) diff --git a/Testing/Cpp/TestData.cc b/Testing/Cpp/TestData.cc index e532c76..97ff36c 100644 --- a/Testing/Cpp/TestData.cc +++ b/Testing/Cpp/TestData.cc @@ -9,6 +9,10 @@ #include "catch.hpp" +#include "logger.hh" +#include "typename.hh" + +LOGGER( testlog, "TestData" ); TEST_CASE( "data", "[data]" ) { @@ -26,4 +30,6 @@ TEST_CASE( "data", "[data]" ) REQUIRE( tdData2.GetDValue1() == Approx( 0.0 ) ); REQUIRE( tdData2.GetDValue2() == Approx( 10.0 ) ); + LWARN( testlog, "Data1: " << scarab::type() ); + LWARN( testlog, "Data1: " << scarab::type() ); } diff --git a/Testing/Cpp/TestDataFrame.cc b/Testing/Cpp/TestDataFrame.cc index f2792f7..dde2ef4 100644 --- a/Testing/Cpp/TestDataFrame.cc +++ b/Testing/Cpp/TestDataFrame.cc @@ -25,56 +25,123 @@ TEST_CASE( "data_frame", "[data]" ) REQUIRE( tdfFrame.DataObjects().empty() ); REQUIRE( tdfFrame.Empty() ); - REQUIRE_FALSE( tdfFrame.Has< TestData1 >() ); - REQUIRE_FALSE( tdfFrame.Has< TestData2 >() ); - - // create a data object with Get() - TestData1& tdfData1 = tdfFrame.Get< TestData1 >(); - REQUIRE( tdfFrame.Has< TestData1 >() ); - // ensure TestData1 object was initialized correctly - REQUIRE( tdfData1.GetIValue1() == 0 ); - REQUIRE( tdfData1.GetIValue2() == 5 ); - - // make sure that we haven't made a copy of the data object - tdfData1.SetIValue1( 50 ); - REQUIRE( tdfData1.GetIValue1() == 50 ); - REQUIRE( tdfFrame.Get< TestData1 >().GetIValue1() == 50 ); - - // check const access - // currently has TestData1; does not have TestData2 - REQUIRE( tdfcFrame.Get< TestData1 >().GetIValue1() == 50 ); - REQUIRE_THROWS_AS( tdfcFrame.Get< TestData2 >(), DataFrameException ); - - // remove a data object - // currently has TestData1; does not have TestData2 - REQUIRE_NOTHROW( tdfFrame.Remove< TestData2 >() ); - REQUIRE_NOTHROW( tdfFrame.Remove< TestData1 >() ); - REQUIRE_FALSE( tdfFrame.Has< TestData1 >() ); - REQUIRE( tdfFrame.Empty() ); - - // set an object with a raw pointer - TestData2* tdfNewTestData2Ptr = new TestData2(); - REQUIRE_NOTHROW( tdfFrame.Set( tdfNewTestData2Ptr ) ); - REQUIRE( tdfFrame.Has< TestData2 >() ); - REQUIRE( (Data*)tdfNewTestData2Ptr == tdfFrame.DataObjects()[typeid(TestData2)].get() ); // we did not make a copy - - // set an object with a unique_ptr - std::unique_ptr< TestData1 > tdfNewTestData1Ptr( new TestData1 ); - REQUIRE_NOTHROW( tdfFrame.Set( std::move(tdfNewTestData1Ptr) ) ); - REQUIRE_FALSE( tdfNewTestData1Ptr ); - REQUIRE( tdfFrame.Has< TestData1 >() ); - REQUIRE( tdfFrame.Get< TestData1 >().GetIValue1() == 0 ); // check that fIValue1 has the default value; we'll overwrite the data object next, and verify we have the new value - - // verify multiple data types are present (TestData1 and TestData2 should be present at this point) - REQUIRE( tdfFrame.Has< TestData1, TestData2 >() ); - REQUIRE( tdfFrame.Has< TestData2, TestData1 >() ); - - // set an object with a copy of an object - TestData1 tdfAnotherTestData1; - tdfAnotherTestData1.SetIValue1( 4000 ); - REQUIRE_NOTHROW( tdfFrame.Set( tdfAnotherTestData1 ) ); - REQUIRE( tdfAnotherTestData1.GetIValue1() == 4000 ); - REQUIRE( tdfFrame.Get< TestData1 >().GetIValue1() == 4000 ); - REQUIRE_FALSE( (Data*)(&tdfAnotherTestData1) == tdfFrame.DataObjects()[typeid(TestData1)].get() ); // we made a copy + SECTION( "type-based indexing" ) + { + REQUIRE_FALSE( tdfFrame.Has< TestData1 >() ); + REQUIRE_FALSE( tdfFrame.Has< TestData2 >() ); + + // create a data object with Get() + TestData1& tdfData1 = tdfFrame.Get< TestData1 >(); + REQUIRE( tdfFrame.Has< TestData1 >() ); + // ensure TestData1 object was initialized correctly + REQUIRE( tdfData1.GetIValue1() == 0 ); + REQUIRE( tdfData1.GetIValue2() == 5 ); + + // make sure that we haven't made a copy of the data object + tdfData1.SetIValue1( 50 ); + REQUIRE( tdfData1.GetIValue1() == 50 ); + REQUIRE( tdfFrame.Get< TestData1 >().GetIValue1() == 50 ); + + // check const access + // currently has TestData1; does not have TestData2 + REQUIRE( tdfcFrame.Get< TestData1 >().GetIValue1() == 50 ); + REQUIRE_THROWS_AS( tdfcFrame.Get< TestData2 >(), DataFrameException ); + + // remove a data object + // currently has TestData1; does not have TestData2 + REQUIRE_NOTHROW( tdfFrame.Remove< TestData2 >() ); + REQUIRE_NOTHROW( tdfFrame.Remove< TestData1 >() ); + REQUIRE_FALSE( tdfFrame.Has< TestData1 >() ); + REQUIRE( tdfFrame.Empty() ); + + // set an object with a raw pointer + TestData2* tdfNewTestData2Ptr = new TestData2(); + REQUIRE_NOTHROW( tdfFrame.Set( tdfNewTestData2Ptr ) ); + REQUIRE( tdfFrame.Has< TestData2 >() ); + REQUIRE( tdfNewTestData2Ptr == &tdfFrame.Get() ); // we did not make a copy + + // set an object with a unique_ptr + std::unique_ptr< TestData1 > tdfNewTestData1Ptr = std::make_unique< TestData1 >(); + REQUIRE_NOTHROW( tdfFrame.Set( std::move(tdfNewTestData1Ptr) ) ); + REQUIRE_FALSE( tdfNewTestData1Ptr ); + REQUIRE( tdfFrame.Has< TestData1 >() ); + REQUIRE( tdfFrame.Get< TestData1 >().GetIValue1() == 0 ); // check that fIValue1 has the default value; we'll overwrite the data object next, and verify we have the new value + + // verify multiple data types are present (TestData1 and TestData2 should be present at this point) + REQUIRE( tdfFrame.Has< TestData1, TestData2 >() ); + REQUIRE( tdfFrame.Has< TestData2, TestData1 >() ); + + // set an object with a copy of an object + TestData1 tdfAnotherTestData1; + tdfAnotherTestData1.SetIValue1( 4000 ); + REQUIRE_NOTHROW( tdfFrame.Set( tdfAnotherTestData1 ) ); + REQUIRE( tdfAnotherTestData1.GetIValue1() == 4000 ); + REQUIRE( tdfFrame.Get< TestData1 >().GetIValue1() == 4000 ); + REQUIRE_FALSE( &tdfAnotherTestData1 == &tdfFrame.Get() ); // we made a copy + } + + SECTION( "string-based indexing" ) + { + std::string typeName1( scarab::type() ); + std::string typeName2( scarab::type() ); + + REQUIRE_FALSE( tdfFrame.Has( typeName1 ) ); + REQUIRE_FALSE( tdfFrame.Has( typeName2 ) ); + + // TODO: creating an object with Get() is not yet implemented; this is a workaround + std::unique_ptr< TestData1 > tdfTestData1PtrWorkaround = std::make_unique< TestData1 >(); + REQUIRE_NOTHROW( tdfFrame.Set( typeName1, std::move(tdfTestData1PtrWorkaround) ) ); + // create a data object with Get() + TestData1* tdfData1 = dynamic_cast< TestData1* >( &tdfFrame.Get( typeName1 ) ); + REQUIRE( tdfData1 ); + REQUIRE( tdfFrame.Has( typeName1 ) ); + // ensure TestData1 object was initialized correctly + REQUIRE( tdfData1->GetIValue1() == 0 ); + REQUIRE( tdfData1->GetIValue2() == 5 ); + + // make sure that we haven't made a copy of the data object + tdfData1->SetIValue1( 50 ); + REQUIRE( tdfData1->GetIValue1() == 50 ); + REQUIRE( dynamic_cast< TestData1* >( &tdfFrame.Get( typeName1 ) )->GetIValue1() == 50 ); + + // check const access + // currently has TestData1; does not have TestData2 + REQUIRE( dynamic_cast< const TestData1* >( &tdfcFrame.Get( typeName1 ) )->GetIValue1() == 50 ); + REQUIRE_THROWS_AS( tdfcFrame.Get( typeName2 ), DataFrameException ); + + // remove a data object + // currently has TestData1; does not have TestData2 + REQUIRE_NOTHROW( tdfFrame.Remove( typeName2 ) ); + REQUIRE_NOTHROW( tdfFrame.Remove( typeName1 ) ); + REQUIRE_FALSE( tdfFrame.Has( typeName1 ) ); + REQUIRE( tdfFrame.Empty() ); + + // set an object with a raw pointer + TestData2* tdfNewTestData2Ptr = new TestData2(); + REQUIRE_NOTHROW( tdfFrame.Set( typeName2, tdfNewTestData2Ptr ) ); + REQUIRE( tdfFrame.Has( typeName2 ) ); + REQUIRE( tdfNewTestData2Ptr == dynamic_cast< TestData2* >( &tdfFrame.Get( typeName2 ) ) ); // we did not make a copy + + // set an object with a unique_ptr + std::unique_ptr< TestData1 > tdfNewTestData1Ptr = std::make_unique< TestData1 >(); + REQUIRE_NOTHROW( tdfFrame.Set( typeName1, std::move(tdfNewTestData1Ptr) ) ); + REQUIRE_FALSE( tdfNewTestData1Ptr ); + REQUIRE( tdfFrame.Has( typeName1 ) ); + REQUIRE( dynamic_cast< TestData1* >( &tdfFrame.Get( typeName1 ) )->GetIValue1() == 0 ); // check that fIValue1 has the default value; we'll overwrite the data object next, and verify we have the new value + + // TODO: this hasn't been implemented yet + // verify multiple data types are present (TestData1 and TestData2 should be present at this point) + //REQUIRE( tdfFrame.Has( typeName1, typeName2 ) ); + //REQUIRE( tdfFrame.Has( typeName2, typeName1 ) ); + + // TODO: this hasn't been implemented + // set an object with a copy of an object + //TestData1 tdfAnotherTestData1; + //tdfAnotherTestData1.SetIValue1( 4000 ); + //REQUIRE_NOTHROW( tdfFrame.Set( typeName1, &tdfAnotherTestData1 ) ); + //REQUIRE( tdfAnotherTestData1.GetIValue1() == 4000 ); + //REQUIRE( dynamic_cast< TestData1* >( tdfFrame.Get( typeName1 ) )->GetIValue1() == 4000 ); + //REQUIRE_FALSE( &tdfAnotherTestData1 == dynamic_cast< TestData1* >( tdfFrame.Get( typeName1 ) ) ); // we made a copy + } } diff --git a/Testing/Python/Bindings/CMakeLists.txt b/Testing/Python/Bindings/CMakeLists.txt index 9917ac6..39e912e 100644 --- a/Testing/Python/Bindings/CMakeLists.txt +++ b/Testing/Python/Bindings/CMakeLists.txt @@ -10,6 +10,7 @@ include_directories( BEFORE # Python binding headers set( TESTING_PYBINDING_HEADERFILES + TestDataClassesPybind.hh ) # Python bindings diff --git a/Testing/Python/Bindings/NymphTestingPybind.cc b/Testing/Python/Bindings/NymphTestingPybind.cc index 3a7400e..bae25cd 100644 --- a/Testing/Python/Bindings/NymphTestingPybind.cc +++ b/Testing/Python/Bindings/NymphTestingPybind.cc @@ -7,6 +7,7 @@ #include +#include "TestDataClassesPybind.hh" namespace py = pybind11; @@ -16,4 +17,7 @@ PYBIND11_MODULE(_nymph_testing, nymphTestingPackage) nymphTestingPackage.doc() = "Nymph Testing package"; + auto nymphTesting = nymphTestingPackage.def_submodule("nymphTesting", "Nymph Testing module"); + NymphTestingPybind::ExportDataClasses(nymphTestingPackage); + } diff --git a/Testing/Python/Bindings/TestDataClassesPybind.hh b/Testing/Python/Bindings/TestDataClassesPybind.hh new file mode 100644 index 0000000..14bb017 --- /dev/null +++ b/Testing/Python/Bindings/TestDataClassesPybind.hh @@ -0,0 +1,35 @@ +/* + * TestDataClassesPybind.hh + * + * Created on: Jul 2, 2022 + * Author: N.S. Oblath + */ + +#ifndef PYTHON_BINDINGS_TESTING_TESTDATACLASSESPYBIND_HH_ +#define PYTHON_BINDINGS_TESTING_TESTDATACLASSESPYBIND_HH_ + +#include "TestDataClasses.hh" + +#include +#include + +namespace py = pybind11; + +namespace NymphTestingPybind +{ + + void ExportDataClasses( py::module_& nymphTesting) + { + py::class_< NymphTesting::TestData1 >( nymphTesting, "TestData1" ) + .def( py::init< >() ) + ; + + py::class_< NymphTesting::TestData2 >( nymphTesting, "TestData2" ) + .def( py::init< >() ) + ; + } + +} + +#endif /* PYTHON_BINDINGS_TESTING_TESTDATACLASSESPYBIND_HH_ */ + diff --git a/Testing/Python/testdataframe.py b/Testing/Python/testdataframe.py new file mode 100644 index 0000000..530b80b --- /dev/null +++ b/Testing/Python/testdataframe.py @@ -0,0 +1,50 @@ + +""" + testdataframe.py + + Created on: Sep 10, 2023 + Author: N. S. Oblath +""" +import unittest + +import _nymph + +'''Data class with integer variables''' +class TestData1(_nymph.data._Data): + + def __init__(self, test_var1=0, test_var2=5): + super().__init__() + self.test_var1 = test_var1 + self.test_var2 = test_var2 + +'''Data class with floating point variables''' +class TestData2(_nymph.data._Data): + + def __init__(self, test_var1=0.0, test_var2=10.0): + super().__init__() + self.test_var1 = test_var1 + self.test_var2 = test_var2 + +# The way to access member variables need to be modified if getters/setters are used in the python version +class TestDataMethods(unittest.TestCase): + dataframe = _nymph.DataFrame() + + + '''Testing of integer type data''' + data1=TestData1() + def test_idata_assignment(self): + self.assertEqual(self.data1.test_var1,0) + self.assertEqual(self.data1.test_var2,5) + + '''Testing of floating point type data''' + data2=TestData2() + def test_fdata_assignment(self): + self.assertAlmostEqual(self.data2.test_var1,0.0) + self.assertAlmostEqual(self.data2.test_var2,10.0) + + #Change value and test + self.data2.test_var2=20.0 + self.assertAlmostEqual(self.data2.test_var2,20.0) + +if __name__ == '__main__': + unittest.main() diff --git a/Testing/Python/testprocessortoolbox.py b/Testing/Python/testprocessortoolbox.py index 643ab14..7fe1bfe 100644 --- a/Testing/Python/testprocessortoolbox.py +++ b/Testing/Python/testprocessortoolbox.py @@ -11,11 +11,12 @@ class TestDataMethods(unittest.TestCase): - tb = _nymph.processor.ProcessorToolbox() + def setUp(self): + self.tb = _nymph.processor.ProcessorToolbox() - procName1 = 'testproc-1' # not a registered processor - procName2 = 'testproc-2' # not a registered processor - procName3 = 'test-proc' # a registered processor + self.procName1 = 'testproc-1' # not a registered processor + self.procName2 = 'testproc-2' # not a registered processor + self.procName3 = 'test-proc' # a registered processor '''Testing Processor handling''' def test_add_remove_processors(self):