Skip to content

Commit

Permalink
BackgroundTask : Warn if ScriptNode can't be found
Browse files Browse the repository at this point in the history
Not being able to find it can lead to very hard to diagnose bugs (see 67aea86), so we need to know any time that happens. This means updating a few tests that weren't using a ScriptNode at all. Technically, several of the tests could have been updated to pass `subject = None` instead, because they're not relying on graph edits causing cancellation. But I think it's better to update them to show the more common real-world use case instead.
  • Loading branch information
johnhaddon committed Jul 11, 2023
1 parent 4954232 commit 11b332f
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 99 deletions.
1 change: 1 addition & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ API
- Added support for creating a `PathMatcherDataPlug` from `PathMatcherData` in `createPlugFromData()`.
- Added support for getting `PathMatcherData` from a `PathMatcherDataPlug` in `getValueAsData()`.
- EditScopeAlgo : Added support for editing options.
- BackgroundTask : A warning is now emitted if the `subject` can not be associated with a ScriptNode for cancellation purposes.

1.2.8.0 (relative to 1.2.7.0)
=======
Expand Down
15 changes: 8 additions & 7 deletions python/GafferImageTest/MedianTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,14 @@ def testDriverChannel( self ) :

def testCancellation( self ) :

c = GafferImage.Constant()
script = Gaffer.ScriptNode()
script["c"] = GafferImage.Constant()

m = GafferImage.Median()
m["in"].setInput( c["out"] )
m["radius"].setValue( imath.V2i( 2000 ) )
script["m"] = GafferImage.Median()
script["m"]["in"].setInput( script["c"]["out"] )
script["m"]["radius"].setValue( imath.V2i( 2000 ) )

bt = Gaffer.ParallelAlgo.callOnBackgroundThread( m["out"], lambda : GafferImageTest.processTiles( m["out"] ) )
bt = Gaffer.ParallelAlgo.callOnBackgroundThread( script["m"]["out"], lambda : GafferImageTest.processTiles( script["m"]["out"] ) )
# Give background tasks time to get into full swing
time.sleep( 0.1 )

Expand All @@ -163,9 +164,9 @@ def testCancellation( self ) :

# Check that we can do the same when using a master
# channel.
m["masterChannel"].setValue( "R" )
script["m"]["masterChannel"].setValue( "R" )

bt = Gaffer.ParallelAlgo.callOnBackgroundThread( m["out"], lambda : GafferImageTest.processTiles( m["out"] ) )
bt = Gaffer.ParallelAlgo.callOnBackgroundThread( script["m"]["out"], lambda : GafferImageTest.processTiles( script["m"]["out"] ) )
time.sleep( 0.1 )

t = time.time()
Expand Down
16 changes: 9 additions & 7 deletions python/GafferImageTest/ResampleTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,15 @@ def testExpandDataWindow( self ) :

def testCancellation( self ) :

c = GafferImage.Constant()
script = Gaffer.ScriptNode()

r = GafferImage.Resample()
r["in"].setInput( c["out"] )
r["filterScale"].setValue( imath.V2f( 2000 ) )
script["c"] = GafferImage.Constant()

script["r"] = GafferImage.Resample()
script["r"]["in"].setInput( script["c"]["out"] )
script["r"]["filterScale"].setValue( imath.V2f( 2000 ) )

bt = Gaffer.ParallelAlgo.callOnBackgroundThread( r["out"], lambda : GafferImageTest.processTiles( r["out"] ) )
bt = Gaffer.ParallelAlgo.callOnBackgroundThread( script["r"]["out"], lambda : GafferImageTest.processTiles( script["r"]["out"] ) )
# Give background tasks time to get into full swing
time.sleep( 0.1 )

Expand All @@ -205,9 +207,9 @@ def testCancellation( self ) :
self.assertLess( time.time() - t, acceptableCancellationDelay )

# Check that we can do the same when using a non-separable filter
r["filter"].setValue( "disk" )
script["r"]["filter"].setValue( "disk" )

bt = Gaffer.ParallelAlgo.callOnBackgroundThread( r["out"], lambda : GafferImageTest.processTiles( r["out"] ) )
bt = Gaffer.ParallelAlgo.callOnBackgroundThread( script["r"]["out"], lambda : GafferImageTest.processTiles( script["r"]["out"] ) )
time.sleep( 0.1 )

t = time.time()
Expand Down
28 changes: 15 additions & 13 deletions python/GafferSceneTest/RenderControllerTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,21 +844,23 @@ def testIDs( self ) :

def testProgressCallback( self ) :

sphere = GafferScene.Sphere()
group = GafferScene.Group()
group["in"][0].setInput( sphere["out"] )
group["in"][1].setInput( sphere["out"] )
group["in"][2].setInput( sphere["out"] )
script = Gaffer.ScriptNode()

