Cook Interface (C++)
====================

C++ Reference
-------------

.. doxygengroup:: FnGeolibOp

Geolib Op Private Data
----------------------

Private Data Overview
`````````````````````

When creating child locations, developers of Ops can pass user-defined data to
the created child locations in one of two ways:

- **Using Op Args**: If you wish to pass data to child locations, the best way
  to do this is through their Op Args. The
  `Foundry::Katana::GeolibCookInterface::getOpArg()
  <#_CPPv2N7Foundry6Katana19GeolibCookInterface8getOpArgEN10FnPlatform10StringViewE>`_
  function gives all arguments as a GroupAttribute.

- **Using private data**: If you need to pass more complex data down to children
  that is not representable as attributes, then you can additionally use the
  private data pointer. For instance:

   - Associative maps
   - Modifiable data
   - Thread-safe data accessors
   - Performance-critical data
   - Other raw data

Private data can be passed as a void pointer to the following functions:

- ``Foundry::Katana::GeolibCookInterface::createChild()``
- ``Foundry::Katana::GeolibCookInterface::replaceChildTraversalOp()``
- ``Foundry::Katana::GeolibCookInterface::execOp()``

A utility class is defined; `Foundry::Katana::GeolibPrivateData
<#_CPPv2N7Foundry6Katana17GeolibPrivateDataE>`_; that can be used as a base
class for custom private data classes.

For example, the class is used in the AlembicIn Op for its own private data
type:

.. code-block:: cpp

    class AlembicInPrivateData : public Foundry::Katana::GeolibPrivateData

To retrieve the private data, the
`Foundry::Katana::GeolibCookInterface::getPrivateData()
<#_CPPv2N7Foundry6Katana19GeolibCookInterface14getPrivateDataEv>`_ function can
be used.


Example Op that uses private data
`````````````````````````````````

The following example shows how private data can be used to pass complex
information to the created child locations.

The first time the Op runs at a specific location, a file reader is created in
order to open a file. Because this operation is usually expensive, we want to
reuse the file reader to retrieve the required information for all the
locations. However, the file reader is not suitable for being stored in a
FnAttribute, therefore, it will need to be wrapped into a private data container
(derived from `Foundry::Katana::GeolibPrivateData
<#_CPPv2N7Foundry6Katana17GeolibPrivateDataE>`_ in this case), then to be passed
down to the created child locations.

.. code-block:: cpp

    /// Example class that reads a metaphorical custom format file and converts its
    /// data into FnAttributes.
    class CustomFormatFileReader final
    {
    public:
        CustomFormatFileReader(const std::string& filepath) { ... }
        ~CustomFormatFileReader() { ... }
        std::vector<std::string> getChildren(const std::string& parent) { ... }
        FnAttribute::GroupAttribute getAttrs(const std::string& location) { ... }
    private:
        ...
    };

    /// Private data container to pass data to child locations.
    class PrivateData : public Foundry::Katana::GeolibPrivateData
    {
    public:
        PrivateData(const std::shared_ptr<CustomFormatFileReader>& fileReader)
            : m_fileReader(fileReader) {}

        std::shared_ptr<CustomFormatFileReader> m_fileReader;
    };

    /// Example Op that reads a custom format file and creates the hierarchy in
    /// Katana along with all its attributes.
    class CustomFormatIn : public Foundry::Katana::GeolibOp
    {
    public:
        static void setup(Foundry::Katana::GeolibSetupInterface& interface)
        {
            interface.setThreading(
                Foundry::Katana::GeolibSetupInterface::ThreadModeConcurrent);
        }

        static void cook(Foundry::Katana::GeolibCookInterface& interface)
        {
            std::shared_ptr<CustomFormatFileReader> fileReader;
            if (interface.atRoot())
            {
                // Open file only when at root.
                const FnAttribute::StringAttribute filepathAttr(
                    interface.getOpArg("filepath"));
                const std::string filepath = filepathAttr.getValue("", false);
                fileReader = std::make_shared<CustomFormatFileReader>(filepath);
            }
            else
            {
                // Get previously opened file from private data.
                const PrivateData* const privateData =
                    static_cast<const PrivateData*>(interface.getPrivateData());

                assert(privateData && "Private data not found where it should");
                if (!privateData)
                    return;

                fileReader = privateData->m_fileReader;
            }

            const std::string relativeOutputPath =
                interface.getRelativeOutputLocationPath();

            // Set attributes for this relative location.
            const auto attrs = fileReader->getAttrs(relativeOutputPath);
            const int64_t attrCount = attrs.getNumberOfChildren();
            for (int64_t i = 0; i < attrCount; ++i)
            {
                interface.setAttr(attrs.getChildName(i), attrs.getChildByIndex(i));
            }

            // Get relative children from file.
            const auto childLocations = fileReader->getChildren(relativeOutputPath);

            // Create one child location per entry, with their respective
            // attributes. The file reader will be passed as private data.
            for (const auto& child : childLocations)
            {
                interface.createChild(
                    child,
                    "CustomFormatIn",
                    FnAttribute::Attribute(),
                    Foundry::Katana::GeolibCookInterface::ResetRootAuto,
                    new PrivateData(fileReader),
                    PrivateData::Delete);
            }
        }
    };

Private Data Life Cycle
```````````````````````

The private data passed to an Op is linked to the lifetime of the location data
of the location that the Op was involved in creating. Location data is deleted
either when the location is evicted, or when the location is re-cooked after
having been invalidated.

During a UI session, eviction only takes place when caches are flushed.
Therefore, unless the location needs to be re-cooked, private data may live for
long periods.

During a render, unless explicit eviction is requested by the renderer (see the
``evict`` parameter present in several functions in :doc:`Scene Graph Iterators
<../../Plugins/Utilities/SceneGraphIterator>`), Geolib will only evict location
data that is not currently in the ancestral path for the location that it is
currently cooking, with the exception of location data that was cooked as a
result of a scattered query.

.. figure:: BasicEvictionCase.png
    :alt: Basic Eviction Case

    Basic eviction case: After the marked location is cooked, locations that are
    not in the ancestral path will get their location data evicted, along with
    the associated Op's private data that was used to create the location.

.. The figure above was generated with the following graph:
    graph basic_eviction_case
    {
        size="4,4"

        a -- b [color=gray70];
        a -- c [color=gray70];
        a -- d;
        b -- e [color=gray70];
        b -- f [color=gray70];
        d -- g;
        g -- h;
        g -- i [color=gray70];
        h -- j [color=gray70];
        h -- k [color=gray70];
        i -- l [color=gray70];
        k -- m [color=gray70];
        k -- n [color=gray70];

        a [label="", shape=circle];
        b [label="", shape=circle, color=gray70];
        c [label="", shape=circle, color=gray70];
        d [label="", shape=circle];
        e [label="", shape=circle, color=gray70];
        f [label="", shape=circle, color=gray70];
        g [label="", shape=circle];
        h [label="", shape=doublecircle];
        i [label="", shape=circle, color=gray70];
        j [label="", shape=circle, color=gray70];
        k [label="", shape=circle, color=gray70];
        l [label="", shape=circle, color=gray70];
        m [label="", shape=circle, color=gray70];
        n [label="", shape=circle, color=gray70];
    }

When private data is used with
``Foundry::Katana::GeolibCookInterface::execOp()``, its lifetime is short. As
soon as the function completes, the provided deleter function will be invoked
and the private data will be deleted.