diff --git a/python/GafferTest/SwitchTest.py b/python/GafferTest/SwitchTest.py index 76a6d0a5c31..9e14bdc7974 100644 --- a/python/GafferTest/SwitchTest.py +++ b/python/GafferTest/SwitchTest.py @@ -528,6 +528,40 @@ def testNestedPlugs( self ) : self.assertEqual( s.correspondingInput( s["out"]["value"]["x"] ), s["in"][0]["value"]["x"] ) self.assertEqual( s.activeInPlug( s["out"]["value"]["y"] ), s["in"][0]["value"]["y"] ) + def testInternalConnectionWithTypeConversionAndCanceller( self ) : + + # Make a switch with 2 inputs. + + switch = Gaffer.Switch() + switch.setup( Gaffer.IntPlug() ) + switch["in"][0].setInput( self.intPlug( 0 ) ) + switch["in"][1].setInput( self.intPlug( 1 ) ) + + # Drive the index with a BoolPlug. This means that `indexPlug()->getValue()` + # will perform a type conversion using `ValuePlug::setFrom()`, performed + # inside a `Process` which will check for cancellation in its constructor. + + boolPlug = Gaffer.BoolPlug() + switch["index"].setInput( boolPlug ) + + # Change the index by setting the value of `boolPlug`, but do this in a + # Context in which cancellation has been requested. This models a bizarre + # condition in which garbage collection destroys a `GafferImage.Shape` node + # from within a cancelled background task, and `Switch::plugInputChanged()` + # is called as the Shape is destroyed (Shape nodes have a bool->index connection + # internally). + # + # We do not want this to throw `IECore::Cancelled` because the graph authoring + # API (here, `setValue()`) is not context-sensitive, so it would be surprising + # if it considered the canceller. + + canceller = IECore.Canceller() + canceller.cancel() + with Gaffer.Context( Gaffer.Context(), canceller ) : + for index in ( 0, 1 ) : + boolPlug.setValue( index ) + self.assertTrue( switch["out"].getInput().isSame( switch["in"][index] ) ) + def setUp( self ) : GafferTest.TestCase.setUp( self ) diff --git a/src/Gaffer/Switch.cpp b/src/Gaffer/Switch.cpp index 7df75bff5f9..f2d874e5c13 100644 --- a/src/Gaffer/Switch.cpp +++ b/src/Gaffer/Switch.cpp @@ -55,6 +55,8 @@ namespace const IECore::InternedString g_inPlugsName( "in" ); const IECore::InternedString g_outPlugName( "out" ); +ConstContextPtr g_defaultContext = new Context; + } // namespace GAFFER_NODE_DEFINE_TYPE( Switch ); @@ -423,6 +425,12 @@ void Switch::updateInternalConnection() return; } - Plug *in = const_cast( oppositePlug( out, Context::current() ) ); + // The context is irrelevant since we've already checked that `indexPlug()` + // and `enabledPlug()` aren't computed. But we need to pass _something_ non-null + // because that is how we tell `oppositePlug()` we want it to take the index + // into account. And we need to scope this default context too - see + // `SwitchTest.testInternalConnectionWithTypeConversionAndCanceller()`. + Context::Scope scope( g_defaultContext.get() ); + Plug *in = const_cast( oppositePlug( out, g_defaultContext.get() ) ); out->setInput( in ); }