allFilter = GafferScene.PathFilter()
allFilter["paths"].setValue( IECore.StringVectorData( [ "/*", "/*/*" ] ) )
script["sphere"] = GafferScene.Sphere()
script["group"] = GafferScene.Group()
script["group"]["in"][0].setInput( script["sphere"]["out"] )
script["group"]["in"][1].setInput( script["sphere"]["out"] )
script["group"]["in"][2].setInput( script["sphere"]["out"] )

script["allFilter"] = GafferScene.PathFilter()
script["allFilter"]["paths"].setValue( IECore.StringVectorData( [ "/*", "/*/*" ] ) )

transform = GafferScene.Transform()
transform["in"].setInput( group["out"] )
transform["filter"].setInput( allFilter["out"] )
script["transform"] = GafferScene.Transform()
script["transform"]["in"].setInput( script["group"]["out"] )
script["transform"]["filter"].setInput( script["allFilter"]["out"] )

renderer = GafferScene.Private.IECoreScenePreview.CapturingRenderer()
controller = GafferScene.RenderController( transform["out"], Gaffer.Context(), renderer )
controller = GafferScene.RenderController( script["transform"]["out"], Gaffer.Context(), renderer )
controller.setMinimumExpansionDepth( 2 )

statuses = []
Expand All @@ -882,15 +884,15 @@ def callback( status ) :
# path we requested, and it's ancestors (not including the root, because
# its transform hasn't changed).
del statuses[:]
transform["transform"]["translate"]["x"].setValue( 1 )
script["transform"]["transform"]["translate"]["x"].setValue( 1 )
controller.updateMatchingPaths( IECore.PathMatcher( [ "/group/sphere" ] ), callback )
self.assertEqual( statuses, [ Status.Running ] * 2 + [ Status.Completed ] )

# Move everything again and do a background update with a priority path.
# We expect updates to all locations (except the root, because its transform
# hasn't changed).
del statuses[:]
transform["transform"]["translate"]["x"].setValue( 2 )
script["transform"]["transform"]["translate"]["x"].setValue( 2 )
task = controller.updateInBackground( callback, priorityPaths = IECore.PathMatcher( [ "/group/sphere" ] ) )
task.wait()
self.assertEqual( statuses, [ Status.Running ] * 4 + [ Status.Completed ] )
Expand Down
28 changes: 15 additions & 13 deletions python/GafferSceneUITest/CropWindowToolTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ class CropWindowToolTest( GafferUITest.TestCase ) :

def testSceneViewStatus( self ) :

camera = GafferScene.Camera()
script = Gaffer.ScriptNode()

script["camera"] = GafferScene.Camera()

view = GafferUI.View.create( camera["out"] )
view = GafferUI.View.create( script["camera"]["out"] )

tool = GafferSceneUI.CropWindowTool( view )
tool["active"].setValue( True )
Expand All @@ -75,28 +77,28 @@ def testSceneViewStatus( self ) :

# Editable

options = GafferScene.StandardOptions()
options["in"].setInput( camera["out"] )
view["in"].setInput( options["out"] )
script["options"] = GafferScene.StandardOptions()
script["options"]["in"].setInput( script["camera"]["out"] )
view["in"].setInput( script["options"]["out"] )

self.waitForIdle( 1000 )
self.assertEqual( tool.status(), "Info: Editing <b>StandardOptions.options.renderCropWindow.value</b>" )
self.assertEqual( tool.status(), "Info: Editing <b>options.options.renderCropWindow.value</b>" )

# Locked value plug

Gaffer.MetadataAlgo.setReadOnly( options["options"]["renderCropWindow"]["value"], True )
Gaffer.MetadataAlgo.setReadOnly( script["options"]["options"]["renderCropWindow"]["value"], True )

self.waitForIdle( 1000 )
self.assertEqual( tool.status(), "Warning: <b>StandardOptions.options.renderCropWindow.value</b> is locked" )
self.assertEqual( tool.status(), "Warning: <b>options.options.renderCropWindow.value</b> is locked" )

# Locked/off enabled plug

Gaffer.MetadataAlgo.setReadOnly( options["options"]["renderCropWindow"]["value"], False )
options["options"]["renderCropWindow"]["enabled"].setValue( False )
Gaffer.MetadataAlgo.setReadOnly( options["options"]["renderCropWindow"]["enabled"], True )
Gaffer.MetadataAlgo.setReadOnly( script["options"]["options"]["renderCropWindow"]["value"], False )
script["options"]["options"]["renderCropWindow"]["enabled"].setValue( False )
Gaffer.MetadataAlgo.setReadOnly( script["options"]["options"]["renderCropWindow"]["enabled"], True )

