.. _scenegraph_overview: SceneGraph Overview =================== The SceneGraph is the scene description component of the :ref:`SceneHub`. The :kl-ref:`SceneGraph` is a generic container of scene objects (:kl-ref:`SGObject`), their properties (:kl-ref:`SGObjectProperty`) and object references (hierarchy). The :kl-ref:`SceneGraph` allows for dynamic definition and caching of data, and value generation (generators). Additionally, it provides an implementation of interfaces allowing its elements to be processed by the :ref:`sceneassembly` graphs. On top of the SceneGraph, the :ref:`scenegraphwrappers_extension` implements common higher-level object wrappers that define common scene types, such as :kl-ref:`SGDirectionalLight` or :kl-ref:`SGCamera`. The wrappers manage underlying scene graph objects' property sets, relationships and value generators (operators). The :kl-ref:`SceneGraph` includes a minimal set of functionality for building common scene graph structures and objects. It provides support for defining objects containers (:kl-ref:`SGObject`, their properties (:kl-ref:`SGObjectProperty`), their metadata, the references to other objects (hierarchy) and value generators (operators). It supports, too, property propagation and notifications (observers). The :kl-ref:`SceneGraph` includes object and interfaces for defining dynamic depencies between objects (operators). This includes a generic operator object (SGBaseOperator), a Canvas graph operator that binds to SceneGraph properties (SGCanvasOperator) and lower-level generator and observer interfaces. See :ref:`_sgoperators_overview` for more details. The :kl-ref:`SceneGraph` supports animated storage and value generation, and the user can control how it caches the animated frames and property values. See :ref:`sgcontexts_and_animation` for more details. The :kl-ref:`SceneGraph` contains all the scene data, such as objects, properties and references, and manages their storage globally for a more efficiency. Adding objects and properties to the SceneGraph can be done dynamically at runtime and its internal structure allows these operations to be very fast. For convenience, the SceneGraph pre-creates a 'root' :kl-ref:`SGObject`. The image below illustrates the structure of a SceneGraph example: .. image:: /images/SceneHub/SceneGraph.png The SceneGraph above presents a three-level hierarchy of :ref:`sgobject_overview`, where "root" is the parent of "Model1" and "Model2", and these are both parent of "Geometry1". Each object defines some :ref:`sgobjectproperty_overview`, with different characteristics: - some are local to the owner :ref:`sgobject_overview`, such as the "localTransform" - some are propagated to the child objects, such as "root.color" - some are dynamically computed by a generator, such as "globalTransform" and "globalBBox" (see :ref:`sggenerators_sgobservers_overview`) - some contain an :ref:`sgobjectreference_overview`, such as "root.Chil1" and "Model1.Model" The "Geometry1" object is owned by both "Model1" and "Model2", which implies the creation of two sub-instances. While the "Geometry1.localBBox" and "Geometry1.geometry" properties are shared by both sub-instances, the values of "Geometry1.globalTransform" and "Geometry1.color" depend on the contextual parent. See :ref:`sgobject_overview` for more details. The SceneGraph objects are associated with higher-level wrappers, which are of type "MyAlembicGroupWrapper", "MyAlembicInstanceWrapper" and "MyAlembicGeomWrapper" in the example above. The example shows, too, the object hierarchy (base classes) of these wrappers. See :ref:`sgobjectwrapper_overview` for more details. The example below builds a simplified version of the scene presented above, with only the 'color' properties and without the object wrappers: .. kl-example:: Simple scene hierarchy with property propagation: require SceneGraph; operator entry() { SceneGraph sg(); // Create a color property on the root, and propagate as default value SGObjectProperty rootColor = sg.root.getOrAddLocalProperty( "color", SGPropertyPropagation_default ); rootColor.setValue( Color( 1.0, 1.0, 1.0, 1.0 ) ); // Add Model1 and Mode2 childs, root is their mainOwner SGObject model1 = sg.root.createChild( "Child1" ); SGObject model2 = sg.root.createChild( "Child2" ); // Set a distinct color on model2, and propagate as default value SGObjectProperty model2Color = model2.getOrAddLocalProperty( "color", SGPropertyPropagation_default ); model2Color.setValue( Color( 0.0, 0.0, 0.0, 1.0 ) ); // Create Geometry1 as an unowned (independant) object that will be shared SGObject geom1 = sg.addObject(); geom1.setUserName( "Geometry1" ); // Add geom1 as a child of both model1 and model2 model1.addChild( geom1, "Model", false /*not main owner; shared*/ ); model2.addChild( geom1, "Model", false /*not main owner; shared*/ ); report( "Scene content, from root.\n" ); report( "(Main) = main object instance" ); report( "(Shared) = object has multiple owners" ); report( "(Base) = property defined on the base object instance" ); report( "(Local) = property defined on that object instance" ); report( "(Parent) = property inherited from parent\n" ); sg.reportContent( SceneGraphDumpOptions(), true ); // Access the color values from their absolute path // Note: using String paths is not recommended for performance critical tasks SGObject resolvedInstance; SGObjectProperty resolvedProperty; sg.getFromFullPath( "root/Child1/Model/color", resolvedInstance, resolvedProperty ); report( "\nValue for " + resolvedProperty.getFullPath() + ":" ); resolvedProperty.reportContent( SceneGraphDumpOptions() ); sg.getFromFullPath( "root/Child2/Model/color", resolvedInstance, resolvedProperty ); report( "\nValue for " + resolvedProperty.getFullPath() + ":" ); resolvedProperty.reportContent( SceneGraphDumpOptions() ); } The main components of a SceneGraph are presented below. .. _sgobject_overview: SGObject -------- The :kl-ref:`SGObject` represents a specific scene graph object (or 'node'), such as a camera. It mainly consists to a set of :kl-ref:`SGObjectProperty`, which might either be locally defined (owned) or inherited from parents (owner objects). The `SGObject` might be wrapped by a higher-level wrapper object (:kl-ref:`SGObjectWrapper`) which manages its properties and exposes them with user-friendly methods (eg: :kl-ref:`SGPointLight`). The SceneGraph allows that :kl-ref:`SGObject` are shared by multiple parents. Because properties might be inherited by parent objects, this implies that a same :kl-ref:`SGObject` might have different properties or property values depending on the contextual parent. The :kl-ref:`SGObject` transparently embeds that notion of parent (owner) context. - The :kl-ref:`SGObject` that represents the scene object in the context of no specific parent (or the unique mainOwner parent) is called the 'main' instance (:kl-ref:`SGObject.isMainInstance`). - The :kl-ref:`SGObject` that represents the :kl-ref:`SGObject` in the context of a specific parent is called a sub-instance. The main instance associated with a sub-instance can be retrieved with the :kl-ref:`SGObject.getBaseInstance` method. In the illustration above, "Geometry1" is the main object, while "root/Child1/model" and "root/Child2/model" are its two sub-instances. Additionally, the :kl-ref:`SGObject` transparently embeds the evaluation context, such as the frame at which its property values are accessed. See :kl-ref:`SGObject` for more details. .. note:: The :kl-ref:`SGObject` is a lightweight structure that contains a handle to internal data. Because it is performance-critical, it allows low memory fragmentation (centralized storage) and simple by-value copy. .. note:: Although an object might have multiple sub-instances, the SceneGraph works hard to optimize the memory usage by sharing as much as possible the data that is common to groups of sub-instances, such as the property set and values. .. _sgobjectproperty_overview: SGObjectProperty ---------------- The :kl-ref:`SGObjectProperty` contains a named property value owned by a :kl-ref:`SGObject`. Its value can be a simple type (such as Float32 and String), a simple structure (such as :kl-ref:`Mat44`), a KL Object or an object reference (see :ref:`sgobjectreference_overview`). The value of a :kl-ref:`SGObjectProperty` can be dynamically generated by attaching a :kl-ref:`SGPropertyGenerator`. The value can depend on the context such as the current frame (see :ref:`sgcontexts_and_animation`), and that context is transparently embedded within the :kl-ref:`SGObjectProperty` structure in order to reduce the need of passing explicit contexts. Because of property inheritance, the value storage container of a :kl-ref:`SGObjectProperty` can be shared by multiple objects. In such case, :kl-ref:`SGObjectProperty.getSGObject()` will differ from :kl-ref:`SGObjectProperty.getOwnerInstance`. Additionally, a property might be defined in the context of a specific parent (instance-specific), allowing it to have a different value depending on the parent. For example, a different "globalTransform" property can be defined for each sub-instances, in which case its value will be different depending on its parents (absolute path). The following property propagation modes are supported (specified when creating the property): - `SGPropertyPropagation_none`: the property is local to the :kl-ref:`SGObject` or its sub-instances - `SGPropertyPropagation_default`: the property is propagated to the owned children recursively. - If a child defines a property of the same name with `SGPropertyPropagation_none`, it will override it locally but the propagation of the default will continue to its own children. - A child with its own `SGPropertyPropagation_default` property of the same name will redefine the default for its own children. - `SGPropertyPropagation_override`: the property is propagated to the owned children recursively, even if these are locally defining a property of the same name, with any propagation mode. .. note:: The :kl-ref:`SGObjectProperty` is a lightweight structure that contains a handle to internal data. Because it is performance-critical, it allows low memory fragmentation (centralized storage) and simple by-value copy. .. note:: Properties are versioned, and any type or value changes will increment the version number. This allows for detecting value changes asynchronously. This version is attached to the property data container itself, and might not be sufficient for detecting changes in general: - If there is a context change (eg: animated value, frame changed), each contextual value might have its own version, which could be the same. - If there is an added or removed property, an object that inherited a property from a parent might suddenly inherit from another property (eg: was inheriting value from a parent, but is now overridden by a new local property of the same name) This is solve by the :kl-ref:`SGObjectPropertyWatch` which provides a coherent property version that will take into account the context or propagation changes observed from a specific object instance. See :kl-ref:`SGObjectProperty` for more details. .. _sgobjectreference_overview: Object references ----------------- A special value type for a :kl-ref:`SGObjectProperty` is an object reference or an object reference array (:kl-ref:`SGObjectProperty.setReferenceTarget`, :kl-ref:`SGObjectProperty.setAsReferenceArray`). A reference stores a link to the target :kl-ref:`SGObject`. This link has a :kl-ref:`SGReferenceType`, which defines the child is owned and if the properties are propagated. If the child is owned, then a child sub-instance will be created for the context of this parent. .. note:: For convenience, the `SceneGraph.childrenReferenceType` member defines an owner :kl-ref:`SGReferenceType` with property propagation, and the `SceneGraph.simpleReferenceType` member defines an non-owner :kl-ref:`SGReferenceType` with no property propagation. When getting the reference target object (:kl-ref:`SGObjectProperty.getReferenceTarget` or :kl-ref:`SGObjectProperty.getReferenceArrayTarget`) referring an owned child, the returned :kl-ref:`SGObject` will be in the context of this parent object. Calling child's :kl-ref:`SGObject.getOwnerInstance` will return this parent object, and calling :kl-ref:`SGObject.getOwnerReferenceProperty` will return this reference property. It is possible that an object is primarely owned by a unique parent, which is called the "mainOwner". The "mainOwner" then directly owns the main instance of the child object, which can only have a single "mainOwner" parent. The :kl-ref:`SGObjectProperty.setReferenceTarget` and :kl-ref:`SGObjectProperty.setAsReferenceArray` method have a `mainOwner` parameter for this purpose. When a child is uniquely owned, the deletion of the parent owner will cause the child to be deleted. .. _sgobjectwrapper_overview: SGBaseObjectWrapper ---------------- A :kl-ref:`SGBaseObjectWrapper` allows to associate a custom KL object to a :kl-ref:`SGObject` in order to provide higher-level functionality and behavior. For example, the :kl-ref:`SGBaseObjectWrapper` can create properties with easy accessor methods and associate generators with these. The :ref:`scenegraphwrappers_extension` defines various object wrappers. For example, the :kl-ref:`SGTransformed` creates a `localTransform` and a `localBBox` property, and generates the `globalBBox` based on the `localBBox` and the `globalTransform` upon request. .. _sgoperators_overview: Operators --------- The SceneGraph provides the ability to compute data only when it is required (pull/lazy evaluation of graph dependencies). This is accessible though lower-level interfaces (:ref:`sggenerators_sgobservers_overview`) or higher-level objects (:ref:`sgbaseoperator_overview`, :ref:`sgcanvasoperator_overview`). Also, it is possible to define background computations through :ref:`sggenerators_async_overview`. .. _sgbaseoperator_overview: SGBaseOperator ................ :kl-ref:`SGBaseOperator` provides a high-level operator base object which can define a data flow, dynamic relationship from source to target properties. Additionally, it provides services for storing operator-owned parameters (arguments), which can be stored on its own container or attached to an existing one (SGObject). A specialized object can then simply create its parameters (arguments), add input and output properties, and implement the :kl-ref:`SGBaseOperator.execute` method to perform the actual computation. The :kl-ref:`SGBaseOperator` can support multiple output properties and handles automatically the contextual (animated) state of these based on inputs and arguments; see :kl-ref:`SGBaseOperator` for more details. .. kl-example:: Simple multiply operator specializing SGBaseOperator: require SceneGraph; // MultiplyOperator: a simple operator that multiplies the source by a multiplier. // This simple implementation assumes a Float32 value. object MultiplyOperator : SGBaseOperator { UInt32 multiplierPropertyKey; UInt32 sourcePropertyKey; UInt32 targetPropertyKey; }; MultiplyOperator( SGObjectProperty source, SGObjectProperty target ) { // Base constructor // We create our own parameter container (SGObject) instead of attaching to an existing object this.initWithOwnedParameterContainer( "Multiply", source.SG ); // We cache the property keys for better performance // Set multiplier default value to 4 this.multiplierPropertyKey = this.getOrAddParameter( "multiplier", 4.0f ); this.sourcePropertyKey = this.addInputProperty( source ); this.targetPropertyKey = this.addOutputProperty( target ); } // We pass in a context: would be required if the value was animated (varies with time/frame for the context) MultiplyOperator.setMultiplier!( Float32 value, SGContext context ) { SGObjectProperty multiplier = this.getProperty( this.multiplierPropertyKey, context ); multiplier.setValue( value ); } Boolean MultiplyOperator.execute!( SGContext context ) { SGObjectProperty multiplier = this.getProperty( this.multiplierPropertyKey ); SGObjectProperty source = this.getProperty( this.sourcePropertyKey ); if( !source.isFloat32Value() ) { setError( this.getPrintName() + ": unexpected: source is not a Float32" ); return false; } SGObjectProperty target = this.getProperty( this.targetPropertyKey ); target.setValue( multiplier.getFloat32Value() * source.getFloat32Value() ); return true; } operator entry() { SceneGraph sg(); SGObjectProperty sourceProperty = sg.root.getOrAddLocalProperty( "source" ); sourceProperty.setValue( 5.0f ); SGObjectProperty targetProperty = sg.root.getOrAddLocalProperty( "target" ); MultiplyOperator multiplyOp( sourceProperty, targetProperty ); report( "Initial value: " + targetProperty.getFloat32Value() ); multiplyOp.setMultiplier( 10, SGContext() ); report( "New value: " + targetProperty.getFloat32Value() ); } .. _sgcanvasoperator_overview: SGCanvasOperator ................ The :kl-ref:`SGCanvasOperator` object is a specialization of :kl-ref:`SGBaseOperator` that binds a Canvas graph to a SGObject, or more precisely, Canvas graph arguments to :kl-ref:`SGObjectProperty`. The computation of the output properties will trigger Canvas graph's execution. Some inputs such as `time` and `frame` are mapped by default to the contextual time or frame. In order to choose the scene parameters to be bound, a default input and output SGObject can be provided, or properties can be set explicitly for more complex cases. .. kl-example:: Using a Canvas graph to drive SGObjectProperty: require SceneGraphWrappers; operator entry() { SceneGraph sg(); SGGeometry geometry = sg.addGeometry( null ); sg.root.addChild( geometry, "geom", true ); // Create a CanvasGeometry graph. Its "geometry" output parameter matches the existing property on the geometry and will bind to it. FilePath path = FilePath( "${FABRIC_SCENE_GRAPH_DIR}/Test/Exts/SceneGraphWrappers/CanvasGeometry.canvas" ).expandEnvVars(); SGCanvasOperator sgCanvasOp = sg.addCanvasOperator( path.string(), geometry.getWrapped(), true ); SGObject operatorParameters = sgCanvasOp.getParameterContainer(); SGObjectPropertyIterator iter = operatorParameters.getPropertyIterator(); SGObjectProperty prop; while( iter.getNext( prop ) ) { if( prop.getName() == "graphFilePath" ) continue;//don't print an absolute path... report( "Parameter: " + prop.getName() + " = " + prop.getValueString() ); } PolygonMesh mesh = geometry.getGeometry( SGContext() ); report( "Mesh poly count (nbSpheres = 5): " + mesh.polygonCount() ); prop = operatorParameters.getProperty("nbSpheres"); prop.setValue(3); mesh = geometry.getGeometry( SGContext() ); report( "Mesh poly count (nbSpheres = 3): " + mesh.polygonCount() ); } .. _sggenerators_sgobservers_overview: Generators and observers ........................ A :kl-ref:`SGObjectGenerator` or a :kl-ref:`SGObjectPropertyGenerator` enables to compute the value of a a :kl-ref:`SGObject` or a :kl-ref:`SGObjectProperty` when requested. The interface provides the target object or property, from contains at the same time the evaluation context (eg: for a specific frame if animated). This allows to implement data flow dependencies and dynamic computations in the SceneGraph, similarly to the :ref:`sgoperators_overview`, but using lower-level interfaces that allows for more optimizations, or defining dynamic behaviors directly within a SGObjectWrapper. The :kl-ref:`SGChangeObserver` interface allows for receiving direct notifications upon changes from the object instance or property of interest. The following observers are available: - :kl-ref:`SGObjectProperty.addObserver`: an observer for changes to a specific property. - :kl-ref:`SceneGraph.getByPropertyNameGlobalVersions`: a global observer for changes related to all property with a given name. - :kl-ref:`SceneGraph.addInstancesObserver`: a global observer for any added object instances (or sub-instances) or hierarchy changes. .. note:: The :kl-ref:`SGIncrementalObserver` object allows to efficiently cumulate scene graph changes as a compact and centralized `changes history`, allowing clients (such as :ref:`sceneassembly`) to update asynchronously. Only the required changes are kept (history scanned by all clients is flushed). The combination of generators and observers allow to implement a data flow graph (operators) that can link the various objects or properties, including `dirty` propagation. For example, the :kl-ref:`SWTransformed` uses these hooks to compute its global transform and global bounding box. The example below shows a simple operator implementation. .. note:: Currently, there is no higher-level operator object that is provided along with the SceneGraph, however it will be added in later releases. This will include support for visual DFGs (data flow graphs) within the SceneGraph. The following example builds a 2-level hierarchy where the "int" value is generated as parent's value + 1: .. kl-example:: Property operator using SGPropertyGenerator and SGChangeObserver: require SceneGraph; // Example of an operator written with lower-level services, as opposed // to specializing the SGBaseOperator class. object AddOneOperator : SGPropertyGenerator, SGChangeObserver { SGObjectProperty inputProperty; SGObjectProperty outputProperty; }; AddOneOperator( SGObjectProperty inputProperty, SGObjectProperty outputProperty ) { this.inputProperty = inputProperty; this.outputProperty = outputProperty; // Register ourselves as a generator observing inputProperty this.inputProperty.addGeneratorObserver( this, 0 ); // Set ourselves as the generator of outputProperty this.outputProperty.setGenerator( this, 0 ); } // SGPropertyGenerator implementation // Sets the target property as source property + 1 UInt8 AddOneOperator._generate!( io SGObjectProperty property, UInt32 userData ) { //NOTE: the property might have been requested for a specific frame, but // we want to output a static value: use the non-contextual version to indicate that SGObjectProperty staticProperty = property.getContextual(false); property.setValue( this.inputProperty.getUInt32Value() + 1 ); return SGPropertyGenerator_succeeded; } // SGPropertyGenerator implementation // Debug name used for tracing. String AddOneOperator._getGeneratorDebugName( SGObjectProperty property, UInt32 userData ) { return "AddOneOperator"; } // default implementation of SGPropertyGenerator Boolean AddOneOperator._isAsynchronous( io Boolean asynchPrepareOnly, UInt32 userData ) { return false; } // default implementation of SGPropertyGenerator UInt8 AddOneOperator._asynchPrepare!( SGObjectProperty property, UInt32 userData ) { return SGPropertyGenerator_succeeded; } // SGChangeObserver implementation // Triggered when the input property changes AddOneOperator._changed!(io SGObject instance, io SGObjectProperty property, UInt8 changeType, UInt32 userData) { this.outputProperty.setGeneratorDirty( changeType ); } // SGChangeObserver implementation: triggered when the SceneGraph destroys AddOneOperator._SGDestroying!() {} operator entry() { SceneGraph sg(); //Set rootInt initial value to 10 SGObjectProperty rootInt = sg.root.getOrAddLocalProperty( "int" ); rootInt.setValue( UInt32(10) ); //Create a child, whose int value depends on root's int value SGObject child = sg.root.createChild( "child" ); SGObjectProperty childInt = child.getOrAddLocalProperty( "int" ); AddOneOperator childOp( rootInt, childInt ); //Create a grandchild, whose int value depends on child's int value SGObject grandChild = sg.root.createChild( "grandchild" ); SGObjectProperty grandChildInt = grandChild.getOrAddLocalProperty( "int" ); AddOneOperator grandChildOp( childInt, grandChildInt ); //Enable debug tracing of generators sg.enableGeneratorsTracing(); report("Get grandChild value (PIdx = global property index):\n"); grandChildInt.getUInt32Value(); report("\nChanging the root value to 20:\n"); rootInt.setValue( UInt32(20) ); report("\nGet grandChild value:\n"); grandChildInt.getUInt32Value(); } .. _sggenerators_async_overview: Asynchronous evaluation ....................... Asynchronous evaluation is a :kl-ref:`SceneGraph` feature that allows some computations to be done in the background in a multithreaded fashion. However, it must be used with care and will provide performance gains only in specific situations. Asynchronous evaluation can be enabled from the :kl-ref:`SGPropertyGenerator` low-level interface, but can also be enabled for :kl-ref:`SGBaseOperator` (:kl-ref:`SGBaseOperator.isAsynchronous`) and for various higher-level scene wrappers (eg: :kl-ref:`SGGeometry.enableGeometryGenerator`). Since asynchronous evaluation requires careful implementations for both the generator and the SceneGraph client or user, it can be disabled globally at the SceneGraph level with :kl-ref:`SceneGraph.enableAsynchronousGenerators` to avoid issues or complexity. In order to provide some gains, asynchronous evaluation should be used in the following conditions: - The generator execution must be relatively long (not a simple matrix multiplication for example), otherwise the overhead will be more important than the gains - The generator execution should lock the graph only for a minor portion of its time, otherwise the UI thread will become unresponsive and other asynchronous generators will be blocked (no multithreading) - The generator should lock the graph if it does many changes to the SceneGraph, such that the SceneGraph is unlocked only once it is in a valid semantic state (eg: for drawing). Higher level mechanisms can be required for ensure this (eg: `SGObject.loading`). A perfect example for potential gains is to generate a Geometry which doesn't depend on other SceneGraph objects, but only depends on the "external world" (eg: Alembic loader). All generators can declare to be asynchronous (:kl-ref:`SGPropertyGenerator._isAsynchronous`). Requesting a value that depends on an asynchronous generator will automatically initiate the background computation process. However, the result will not be available immediately. The non-availability of the data should be tested with methods such as :kl-ref:`SGObjectProperty.waitingGenerator`. Asynchronous generators can be attached to the following entities: - :kl-ref:`SGObject` (:kl-ref:`SGObject.setGenerator`): when the SGObject is accessed, the :kl-ref:`SGObject.waitingGenerator` indicates that it has not finish to evaluate yet (background exection pending). - :kl-ref:`SGObjectProperty` (:kl-ref:`SGObjectProperty.setGenerator`): when SGObjectProperty data is requested (value, type, version), the :kl-ref:`SGObjectProperty.waitingGenerator` method indicates that it has not finish to evaluate yet (background exection pending). The :kl-ref:`SGObjectProperty.forceGenerate` method will wait for an asynchronous value to be ready, at the cost of performance (no gains from computing in backgound at that point). - :kl-ref:`SGObjectProperty` references (:kl-ref:`SGObjectProperty.setReferenceTargetGenerator`, :kl-ref:`SGObjectProperty.setReferenceArrayTargetGenerator`): when the reference target is accessed, the :kl-ref:`SGObjectProperty.isReferenceTargetWaitingGenerator` or :kl-ref:`SGObjectProperty.isReferenceArrayTargetWaitingGenerator` methods indicate that it has not finish to evaluate yet (background exection pending). The :kl-ref:`SGObjectProperty.forceGenerateReferenceTarget` or :kl-ref:`SGObjectProperty.forceGenerateReferenceArrayTarget` methods will wait for an asynchronous reference target to be ready, at the cost of performance (no gains from computing in backgound at that point). The asynchronous evaluation is implemented as one of the core mechanisms in the SceneGraph. When a value is in waiting state, it should be pulled again later to check if it is then available. One way of doing this is to have a timer in the UI thread which verifies if something changed in the SceneGraph (:kl-ref:`SceneGraph.getGlobalVersions`), as implemented by the sceneHub.py standalone. The :kl-ref:`SGInstanceTraverser` allows to traverse a scene's hierarchy, and is asynchronous evaluation-aware. An option of the SGInstanceTraverser allows it to asynchronously wait for hierarchy children to be generated in the background, and will resume its traversal once these are available. Also, it can optionally force the values to be generated, in which case it will synchronously block until these are finished. The :kl-ref:`SGLoadTraverser` specializes SGInstanceTraverser in order to load the scene asynchronously. The following illustrates how the low-level mechanism works. Note that this complexity is hidden if you use higher-level objects, such as :kl-ref:`SGBaseOperator` or wrappers such as :kl-ref:`SGGeometry`. Let say :kl-ref:`SGObjectProperty` A's generator depends on :kl-ref:`SGObjectProperty` B, and SGObjectProperty B's generator is asynchronous. The following steps are involved: - A.getValue() is called, its generator request B's value, which triggers a call to B's generator (since it depends on it), however since B's generator is asynchronous, it will be queued and B's value will not be up-to-date. - A's generator should actually call B.valuesAreContextual( isWaiting ) to know if the stored value will be contextual (animated) and to know at the same time if B is waiting for its generator. If B is waiting, then A's generator must return SGPropertyGenerator_waiting immediately to indicate that because of A is now also waiting on asynchronous computations. - Once B's generator returns, a SGObserverChange_generatedAsync notification will be triggered, and A's generator will receive it (since it depends on B, it must be registered as its observer - :kl-ref:`SGObserverChange._changed` ). A's generator must then relay the notification to its generated properties ( `A.setGeneratorDirty( changeType )` ), just like it does it anyway to propagate dirty states. This will remove A's waiting state. - Requesting A again will trigger its evaluation, which will now properly compute it since B's value is available. .. _sgcontexts_and_animation: Contexts and animation ------------------------ :kl-ref:`SceneGraph` contexts allow to control how the :kl-ref:`SceneGraph` data is accessed and stored, which enables to implement animation or simulation evaluation or caching. The context is transparently embedded with the :kl-ref:`SGObject` and :kl-ref:`SGObjectProperty`, and the :kl-ref:`SGContext` allows to access information about that context (accessible with :kl-ref:`SGObject.getContext` and :kl-ref:`SGObjectProperty.getContext`, respectively). .. _sgcontext_overview: SGContext ......... The context (:kl-ref:`SGContext`) is a central notion in the :kl-ref:`SceneGraph` design. When getting or setting a property value, the context allows the :kl-ref:`SceneGraph` to access the right data by specifying the animation time or frame to be used, and can include the active hierarchy path (for :kl-ref:`SGObject` instances shared by multiple parents). The :kl-ref:`SGContext` can represent an immutable set of parameters and the contextual instance (active hierarchy parent). A :kl-ref:`SGContext` associated to a specific set of parameters can be created with :kl-ref:`SceneGraph.getContext`. The :kl-ref:`SceneGraph.getFrameContext` and :kl-ref:`SceneGraph.getTimeContext` built-in helpers allow to easily create a context for a specific animation "time" and "frame". If two :kl-ref:`SGContext` are created for the same parameters, the same context will be returned (shared). The :kl-ref:`SGObject` and :kl-ref:`SGObjectProperty` transparently embed a :kl-ref:`SGContext`, which allows to access their values without worrying about the context. For example, the :kl-ref:`SGObject.getProperty` method will return a SGObjectProperty which embeds owner SGObject's context (contextual instance and parameters). When needed, specialized methods allow to get or change the context associated with a :kl-ref:`SGContext` (eg: :kl-ref:`SGObjectProperty.getContextual`). For a specific :kl-ref:`SGObject` or :kl-ref:`SGObjectProperty`, the associated :kl-ref:`SGContext` (:kl-ref:`SGObject.getContext` or :kl-ref:`SGObjectProperty.getContext`) embeds, too, the "contextual instance", which provides the context of the active parent (hierarchy path) for a shared object. The contextual instance is important since depending on the owner parent, a shared :kl-ref:`SGObject` might have different properties or property values (see :ref:`sgobjectproperty_overview`). However, for performance and other reasons, a :kl-ref:`SGObjectWrapper` is shared by all instances of a same base :kl-ref:`SGObject`, which is why the wrapper methods often require a :kl-ref:`SGContext` to be provided in order to precise the contextual instance. The :kl-ref:`SGObject.getWrapper` provides the right :kl-ref:`SGContext` to be used for preserving the SGObject's context. .. warning:: Contexts must be used with care: trying to access properties with a :kl-ref:`SGContext` that embeds another :kl-ref:`SGObject` instance will trigger an expection because the contextual instances will mismatch. Various methods exist for accessing or modifying the context of an object or property, for example: - :kl-ref:`SGObjectProperty.valuesAreContextual`: returns true if property's values depend on the frame context (see :ref:`sg_frame_storage`) - :kl-ref:`SGObjectProperty.getContextual`: returns a :kl-ref:`SGObjectProperty` representing the same property and contextual instance, but with other contextual parameters. In particular, calling `SGObjectProperty.getContextual(false)` returns the property with no specific context, in which case setting its value will define a static (non-animated) value. .. _sg_frame_parameters: Frame parameters ................ The :kl-ref:`SceneGraph` can handle and store animated, frame-specific property values. A 'frame' is a general term used by the :kl-ref:`SceneGraph` for referencing an animation-specific storage or, more generally, a contextual storage. The frame context can be embedded within a :kl-ref:`SGContext`, a :kl-ref:`SGObject` or a :kl-ref:`SGObjectProperty`, or even within the :kl-ref:`SceneGraph` itself though its support of a 'current context' ( :kl-ref:`SceneGraph.defineCurrentFrame` ). While a :kl-ref:`SGContext` can represent a set of arbitrary parameters, some of these parameters have the special status of being "frame parameters". For each combination of frame parameters, a :kl-ref:`SGObjectProperty` can define and store a different value, making it "animated" (function of the frame parameters). By default, the :kl-ref:`SceneGraph` defines the "time" and the "frame" context parameters as being "frame parameters", and defines built-in helpers for accessing these (:kl-ref:`SceneGraph.getCurrentFrame` , :kl-ref:`SceneGraph.getCurrentTime` , :kl-ref:`SGContext.getFrame` , :kl-ref:`SGContext.getTime` ). .. note:: The relationship between the frame and the time can be customized with :kl-ref:`SceneGraph.setupSimpleFrameToTimeConversion` or :kl-ref:`SceneGraph.setupFrameToTimeConversion` . If other frame parameters are required, these can be defined with :kl-ref:`SceneGraph.registerFrameParameter` . If a :kl-ref:`SGContext` has frame parameters among other ones, the :kl-ref:`SGContext.getFrameContext` method allows to efficiently retrieve an associated context which strictly contains these frame parameters. .. _sg_frame_storage: Per-frame storage or animation .............................. In the :kl-ref:`SceneGraph`, the terms "animated value", "per-frame value" or "contextual value" usually refer to the fact that a :kl-ref:`SGObjectProperty` value can depend on the contextual frame. Per-frame storage can be controlled at a global level (all :kl-ref:`SceneGraph` animated properties) or for individual properties. The contextual frame can be specified either by defining it at the SceneGraph level (default current frame), or can be embedded within the contexts of the :kl-ref:`SGObject` and the :kl-ref:`SGObjectProperty`. The :kl-ref:`SceneGraph.defineCurrentFrame` method sets the frame context to be used if none was specified. For example, calling `sg.defineCurrentContext( sg.getFrameContext( 2 ) );` will set the default frame to '2'. There are two ways to provide per-frame values for an :kl-ref:`SGObjectProperty`. The first method is to push the required per-frame values in advance by using the :kl-ref:`SGObjectProperty.enableAllFramesCaching` method. The :kl-ref:`SceneGraph` will then select the right stored value depending on the frame. The other way is to associate a :kl-ref:`SGPropertyGenerator` with a property (:kl-ref:`SGObjectProperty.setGenerator`). Generator's implementation of :kl-ref:`SGPropertyGenerator._generate` can then return a contextual value when requested. .. note:: In the :kl-ref:`SGPropertyGenerator._generate` function, if the value to be stored is always the same for all frames (static value), then the `setValue` method must be called on a non-contextual SGObjectProperty to tell the SceneGraph that the values is not context dependent. Such a :kl-ref:`SGObjectProperty` can be retrieved by calling `SGObjectProperty.getContextual( false )`. The following example demonstrates the definition of contextual property values using both methods: .. kl-example:: Setting per-frame SGObjectProperty values require SceneGraph; // This example shows a simple usage of SceneGraph per-frame (animation) // storage and property generation. // A simple generator outputting the current time from the frame index object TimeGenerator : SGBaseOperator{ UInt32 timePropertyKey; }; TimeGenerator( SGObjectProperty timeProp ) { // Call base object constructor this.initWithOwnedParameterContainer( "TimeGenerator", timeProp.SG ); this.timePropertyKey = this.addOutputProperty( timeProp ); this.setForceContextSpecific( true );//Indicate that we are depending on the context even if no contextual inputs } Boolean TimeGenerator.execute!( SGContext context ) { SGObjectProperty timeProperty = this.getProperty( this.timePropertyKey ); timeProperty.setValue( context.getTime() ); return true; } operator entry() { SceneGraph sg(); // Define the "frame to time" ratio to 2 frames per second, with no time offset sg.setupSimpleFrameToTimeConversion( 2, 0 ); // Add a "time" generated property to 'root' SGObjectProperty timeProp = sg.root.getOrAddLocalProperty( "time" ); TimeGenerator( timeProp );//Note: the property owns the generator so no need to keep a ref // Create a "weather" property with pre-cached per-frame values // (instead of being generated like the 'time' above) SGObjectProperty weatherProp = sg.root.getOrAddLocalProperty( "weather" ); weatherProp.enableAllFramesCaching( true ); // Pre-create the weather properties for frames 0, 1 and 2 SGObjectProperty weatherPropAtFrame = weatherProp.getContextual( sg.getFrameContext(0) ); weatherPropAtFrame.setValue( "sunny" ); weatherPropAtFrame = weatherProp.getContextual( sg.getFrameContext(1) ); weatherPropAtFrame.setValue( "cloudy" ); weatherPropAtFrame = weatherProp.getContextual( sg.getFrameContext(2) ); weatherPropAtFrame.setValue( "raining" ); // Print the values for frames 0..2 for( Size i = 0; i <= 2; ++i ) { // For simplicity, we will just change the "current frame" of the scene. sg.defineCurrentFrame( sg.getFrameContext(i) ); report( "Frame " + i + ": time = " + timeProp.getFloat64Value() + " weather = " + weatherProp.getStringValue() ); } } In order to provide a good control and performance relatively to contextual (animation) storage, the user must define the required storage at the scene level before requesting animated values. By default, the :kl-ref:`SceneGraph` only provides contextual (animation) storage for the defined current context ( :kl-ref:`SceneGraph.defineCurrentFrame` ). Trying to set or get the value of an animated property for another frame will cause an exception. If required, it is possible to enable parallel storage of multiple frames with the :kl-ref:`SceneGraph.pinFrameValues` method. For example, this can allow to define a sliding window of multiple frames for implementing effects such as simulation or motion blur. The following image illustrates the various storage modes available in the SceneGraphs: .. image:: /images/SceneHub/PropertyStorage.png The figure above shows the storage of the SceneHub (sparse matrix of data). The static properties (non-contextual) have a dedicated storage, shared by all frames. Per-frame property value storage is created for stored frames, or can be pre-cached in advance and kept for all required frames.