From 23d31188c6a0feac2c4181c2ceaaac9a5b50dbac Mon Sep 17 00:00:00 2001 From: John Haddon Date: Wed, 30 Oct 2024 18:11:39 +0000 Subject: [PATCH] Metadata : Add `targetsWithMetadata()` function This is equivalent to `nodesWithMetadata()` and `plugsWithMetadata()`, but for string targets. Ths also switched the internal container to maintain order of insertion so that we can return targets in the order they were first created. --- Changes.md | 1 + include/Gaffer/Metadata.h | 4 ++ python/GafferTest/MetadataTest.py | 17 ++++++++ src/Gaffer/Metadata.cpp | 62 ++++++++++++++++++++++++---- src/GafferModule/MetadataBinding.cpp | 18 ++++++++ 5 files changed, 93 insertions(+), 9 deletions(-) diff --git a/Changes.md b/Changes.md index c8e850cf9f0..b7c1a51a6aa 100644 --- a/Changes.md +++ b/Changes.md @@ -19,6 +19,7 @@ API --- - RenderPassEditor : Added optional `index` argument to `registerOption()` and `registerColumn()`. This can be used to specify the column's position in the UI. +- Metadata : Added `targetsWithMetadata()` function, returning all the string targets which match a pattern and have a specific metadata key. 1.5.3.0 (relative to 1.5.2.0) ======= diff --git a/include/Gaffer/Metadata.h b/include/Gaffer/Metadata.h index 4aed04039f8..b89e6613047 100644 --- a/include/Gaffer/Metadata.h +++ b/include/Gaffer/Metadata.h @@ -139,6 +139,10 @@ class GAFFER_API Metadata /// Utilities /// ========= + /// Returns the names of all matching string targets with the specified + /// metadata key. + static std::vector targetsWithMetadata( const IECore::StringAlgo::MatchPattern &targetPattern, IECore::InternedString key ); + /// Lists all node descendants of "root" with the specified metadata key. /// If instanceOnly is true the search is restricted to instance metadata. static std::vector nodesWithMetadata( GraphComponent *root, IECore::InternedString key, bool instanceOnly = false ); diff --git a/python/GafferTest/MetadataTest.py b/python/GafferTest/MetadataTest.py index fba20a107f5..c13162a9f21 100644 --- a/python/GafferTest/MetadataTest.py +++ b/python/GafferTest/MetadataTest.py @@ -1356,6 +1356,23 @@ def changed( node, key, reason ) : Gaffer.Metadata.registerValue( node, "test", 2 ) self.assertEqual( Gaffer.Metadata.value( node, "test" ), 1 ) + def testTargetsWithMetadata( self ) : + + for target, key in [ + [ "target1", "k1" ], + [ "target1", "k2" ], + [ "target2", "k2" ], + [ "target2", "k3" ], + [ "target3", "k4" ], + [ "targetA", "k1" ], + ] : + Gaffer.Metadata.registerValue( target, key, "test" ) + self.addCleanup( Gaffer.Metadata.deregisterValue, target, key ) + + self.assertEqual( Gaffer.Metadata.targetsWithMetadata( "target[1-3]", "k2" ), [ "target1", "target2" ] ) + self.assertEqual( Gaffer.Metadata.targetsWithMetadata( "target*", "k1" ), [ "target1", "targetA" ] ) + self.assertEqual( Gaffer.Metadata.targetsWithMetadata( "*", "k3" ), [ "target2" ] ) + def tearDown( self ) : GafferTest.TestCase.tearDown( self ) diff --git a/src/Gaffer/Metadata.cpp b/src/Gaffer/Metadata.cpp index fdd768b3521..1b070aea4ef 100644 --- a/src/Gaffer/Metadata.cpp +++ b/src/Gaffer/Metadata.cpp @@ -217,7 +217,17 @@ using Values = multi_index::multi_index_container< > >; -using MetadataMap = std::map; +using NamedValues = std::pair; + +using MetadataMap = multi_index::multi_index_container< + NamedValues, + multi_index::indexed_by< + multi_index::ordered_unique< + multi_index::member + >, + multi_index::sequenced<> + > +>; MetadataMap &metadataMap() { @@ -481,12 +491,23 @@ void Metadata::registerValue( IECore::InternedString target, IECore::InternedStr void Metadata::registerValue( IECore::InternedString target, IECore::InternedString key, ValueFunction value ) { - NamedValue namedValue( key, value ); - auto &m = metadataMap()[target]; - auto i = m.insert( namedValue ); - if( !i.second ) + auto &targetMap = metadataMap(); + + auto targetIt = targetMap.find( target ); + if( targetIt == targetMap.end() ) + { + targetIt = targetMap.insert( NamedValues( target, Values() ) ).first; + } + + // Cast is safe because we don't use `second` as a key in the `multi_index_container`, + // so we can modify it without affecting indexing. + Values &values = const_cast( targetIt->second ); + + const NamedValue namedValue( key, value ); + auto keyIt = values.insert( namedValue ); + if( !keyIt.second ) { - m.replace( i.first, namedValue ); + values.replace( keyIt.first, namedValue ); } valueChangedSignal()( target, key ); @@ -501,13 +522,17 @@ void Metadata::deregisterValue( IECore::InternedString target, IECore::InternedS return; } - auto vIt = mIt->second.find( key ); - if( vIt == mIt->second.end() ) + // Cast is safe because we don't use `second` as a key in the `multi_index_container`, + // so we can modify it without affecting indexing. + Values &values = const_cast( mIt->second ); + + auto vIt = values.find( key ); + if( vIt == values.end() ) { return; } - mIt->second.erase( vIt ); + values.erase( vIt ); valueChangedSignal()( target, key ); } @@ -544,6 +569,25 @@ IECore::ConstDataPtr Metadata::valueInternal( IECore::InternedString target, IEC return nullptr; } +std::vector Metadata::targetsWithMetadata( const IECore::StringAlgo::MatchPattern &targetPattern, IECore::InternedString key ) +{ + vector result; + const auto &orderedIndex = metadataMap().get<1>(); + for( const auto &[target, values] : orderedIndex ) + { + if( !StringAlgo::match( target.c_str(), targetPattern ) ) + { + continue; + } + if( values.find( key ) != values.end() ) + { + result.push_back( target ); + } + } + + return result; +} + void Metadata::registerValue( IECore::TypeId typeId, IECore::InternedString key, IECore::ConstDataPtr value ) { registerValue( typeId, key, [value]( const GraphComponent * ){ return value; } ); diff --git a/src/GafferModule/MetadataBinding.cpp b/src/GafferModule/MetadataBinding.cpp index 357bc23c5fc..ca0916d466f 100644 --- a/src/GafferModule/MetadataBinding.cpp +++ b/src/GafferModule/MetadataBinding.cpp @@ -323,6 +323,16 @@ list registeredGraphComponentValuesDeprecated( const GraphComponent *target, boo return keysToList( keys ); } +list targetsWithMetadataWrapper( const IECore::StringAlgo::MatchPattern &targetPattern, IECore::InternedString key ) +{ + std::vector targets; + { + IECorePython::ScopedGILRelease gilRelease; + targets = Metadata::targetsWithMetadata( targetPattern, key ); + } + return keysToList( targets ); +} + list plugsWithMetadata( GraphComponent *root, const std::string &key, bool instanceOnly ) { std::vector plugs = Metadata::plugsWithMetadata( root, key, instanceOnly ); @@ -452,6 +462,14 @@ void GafferModule::bindMetadata() .def( "plugValueChangedSignal", (Metadata::PlugValueChangedSignal &(*)( Gaffer::Node * ) )&Metadata::plugValueChangedSignal, return_value_policy() ) .staticmethod( "plugValueChangedSignal" ) + .def( "targetsWithMetadata", &targetsWithMetadataWrapper, + ( + boost::python::arg( "targetPattern" ), + boost::python::arg( "key" ) + ) + ) + .staticmethod( "targetsWithMetadata" ) + .def( "plugsWithMetadata", &plugsWithMetadata, ( boost::python::arg( "root" ),