self.waitForIdle( 1000 )
self.assertEqual( tool.status(), "Warning: <b>StandardOptions.options.renderCropWindow.value</b> isn't editable" )
self.assertEqual( tool.status(), "Warning: <b>options.options.renderCropWindow.value</b> isn't editable" )

# Check status across visible/invisible overlay transitions (this is
# really testing one of the gnarly parts of the status implementation
Expand All @@ -110,7 +112,7 @@ def testSceneViewStatus( self ) :
view["camera"]["lookThroughEnabled"].setValue( True )

self.waitForIdle( 1000 )
self.assertEqual( tool.status(), "Warning: <b>StandardOptions.options.renderCropWindow.value</b> isn't editable" )
self.assertEqual( tool.status(), "Warning: <b>options.options.renderCropWindow.value</b> isn't editable" )

def testImageViewStatus( self ) :

Expand Down
70 changes: 37 additions & 33 deletions python/GafferSceneUITest/SceneGadgetTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,23 +371,25 @@ def testExceptionsDuringCompute( self ) :

def testObjectsAtBox( self ) :

plane = GafferScene.Plane()
script = Gaffer.ScriptNode()

sphere = GafferScene.Sphere()
sphere["radius"].setValue( 0.25 )
script["plane"] = GafferScene.Plane()

instancer = GafferScene.Instancer()
instancer["in"].setInput( plane["out"] )
instancer["prototypes"].setInput( sphere["out"] )
instancer["parent"].setValue( "/plane" )
script["sphere"] = GafferScene.Sphere()
script["sphere"]["radius"].setValue( 0.25 )

subTree = GafferScene.SubTree()
subTree["in"].setInput( instancer["out"] )
subTree["root"].setValue( "/plane" )
script["instancer"] = GafferScene.Instancer()
script["instancer"]["in"].setInput( script["plane"]["out"] )
script["instancer"]["prototypes"].setInput( script["sphere"]["out"] )
script["instancer"]["parent"].setValue( "/plane" )

script["subTree"] = GafferScene.SubTree()
script["subTree"]["in"].setInput( script["instancer"]["out"] )
script["subTree"]["root"].setValue( "/plane" )

sg = GafferSceneUI.SceneGadget()
sg.setRenderer( self.renderer )
sg.setScene( subTree["out"] )
sg.setScene( script["subTree"]["out"] )
sg.setMinimumExpansionDepth( 100 )

with GafferUI.Window() as w :
Expand Down Expand Up @@ -436,21 +438,19 @@ def testObjectsAtBox( self ) :

def testObjectAtLine( self ) :

cubes = []
names = ( "left", "center", "right" )
for i in range( 3 ) :
script = Gaffer.ScriptNode()
script["group"] = GafferScene.Group()

for i, name in enumerate( [ "left", "center", "right" ] ) :
cube = GafferScene.Cube()
cube["transform"]["translate"].setValue( imath.V3f( ( i - 1 ) * 2.0, 0.0, -2.5 ) )
cube["name"].setValue( names[i] )
cubes.append( cube )

group = GafferScene.Group()
for i, cube in enumerate( cubes ) :
group["in"][i].setInput( cube["out"] )
cube["name"].setValue( name )
script.addChild( cube )
script["group"]["in"][i].setInput( cube["out"] )

sg = GafferSceneUI.SceneGadget()
sg.setRenderer( self.renderer )
sg.setScene( group["out"] )
sg.setScene( script["group"]["out"] )
sg.setMinimumExpansionDepth( 100 )

with GafferUI.Window() as w :
Expand Down Expand Up @@ -532,13 +532,15 @@ def testSetAndGetScene( self ) :

def testBoundOfUnexpandedEmptyChildren( self ) :

group1 = GafferScene.Group()
group2 = GafferScene.Group()
group2["in"][0].setInput( group1["out"] )
script = Gaffer.ScriptNode()

script["group1"] = GafferScene.Group()
script["group2"] = GafferScene.Group()
script["group2"]["in"][0].setInput( script["group1"]["out"] )

sg = GafferSceneUI.SceneGadget()
sg.setRenderer( self.renderer )
sg.setScene( group2["out"] )
sg.setScene( script["group2"]["out"] )

self.waitForRender( sg )
self.assertEqual( sg.bound(), imath.Box3f() )
Expand All @@ -563,18 +565,20 @@ def testSelectionMaskAccessors( self ) :

def testSelectionMask( self ) :

plane = GafferScene.Plane()
plane["dimensions"].setValue( imath.V2f( 10 ) )
plane["transform"]["translate"]["z"].setValue( 4 )
script = Gaffer.ScriptNode()

script["plane"] = GafferScene.Plane()
script["plane"]["dimensions"].setValue( imath.V2f( 10 ) )
script["plane"]["transform"]["translate"]["z"].setValue( 4 )

