Skip to content

Commit

Permalink
Merge pull request #5967 from johnhaddon/contextTrackerFor1.4
Browse files Browse the repository at this point in the history
ContextTracker and EditScopePlugValueWidget changes for 1.4.x
  • Loading branch information
johnhaddon authored Jul 24, 2024
2 parents 58afc72 + 59b94e0 commit 1d753b1
Show file tree
Hide file tree
Showing 12 changed files with 2,060 additions and 65 deletions.
7 changes: 7 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ Improvements

- LightEditor : Values of inherited attributes are now displayed in the Light Editor. These are presented as dimmed "fallback" values.
- LightEditor, RenderPassEditor : Fallback values shown in the history window are displayed with the same dimmed text colour used for fallback values in editor columns.
- EditScope : Filtered the EditScope menu to show only nodes that are active in the relevant context.

Fixes
-----

- LightEditor, RenderPassEditor : Added missing icon representing use of the `CreateIfMissing` tweak mode in the history window.

API
---

- ContextTracker : Added a new class that determines what contexts nodes are evaluated in relative to the focus node. This allows UI components to provide improved context-sensitive feedback to the user.
- Loop : Added `previousIteration()` method.

1.4.9.0 (relative to 1.4.8.0)
=======

Expand Down
5 changes: 5 additions & 0 deletions include/Gaffer/Loop.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ class GAFFER_API Loop : public ComputeNode
/// the next iteration of the loop (relative to the current context).
ContextPtr nextIterationContext() const;

/// Returns the input plug and context that form the previous iteration of the loop
/// with respect to the `output` plug and the current context. Returns `{ nullptr, nullptr }`
/// if there is no such iteration.
std::pair<const ValuePlug *, ContextPtr> previousIteration( const ValuePlug *output ) const;

void affects( const Plug *input, DependencyNode::AffectedPlugsContainer &outputs ) const override;

protected :
Expand Down
184 changes: 184 additions & 0 deletions include/GafferUI/ContextTracker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2024, Cinesite VFX Ltd. 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 John Haddon nor the names of
// any other contributors to this software 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.
//
//////////////////////////////////////////////////////////////////////////

#pragma once

#include "GafferUI/Export.h"

#include "Gaffer/Context.h"
#include "Gaffer/Set.h"
#include "Gaffer/Signals.h"

#include "IECore/Canceller.h"
#include "IECore/RefCounted.h"

#include <unordered_map>
#include <unordered_set>

namespace Gaffer
{

class BackgroundTask;
class DependencyNode;
class Plug;
class ScriptNode;
IE_CORE_FORWARDDECLARE( Node );
IE_CORE_FORWARDDECLARE( Context )

} // namespace Gaffer

