.. _omnigraph_versioning_oni:

OmniGraph Versioning For ONI Interfaces
#######################################

See the description of the :ref:`omnigraph_oni_versions`.

For this example this sample interface will be used, shown in its initial form.

.. code-block:: cpp

    OMNI_DECLARE_INTERFACE(ITestInterface);

    class ITestInterface_abi
        : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.graph.core.ITestInterface")>
    {
    protected:
        /**
         * Does the old thing
         *
         * @param[in] value The value of the old thing
         */
        virtual void oldThing_abi(int value) noexcept = 0;
    };

    class TestInterface : public omni::Implements<ITestInterface>
    {
    public:
        TestInterface() = default;
        ~TestInterface() = default;

    protected:
        void oldThing_abi(int value) noexcept override
        {
            doSomething(value);
            return;
        }
    };

    void getInterfaceImplementations(const omni::InterfaceImplementation** out, uint32_t* outCount)
    {
        static const char* interfacesImplemented[] = { "ITestInterface" };
        static omni::InterfaceImplementation impls[] = {
            {
                "omni.graph.core.ITestInterface",
                []() { return static_cast<omni::IObject*>(new ITestInterface); },
                1,
                interfacesImplemented, CARB_COUNTOF32(interfacesImplemented)
            }
        };

        *out = impls;
        *outCount = CARB_COUNTOF32(impls);
    }

To call the interface code you instantiate one of them and call an interface function:

.. code-block:: cpp

    auto iTestOld = omni::core::createType<ITestInterface>();
    iTest.oldThing(1);


Adding A Function
*****************

A new function is added by creating a new interface that derives from the old interface and implements the new function.

.. code-block:: cpp
    :emphasize-lines: 2,13,30,38-43

    class ITestInterface2_abi
        : public omni::core::Inherits<omni::graph::core::ITestInterface, OMNI_TYPE_ID("omni.graph.core.ITestInterface2")>
    {
    protected:
        /**
         * Does the new thing
         *
         * @param[in] value The value of the new thing
         */
        virtual void newThing_abi(int value) noexcept = 0;
    };

    class TestInterface2 : public omni::Implements<ITestInterface2>
    {
    public:
        TestInterface2() = default;
        ~TestInterface2() = default;

    protected:
        void newThing_abi(int value) noexcept override
        {
            doSomethingNew(value);
            return;
        }
    };

    void getInterfaceImplementations(const omni::InterfaceImplementation** out, uint32_t* outCount)
    {
        static const char* interfacesImplemented[] = { "ITestInterface" };
        static const char* interfacesImplemented2[] = { "ITestInterface2" };
        static omni::InterfaceImplementation impls[] = {
            {
                "omni.graph.core.ITestInterface",
                []() { return static_cast<omni::IObject*>(new ITestInterface); },
                1,
                interfacesImplemented, CARB_COUNTOF32(interfacesImplemented)
            },
            {
                "omni.graph.core.ITestInterface2",
                []() { return static_cast<omni::IObject*>(new ITestInterface2); },
                1,
                interfacesImplemented2, CARB_COUNTOF32(interfacesImplemented)
            }
        };

        *out = impls;
        *outCount = CARB_COUNTOF32(impls);
    }


The old code is untouched by this and continues to function as usual. Any code wishing to use the new function has to
instantiate the new interface.

.. code-block:: cpp

    auto iTestOld = omni::core::createType<ITestInterface>();
    iTest.oldThing(1);
    // iTest.newThing(2);  // Would fail - this behaves as the old interface only

    auto iTestNew = omni::core::createType<ITestInterface2>();
    iTestNew.oldThing(1);  // Still works
    iTestNew.newThing(2);

Deprecating Functions
*********************

In the rare occasion where you want to deprecate a function entirely you must follow this process to
ensure maximum compatibility and notification to affected users.

Step 1: Add a Deprecation Notice
================================

The team should decide exactly when a deprecation will happen, as it is beneficial to group deprecations together to
minimize the work required on the user end to deal with the deprecations. Once that's decided the first thing to do is
to communicate the deprecation intent through the decided-upon channels.