camera = GafferScene.Camera()
group = GafferScene.Group()
group["in"][0].setInput( plane["out"] )
group["in"][1].setInput( camera["out"] )
script["camera"] = GafferScene.Camera()
script["group"] = GafferScene.Group()
script["group"]["in"][0].setInput( script["plane"]["out"] )
script["group"]["in"][1].setInput( script["camera"]["out"] )

sg = GafferSceneUI.SceneGadget()
sg.setRenderer( self.renderer )
sg.setScene( group["out"] )
sg.setScene( script["group"]["out"] )
sg.setMinimumExpansionDepth( 100 )

with GafferUI.Window() as w :
Expand Down
13 changes: 7 additions & 6 deletions python/GafferTest/ValuePlugTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -953,13 +953,14 @@ def computeCachePolicy( self, output ) :
# in `LRUCachePolicy.TaskParallel.Handle.acquire`.
) :

node = InfiniteLoop( cachePolicy = cachePolicy )
script = Gaffer.ScriptNode()
script["node"] = InfiniteLoop( cachePolicy = cachePolicy )

# Launch a compute in the background, and wait for it to start.

with node.computeStartedCondition :
backgroundTask1 = Gaffer.ParallelAlgo.callOnBackgroundThread( node["out"], lambda : node["out"].getValue() )
node.computeStartedCondition.wait()
with script["node"].computeStartedCondition :
backgroundTask1 = Gaffer.ParallelAlgo.callOnBackgroundThread( script["node"]["out"], lambda : script["node"]["out"].getValue() )
script["node"].computeStartedCondition.wait()

# Launch a second compute in the background, wait for it to start, and
# then make sure we can cancel it even though the compute is already in
Expand All @@ -973,10 +974,10 @@ def getValueExpectingCancellation() :
startedCondition.notify()

with self.assertRaises( IECore.Cancelled ) :
node["out"].getValue()
script["node"]["out"].getValue()

with startedCondition :
backgroundTask2 = Gaffer.ParallelAlgo.callOnBackgroundThread( node["out"], getValueExpectingCancellation )
backgroundTask2 = Gaffer.ParallelAlgo.callOnBackgroundThread( script["node"]["out"], getValueExpectingCancellation )
startedCondition.wait()

backgroundTask2.cancelAndWait()
Expand Down
14 changes: 8 additions & 6 deletions python/GafferUITest/BoolPlugValueWidgetTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,18 +83,20 @@ def testInitialValue( self ) :

def testErrorHandling( self ) :

n = Gaffer.Node()
n["user"]["p"] = Gaffer.BoolPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )
script = Gaffer.ScriptNode()

script["n"] = Gaffer.Node()
script["n"]["user"]["p"] = Gaffer.BoolPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic )

w = GafferUI.BoolPlugValueWidget( n["user"]["p"] )
w = GafferUI.BoolPlugValueWidget( script["n"]["user"]["p"] )
self.assertFalse( w.boolWidget().getErrored() )

b = GafferTest.BadNode()
n["user"]["p"].setInput( b["out3"] )
script["b"] = GafferTest.BadNode()
script["n"]["user"]["p"].setInput( script["b"]["out3"] )
GafferUITest.PlugValueWidgetTest.waitForUpdate( w )
self.assertTrue( w.boolWidget().getErrored() )

n["user"]["p"].setInput( None )
script["n"]["user"]["p"].setInput( None )
GafferUITest.PlugValueWidgetTest.waitForUpdate( w )
self.assertFalse( w.boolWidget().getErrored() )

Expand Down
10 changes: 6 additions & 4 deletions python/GafferUITest/StringPlugValueWidgetTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,15 +141,17 @@ def testMixedValuesPreserved( self ) :

def testExceptionHandling( self ) :

# Compute for `n["p"]` will error because it's an output plug
# Compute for `script["n"]["p"]` will error because it's an output plug
# that ComputeNode knows nothing about.

n = Gaffer.ComputeNode()
n["p"] = Gaffer.StringPlug( direction = Gaffer.Plug.Direction.Out )
script = Gaffer.ScriptNode()

script["n"] = Gaffer.ComputeNode()
script["n"]["p"] = Gaffer.StringPlug( direction = Gaffer.Plug.Direction.Out )

# We want that to be reflected in the UI.

w = GafferUI.StringPlugValueWidget( n["p"] )
w = GafferUI.StringPlugValueWidget( script["n"]["p"] )
GafferUITest.PlugValueWidgetTest.waitForUpdate( w )
self.assertEqual( w.textWidget().getText(), "" )
self.assertTrue( w.textWidget().getErrored() )
Expand Down
Loading

0 comments on commit 11b332f

Please sign in to comment.