namespace GafferUI
{

/// Utility class for UI components which display context-sensitive information
/// to users. This tracks which upstream nodes contribute to the result at a
/// particular target node, and also what context they should be evaluated in
/// with respect to that node.
class GAFFERUI_API ContextTracker final : public IECore::RefCounted, public Gaffer::Signals::Trackable
{

public :

/// Constructs an instance that will track the graph upstream of the
/// target `node`, taking into account what connections are active in
/// the target `context`.
ContextTracker( const Gaffer::NodePtr &node, const Gaffer::ContextPtr &context );
~ContextTracker() override;

IE_CORE_DECLAREMEMBERPTR( ContextTracker );

/// Shared instances
/// ================
///
/// Tracking the upstream contexts can involve significant computation,
/// so it is recommended that ContextTracker instances are shared
/// between UI components. The `aquire()` methods maintain a pool of
/// instances for this purpose. Acquisition and destruction of shared
/// instances is not threadsafe, and must always be done on the UI
/// thread.

/// Returns a shared instance for the target `node`. The node must
/// belong to a ScriptNode, so that `ScriptNode::context()` can be used
/// to provide the target context.
static Ptr acquire( const Gaffer::NodePtr &node );
/// Returns an shared instance that will automatically track the focus
/// node in the specified `script`.
static Ptr acquireForFocus( Gaffer::ScriptNode *script );

/// Target
/// ======

const Gaffer::Node *targetNode() const;
const Gaffer::Context *targetContext() const;

/// Update and signalling
/// =====================
///
/// Updates are performed asynchronously in background tasks so that
/// the UI is never blocked. Clients should connect to `changedSignal()`
/// to be notified when updates are complete.

/// Returns true if an update is in-progress, in which case queries will
/// return stale values.
bool updatePending() const;
using Signal = Gaffer::Signals::Signal<void ( ContextTracker & ), Gaffer::Signals::CatchingCombiner<void>>;
/// Signal emitted when the results of any queries have changed.
Signal &changedSignal();

/// Queries
/// =======
///
/// Queries return immediately so will not block the UI waiting for computation.
/// But while `updatePending()` is `true` they will return stale values.

/// Returns true if the specified plug or node contributes to the
/// evaluation of the target.
bool isTracked( const Gaffer::Plug *plug ) const;
bool isTracked( const Gaffer::Node *node ) const;

/// Returns the most suitable context for the UI to evaluate a plug or
/// node in. This will always return a valid context, even if the plug
/// or node has not been tracked.
Gaffer::ConstContextPtr context( const Gaffer::Plug *plug ) const;
Gaffer::ConstContextPtr context( const Gaffer::Node *node ) const;

/// If the node is tracked, returns the value of `node->enabledPlug()`
/// in `context( node )`. If the node is not tracked, returns `false`.
bool isEnabled( const Gaffer::DependencyNode *node ) const;

private :

void updateNode( const Gaffer::NodePtr &node );
void plugDirtied( const Gaffer::Plug *plug );
void contextChanged( IECore::InternedString variable );
void scheduleUpdate();
void updateInBackground();
const Gaffer::Context *findPlugContext( const Gaffer::Plug *plug ) const;

Gaffer::ConstNodePtr m_node;
Gaffer::ConstContextPtr m_context;
Gaffer::Signals::ScopedConnection m_plugDirtiedConnection;

Gaffer::Signals::ScopedConnection m_idleConnection;
std::unique_ptr<Gaffer::BackgroundTask> m_updateTask;
Signal m_changedSignal;

struct NodeData
{
Gaffer::ConstContextPtr context = nullptr;
bool dependencyNodeEnabled = false;
// If `true`, then all input plugs on the node are assumed to be
// active in the Node's context. This is just an optimisation that
// allows us to keep the size of `m_plugContexts` to a minimum.
bool allInputsActive = false;
};

using NodeContexts = std::unordered_map<Gaffer::ConstNodePtr, NodeData>;
NodeContexts m_nodeContexts;
using PlugContexts = std::unordered_map<Gaffer::ConstPlugPtr, Gaffer::ConstContextPtr>;
// Stores plug-specific contexts, which take precedence over `m_nodeContexts`.
PlugContexts m_plugContexts;

static void visit(
std::deque<std::pair<const Gaffer::Plug *, Gaffer::ConstContextPtr>> &toVisit,
NodeContexts &nodeContexts, PlugContexts &plugContexts, const IECore::Canceller *canceller
);

};

IE_CORE_DECLAREPTR( ContextTracker )

} // namespace GafferUI
20 changes: 20 additions & 0 deletions python/GafferTest/LoopTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,5 +355,25 @@ def testNextLoopIteration( self ) :
loop["indexVariable"].setValue( "" )
self.assertIsNone( loop.nextIterationContext() )

def testPreviousIteration( self ) :

loop = self.intLoop()
loop["next"].setInput( loop["previous"] )
loop["iterations"].setValue( 10 )

iteration = loop.previousIteration( loop["out"] )
self.assertTrue( iteration[0].isSame( loop["next"] ) )
self.assertEqual( iteration[1]["loop:index"], 9 )

for i in range( 0, 9 ) :
with iteration[1] :
iteration = loop.previousIteration( loop["previous"] )
self.assertTrue( iteration[0].isSame( loop["next"] ) )
self.assertEqual( iteration[1]["loop:index"], 8 - i )

iteration = loop.previousIteration( loop["previous"] )
self.assertTrue( iteration[0].isSame( loop["in"] ) )
self.assertNotIn( "loop:index", iteration[1] )

if __name__ == "__main__":
unittest.main()
Loading

0 comments on commit 1d753b1

Please sign in to comment.