Once that has happend the implementation of the interface function will add a warning message that the function is
deprecated and will be going away.

.. code-block:: cpp
    :emphasize-lines: 8,26

    OMNI_DECLARE_INTERFACE(ITestInterface);

    class ITestInterface_abi
        : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.graph.core.ITestInterface")>
    {
    protected:
        /**
         * This function is deprecated - use newFunction() instead
         *
         * Does the old thing
         *
         * @param[in] value The value of the old thing
         */
        virtual void oldThing_abi(int value) noexcept = 0;
    };

    class TestInterface : public omni::Implements<ITestInterface>
    {
    public:
        TestInterface() = default;
        ~TestInterface() = default;

    protected:
        void oldThing_abi(int value) noexcept override
        {
            CARB_LOG_WARNING("ITestInterface::oldFunction() is deprecated - use ITestInterface::newFunction() instead");
            doSomething(value);
            return;
        }
    };


Step 2: Make The Deprecated Path An Error
=========================================

To ensure the use of the deprecated path is highly visible in this phase the warning is upgraded to an error and the
comments on the function make it more clear that it is not to be used in new code.

.. code-block:: cpp
    :emphasize-lines: 8,22

    OMNI_DECLARE_INTERFACE(ITestInterface);

    class ITestInterface_abi
        : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.graph.core.ITestInterface")>
    {
    protected:
        /**
         * This function is deprecated - use newFunction() instead
         */
        virtual void oldThing_abi(int value) noexcept = 0;
    };

    class TestInterface : public omni::Implements<ITestInterface>
    {
    public:
        TestInterface() = default;
        ~TestInterface() = default;

    protected:
        void oldThing_abi(int value) noexcept override
        {
            CARB_LOG_ERROR("ITestInterface::oldFunction() is deprecated - use ITestInterface::newFunction() instead");
            doSomething(value);
            return;
        }
    };

.. note::

    You can choose to make the deprecation even more emphatic if calling the function could do something bad like
    create instability in the code. You can do so by using a `throw` instead of just `CARB_LOG_ERROR`.


Step 3: Hard Deprecation Of The Function
========================================

When the final step is necessary you must bump the major version number of the extension as this is a breaking change.
You must also bump the version of the interface so that in those cases where an extension has access to both the old
and new versions of the interface it will prefer the new one. The interface class remains though, even if the last
function has been taken from it.

.. important::

    This type of hard deprecation is extremely disruptive as it will require extensions to be rebuilt and possibly
    modified to conform to the new interface. It should only be used when there is something in the interface that
    is intrinsically dangerous or difficult to continue supporting.


.. code-block:: cpp
    :emphasize-lines: 5-6,14-15,25

    OMNI_DECLARE_INTERFACE(ITestInterface);

    class ITestInterface_abi
        : public omni::core::Inherits<omni::core::IObject, OMNI_TYPE_ID("omni.graph.core.ITestInterface")>
    {
    };

    class TestInterface : public omni::Implements<ITestInterface>
    {
    public:
        TestInterface() = default;
        ~TestInterface() = default;

    protected:
    };

    void getInterfaceImplementations(const omni::InterfaceImplementation** out, uint32_t* outCount)
    {
        static const char* interfacesImplemented[] = { "ITestInterface" };
        static const char* interfacesImplemented2[] = { "ITestInterface2" };
        static omni::InterfaceImplementation impls[] = {
            {
                "omni.graph.core.ITestInterface",
                []() { return static_cast<omni::IObject*>(new ITestInterface); },
                2,
                interfacesImplemented, CARB_COUNTOF32(interfacesImplemented)
            },
            {
                "omni.graph.core.ITestInterface2",
                []() { return static_cast<omni::IObject*>(new ITestInterface2); },
                1,
                interfacesImplemented2, CARB_COUNTOF32(interfacesImplemented)
            }
        };

        *out = impls;
        *outCount = CARB_COUNTOF32(impls);
    }

