// Copyright (c) 2020-2021, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once

// The purpose of this file is to implement the C++ classes StageInProgress,
// StageAtTime, StageAtTimeInterval and StageHistoryWindow by calling the
// carbonite C-ABI interfaces, IStageInProgress, IStageAtTime,
// IStageAtTimeWindow and IStageHistoryWindow.
//
//


#include "StageWithHistory.h"

#include <carb/InterfaceUtils.h>
#include <carb/logging/Log.h>

#include <type_traits>
#include <cstdint>

namespace carb
{
namespace flatcache
{
// StageInProgress implementation starts here

// RAII constructor
inline StageInProgress::StageInProgress(StageWithHistory& stageWithHistory, size_t simFrameNumber)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    m_stageInProgress = iStageInProgress->create(stageWithHistory.m_usdStageId, simFrameNumber);
    m_usdStageId = stageWithHistory.m_usdStageId;
    m_createdFromId = false;
}

// Non-RAII constructor
inline StageInProgress::StageInProgress(StageInProgressId stageInProgressId)
{
    m_stageInProgress = stageInProgressId;
    m_createdFromId = true;
    // m_usdStageId is not valid when m_createdFromId==true
}

inline StageInProgress::~StageInProgress()
{
    if (!m_createdFromId)
    {
        auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
        iStageInProgress->destroy(m_usdStageId);
    }
}

inline size_t StageInProgress::getFrameNumber()
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getFrameNumber(m_stageInProgress);
}

inline ValidMirrors StageInProgress::getAttributeValidBits(const Path& path, const Token& attrName) const
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getAttributeValidBits(m_stageInProgress, path, attrName);
}

inline RationalTime StageInProgress::getFrameTime()
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getFrameTime(m_stageInProgress);
}

template <typename T>
T* StageInProgress::getAttribute(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC ptrAndSize = iStageInProgress->getAttribute(m_stageInProgress, path, attrName);

    if (sizeof(T) == ptrAndSize.elementSize)
    {
        return reinterpret_cast<T*>(ptrAndSize.ptr);
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
        return nullptr;
    }
}

template <typename T>
const T* StageInProgress::getAttributeRd(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    ConstSpanC ptrAndSize = iStageInProgress->getAttributeRd(m_stageInProgress, path, attrName);

    if (sizeof(T) == ptrAndSize.elementSize)
    {
        return reinterpret_cast<const T*>(ptrAndSize.ptr);
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
        return nullptr;
    }
}

template <typename T>
T* StageInProgress::getAttributeWr(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC ptrAndSize = iStageInProgress->getAttributeWr(m_stageInProgress, path, attrName);

    if (sizeof(T) == ptrAndSize.elementSize)
    {
        return reinterpret_cast<T*>(ptrAndSize.ptr);
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
        return nullptr;
    }
}

template <typename T>
T* StageInProgress::getAttributeGpu(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC ptrAndSize = iStageInProgress->getAttributeGpu(m_stageInProgress, path, attrName);

    if (sizeof(T) == ptrAndSize.elementSize)
    {
        return reinterpret_cast<T*>(ptrAndSize.ptr);
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
        return nullptr;
    }
}

template <typename T>
const T* StageInProgress::getAttributeRdGpu(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    ConstSpanC ptrAndSize = iStageInProgress->getAttributeRdGpu(m_stageInProgress, path, attrName);

    if (sizeof(T) == ptrAndSize.elementSize)
    {
        return reinterpret_cast<const T*>(ptrAndSize.ptr);
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
        return nullptr;
    }
}

template <typename T>
T* StageInProgress::getAttributeWrGpu(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC ptrAndSize = iStageInProgress->getAttributeWrGpu(m_stageInProgress, path, attrName);

    if (sizeof(T*) == ptrAndSize.elementSize)
    {
        return reinterpret_cast<T*>(ptrAndSize.ptr);
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
        return nullptr;
    }
}

template <typename T>
T& StageInProgress::getOrCreateAttributeWr(const Path& path, const Token& attrName, Type type)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC ptrAndSize = iStageInProgress->getOrCreateAttributeWr(m_stageInProgress, path, attrName, TypeC(type));

    if (sizeof(T) != ptrAndSize.elementSize)
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), ptrAndSize.elementSize);
    }

    return *reinterpret_cast<T*>(ptrAndSize.ptr);
}

template <typename T>
gsl::span<T> StageInProgress::getArrayAttribute(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC arrayData = iStageInProgress->getArrayAttributeWr(m_stageInProgress, path, attrName);

    if (sizeof(T) != arrayData.elementSize)
    {
        CARB_LOG_WARN_ONCE(
            "Trying to access array with elements of size %zu bytes from %s.%s, but flatcache has only %zu bytes",
            sizeof(T), path.getText(), attrName.getText(), arrayData.elementSize);
        return gsl::span<T>();
    }
    gsl::span<T> retval(reinterpret_cast<T*>(arrayData.ptr), arrayData.elementCount);
    return retval;
}

template <typename T>
gsl::span<const T> StageInProgress::getArrayAttributeRd(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    ConstSpanC arrayData = iStageInProgress->getArrayAttributeRd(m_stageInProgress, path, attrName);

    if (sizeof(T) != arrayData.elementSize)
    {
        CARB_LOG_WARN_ONCE(
            "Trying to access array with elements of size %zu bytes from %s.%s, but flatcache has only %zu bytes",
            sizeof(T), path.getText(), attrName.getText(), arrayData.elementSize);
        return gsl::span<const T>();
    }
    gsl::span<const T> retval(reinterpret_cast<const T*>(arrayData.ptr), arrayData.elementCount);
    return retval;
}

template <typename T>
gsl::span<T> StageInProgress::getArrayAttributeWr(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC arrayData = iStageInProgress->getArrayAttributeWr(m_stageInProgress, path, attrName);

    if (sizeof(T) != arrayData.elementSize)
    {
        CARB_LOG_WARN_ONCE(
            "Trying to access array with elements of size %zu bytes from %s.%s, but flatcache has only %zu bytes",
            sizeof(T), path.getText(), attrName.getText(), arrayData.elementSize);
        return gsl::span<T>();
    }
    gsl::span<T> retval(reinterpret_cast<T*>(arrayData.ptr), arrayData.elementCount);
    return retval;
}

inline size_t StageInProgress::getArrayAttributeSize(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getArrayAttributeSize(m_stageInProgress, path, attrName);
}

inline void StageInProgress::setArrayAttributeSize(const Path& path, const Token& attrName, size_t elemCount)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->setArrayAttributeSize(m_stageInProgress, path, attrName, elemCount);
}

template <typename T>
inline gsl::span<T> StageInProgress::setArrayAttributeSizeAndGet(const PrimBucketList& primBucketList,
                                                                 size_t primBucketListIndex,
                                                                 size_t indexInBucket,
                                                                 const Token& attrName,
                                                                 size_t newElemCount)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    SpanC newArrayC = iStageInProgress->setArrayAttributeSizeAndGet(
        m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, indexInBucket, attrName, newElemCount);
    T* typedElementsPtr = reinterpret_cast<T*>(newArrayC.ptr);
    return { typedElementsPtr, newArrayC.elementCount };
}

inline void StageInProgress::createPrim(const Path& path)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->createPrim(m_stageInProgress, path);
}

inline void StageInProgress::destroyPrim(const Path& path)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->destroyPrim(m_stageInProgress, path);
}

inline void StageInProgress::createAttribute(const Path& path, const Token& attrName, Type type)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->createAttribute(m_stageInProgress, path, attrName, TypeC(type));
}

template <int n>
inline void StageInProgress::createAttributes(const Path& path, std::array<AttrNameAndType, n> attributes)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();

    std::array<TokenC, n> names;
    std::array<TypeC, n> types;

    for (int c = 0; c < n; ++c)
    {
        names[c] = attributes[c].name;
        types[c] = TypeC(attributes[c].type);
    }

    iStageInProgress->createAttributes(m_stageInProgress, path, names.data(), types.data(), n);
}


inline void StageInProgress::destroyAttribute(const Path& path, const Token& attrName, Type)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->destroyAttribute2(m_stageInProgress, path, attrName);
}

inline void StageInProgress::destroyAttribute(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->destroyAttribute2(m_stageInProgress, path, attrName);
}

template <int n>
inline void StageInProgress::destroyAttributes(const Path& path, const std::array<Token, n>& attributes)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();

    std::array<TokenC, n> names;

    for (int c = 0; c < n; ++c)
    {
        names[c] = TokenC(attributes[c]);
    }

    iStageInProgress->destroyAttributes(m_stageInProgress, path, names.data(), n);
}

inline void StageInProgress::destroyAttributes(const Path& path, const std::vector<Token>& attributes)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();

    const size_t n = attributes.size();
    std::vector<TokenC> names(n);

    for (size_t c = 0; c < n; ++c)
    {
        names[c] = TokenC(attributes[c]);
    }

    iStageInProgress->destroyAttributes(m_stageInProgress, path, names.data(), (uint32_t)n);
}

inline PrimBucketList StageInProgress::findPrims(const carb::flatcache::set<AttrNameAndType>& all,
                                                 const carb::flatcache::set<AttrNameAndType>& any,
                                                 const carb::flatcache::set<AttrNameAndType>& none)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    PrimBucketListId primBucketListId = iStageInProgress->findPrims(m_stageInProgress, all, any, none);

    return { primBucketListId };
}

inline void StageInProgress::attributeEnableChangeTracking(const Token& attrName, ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    iChangeTrackerConfig->attributeEnable(m_stageInProgress, attrName, listenerId);
}

inline void StageInProgress::enablePrimCreateTracking(ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    iChangeTrackerConfig->enablePrimCreateTracking(m_stageInProgress, listenerId);
}

inline void StageInProgress::attributeDisableChangeTracking(const Token& attrName, ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    iChangeTrackerConfig->attributeDisable(m_stageInProgress, attrName, listenerId);
}

inline void StageInProgress::pauseChangeTracking(ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    iChangeTrackerConfig->pause(m_stageInProgress, listenerId);
}

inline void StageInProgress::resumeChangeTracking(ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    iChangeTrackerConfig->resume(m_stageInProgress, listenerId);
}

inline bool StageInProgress::isChangeTrackingPaused(ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    return iChangeTrackerConfig->isChangeTrackingPaused(m_stageInProgress, listenerId);
}

inline bool StageInProgress::isListenerAttached(ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    return iChangeTrackerConfig->isListenerAttached(m_stageInProgress, listenerId);
}

inline void StageInProgress::detachListener(ListenerId listenerId)
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    iChangeTrackerConfig->detachListener(m_stageInProgress, listenerId);
}

inline size_t StageInProgress::getListenerCount()
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IChangeTrackerConfig>();
    return iChangeTrackerConfig->getListenerCount(m_stageInProgress);
}

inline ChangedPrimBucketList StageInProgress::getChanges(ListenerId listenerId)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    PrimBucketListId changeListId = iStageInProgress->getChanges(m_stageInProgress, listenerId);

    return ChangedPrimBucketList(changeListId);
}

inline void StageInProgress::popChanges(ListenerId listenerId)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->popChanges(m_stageInProgress, listenerId);
}

template <typename T>
gsl::span<T> StageInProgress::getAttributeArray(const PrimBucketList& primBucketList,
                                                size_t primBucketListIndex,
                                                const Token& attrName)
{
    SpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getAttributeArray(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    T* typedElementsPtr = reinterpret_cast<T*>(array.ptr);
    gsl::span<T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
gsl::span<const T> StageInProgress::getAttributeArrayRd(const PrimBucketList& primBucketList,
                                                        size_t primBucketListIndex,
                                                        const Token& attrName) const
{
    ConstSpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getAttributeArrayRd(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    const T* typedElementsPtr = reinterpret_cast<const T*>(array.ptr);
    gsl::span<const T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
gsl::span<T> StageInProgress::getAttributeArrayWr(const PrimBucketList& primBucketList,
                                                  size_t primBucketListIndex,
                                                  const Token& attrName)
{
    SpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getAttributeArrayWr(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    T* typedElementsPtr = reinterpret_cast<T*>(array.ptr);
    gsl::span<T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
gsl::span<T> StageInProgress::getAttributeArrayGpu(const PrimBucketList& primBucketList,
                                                   size_t primBucketListIndex,
                                                   const Token& attrName)
{
    SpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getAttributeArrayGpu(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    T* typedElementsPtr = reinterpret_cast<T*>(array.ptr);
    gsl::span<T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
gsl::span<const T> StageInProgress::getAttributeArrayRdGpu(const PrimBucketList& primBucketList,
                                                           size_t primBucketListIndex,
                                                           const Token& attrName) const
{
    ConstSpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getAttributeArrayRdGpu(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    const T* typedElementsPtr = reinterpret_cast<const T*>(array.ptr);
    gsl::span<const T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
gsl::span<T> StageInProgress::getAttributeArrayWrGpu(const PrimBucketList& primBucketList,
                                                     size_t primBucketListIndex,
                                                     const Token& attrName)
{
    SpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getAttributeArrayWrGpu(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    T* typedElementsPtr = reinterpret_cast<T*>(array.ptr);
    gsl::span<T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
gsl::span<T> StageInProgress::getOrCreateAttributeArrayWr(const PrimBucketList& primBucketList,
                                                          size_t primBucketListIndex,
                                                          const Token& attrName,
                                                          Type type)
{
    SpanC array;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getOrCreateAttributeArrayWr(
        &array, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName, TypeC(type));

    T* typedElementsPtr = reinterpret_cast<T*>(array.ptr);
    gsl::span<T> retval(typedElementsPtr, array.elementCount);

    return retval;
}

template <typename T>
std::vector<gsl::span<T>> StageInProgress::getArrayAttributeArray(const PrimBucketList& primBucketList,
                                                                  size_t primBucketListIndex,
                                                                  const Token& attrName) const
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();

    ArrayPointersAndSizesC pointersAndSizes = iStageInProgress->getArrayAttributeArrayWithSizes(
        m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    size_t primCount = pointersAndSizes.elementCount;
    std::vector<gsl::span<T>> arrays(primCount);
    for (size_t i = 0; i != primCount; i++)
    {
        T* typedElementsPtr = reinterpret_cast<T*>(pointersAndSizes.arrayPtrs[i]);
        arrays[i] = { typedElementsPtr, pointersAndSizes.sizes[i] };
    }

    return arrays;
}

template <typename T>
std::vector<gsl::span<const T>> StageInProgress::getArrayAttributeArrayRd(const PrimBucketList& primBucketList,
                                                                          size_t primBucketListIndex,
                                                                          const Token& attrName) const
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();

    ConstArrayPointersAndSizesC pointersAndSizes = iStageInProgress->getArrayAttributeArrayWithSizesRd(
        m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    size_t primCount = pointersAndSizes.elementCount;
    std::vector<gsl::span<const T>> arrays(primCount);
    for (size_t i = 0; i != primCount; i++)
    {
        const T* typedElementsPtr = reinterpret_cast<const T*>(pointersAndSizes.arrayPtrs[i]);
        arrays[i] = { typedElementsPtr, pointersAndSizes.sizes[i] };
    }

    return arrays;
}

template <typename T>
std::vector<gsl::span<T>> StageInProgress::getArrayAttributeArrayWr(const PrimBucketList& primBucketList,
                                                                    size_t primBucketListIndex,
                                                                    const Token& attrName) const
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();

    ArrayPointersAndSizesC pointersAndSizes = iStageInProgress->getArrayAttributeArrayWithSizesWr(
        m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    size_t primCount = pointersAndSizes.elementCount;
    std::vector<gsl::span<T>> arrays(primCount);
    for (size_t i = 0; i != primCount; i++)
    {
        T* typedElementsPtr = reinterpret_cast<T*>(pointersAndSizes.arrayPtrs[i]);
        arrays[i] = { typedElementsPtr, pointersAndSizes.sizes[i] };
    }

    return arrays;
}


inline gsl::span<const Path> StageInProgress::getPathArray(const PrimBucketList& primBucketList,
                                                           size_t primBucketListIndex) const
{
    ConstPathCSpan arrayC;

    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->getPathArray(&arrayC, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex);

    const Path* array = reinterpret_cast<const Path*>(arrayC.ptr);
    gsl::span<const Path> retval(array, arrayC.elementCount);

    return retval;
}

inline void StageInProgress::printBucketNames() const
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->printBucketNames(m_stageInProgress);
}

inline void StageInProgress::logAttributeWriteForNotice(const Path& path, const Token& attrName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->logAttributeWriteForNotice(m_stageInProgress, path, attrName);
}


inline flatcache::set<AttrNameAndType> StageInProgress::getAttributeNamesAndTypes(const PrimBucketList& primBucketList,
                                                                                     size_t primBucketListIndex) const
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    size_t attrCount = iStageInProgress->getBucketAttributeCount(
        m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex);

    flatcache::set<AttrNameAndType> namesAndTypes;
    namesAndTypes.v.resize(attrCount);
    // getBucketAttributeNamesAndTypes is guaranteed to return an ordered vector, so we don't have to sort namesAndTypes
    iStageInProgress->getBucketAttributeNamesAndTypes(
        namesAndTypes.data(), attrCount, m_stageInProgress, primBucketList.m_primBucketListId, primBucketListIndex);


    return namesAndTypes;
}

// Connection API
inline void StageInProgress::createConnection(const Path& path, const Token& connectionName, const Connection& connection)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->createConnection(m_stageInProgress, path, connectionName, connection);
}

inline void StageInProgress::createConnections(const Path& path, const gsl::span<Token>& connectionNames, const gsl::span<Connection>& connections )
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    if(connectionNames.size() != connections.size())
        return;
    const TokenC* namesC = reinterpret_cast<const TokenC*>(connectionNames.data());
    iStageInProgress->createConnections(m_stageInProgress, path, namesC, connections.data(), connectionNames.size());
}


inline void StageInProgress::destroyConnection(const Path& path, const Token& connectionName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->destroyConnection(m_stageInProgress, path, connectionName);
}

inline void StageInProgress::destroyConnections(const Path& path, const gsl::span<Token>& connectionNames)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    const TokenC* namesC = reinterpret_cast<const TokenC*>(connectionNames.data());
    iStageInProgress->destroyConnections(m_stageInProgress, path, namesC, connectionNames.size());
}

inline Connection* StageInProgress::getConnection(const Path& path, const Token& connectionName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getConnection(m_stageInProgress, path, connectionName);
}

inline const Connection* StageInProgress::getConnectionRd(const Path& path, const Token& connectionName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getConnectionRd(m_stageInProgress, path, connectionName);
}

inline Connection* StageInProgress::getConnectionWr(const Path& path, const Token& connectionName)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    return iStageInProgress->getConnectionWr(m_stageInProgress, path, connectionName);
}

inline void StageInProgress::copyAttributes(const Path& srcPath, const Path& dstPath)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    iStageInProgress->copyAllAttributes(m_stageInProgress, srcPath, dstPath);
}

inline void StageInProgress::copyAttributes(const Path& srcPath, const gsl::span<Token>& srcAttrs, const Path& dstPath)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    size_t n = srcAttrs.size();
    const TokenC* srcAttrsC = reinterpret_cast<const TokenC*>(srcAttrs.data());
    iStageInProgress->copySpecifiedAttributes(m_stageInProgress, srcPath, srcAttrsC, dstPath, srcAttrsC, n);
}

inline void StageInProgress::copyAttributes(const Path& srcPath, const gsl::span<Token>& srcAttrs, const Path& dstPath, const gsl::span<Token>& dstAttrs)
{
    auto iStageInProgress = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    if(srcAttrs.size() != dstAttrs.size())
    {
        return;
    }
    size_t n = srcAttrs.size();
    const TokenC* srcAttrsC = reinterpret_cast<const TokenC*>(srcAttrs.data());
    const TokenC* dstAttrsC = reinterpret_cast<const TokenC*>(dstAttrs.data());
    iStageInProgress->copySpecifiedAttributes(m_stageInProgress, srcPath, srcAttrsC, dstPath, dstAttrsC, n);
}

inline bool StageInProgress::primExists(const Path& path)
{
    auto iStageReaderWriter = carb::getCachedInterface<carb::flatcache::IStageInProgress>();
    bool retval = iStageReaderWriter->getAttributeCount(m_stageInProgress, path) != 0;
    return retval;
}

// PrimBucketList implementation starts here
inline carb::flatcache::IPrimBucketList* PrimBucketList::sIPrimBucketList()
{
    // Acquire carbonite interface on first use
    return carb::getCachedInterface<carb::flatcache::IPrimBucketList>();
}

inline size_t PrimBucketList::bucketCount() const
{
    return sIPrimBucketList()->getBucketCount(m_primBucketListId);
}

inline size_t PrimBucketList::size() const
{
    return sIPrimBucketList()->getBucketCount(m_primBucketListId);
}

inline void PrimBucketList::print() const
{
    return sIPrimBucketList()->print(m_primBucketListId);
}

inline PrimBucketList::~PrimBucketList()
{
    sIPrimBucketList()->destroy(m_primBucketListId);
}

inline BucketChanges ChangedPrimBucketList::getChanges(size_t index)
{
    return BucketChanges(sIPrimBucketList()->getChanges(m_primBucketListId, index));
}

inline AddedPrimIndices ChangedPrimBucketList::getAddedPrims(size_t index)
{
    return AddedPrimIndices(sIPrimBucketList()->getAddedPrims(m_primBucketListId, index));
}

// StageAtTimeInterval implementation starts here
inline carb::flatcache::IStageAtTimeInterval* StageAtTimeInterval::sIStageAtTimeInterval()
{
    return carb::getCachedInterface<carb::flatcache::IStageAtTimeInterval>();
}

inline StageAtTimeInterval::StageAtTimeInterval(StageWithHistory& stageWithHistory,
                                                RationalTime beginTime,
                                                RationalTime endTime,
                                                bool includeEndTime)
{
    m_stageAtTimeInterval =
        sIStageAtTimeInterval()->create(stageWithHistory.m_stageWithHistory, beginTime, endTime, includeEndTime);
}

inline StageAtTimeInterval::StageAtTimeInterval(StageWithHistoryId stageWithHistoryId,
                                                RationalTime beginTime,
                                                RationalTime endTime,
                                                bool includeEndTime)
{
    m_stageAtTimeInterval = sIStageAtTimeInterval()->create(stageWithHistoryId, beginTime, endTime, includeEndTime);
}

inline ValidMirrors StageAtTimeInterval::getAttributeValidBits(const PathC& path, const TokenC& attrName) const
{
    return sIStageAtTimeInterval()->getAttributeValidBits(m_stageAtTimeInterval, path, attrName);
}

template <typename T>
std::vector<const T*> StageAtTimeInterval::getAttributeRd(const Path& path, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<const T*> retval(count);
    const void** retvalData = reinterpret_cast<const void**>(retval.data());

    size_t bytesPerAttr =
        sIStageAtTimeInterval()->getAttributeRd(retvalData, count, m_stageAtTimeInterval, path, attrName);

    if (sizeof(T) == bytesPerAttr)
    {
        return retval;
    }
    else
    {
        CARB_LOG_WARN_ONCE("Trying to access %zu bytes from %s.%s, but flatcache has only %zu bytes", sizeof(T),
                           path.getText(), attrName.getText(), bytesPerAttr);
        return std::vector<const T*>();
    }
}

template <typename T>
std::vector<const T*> StageAtTimeInterval::getAttributeRdGpu(const Path& path, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<const T*> retval(count);
    std::vector<ConstSpanC> arrays(count);

    sIStageAtTimeInterval()->getAttributeRdGpu(arrays.data(), count, m_stageAtTimeInterval, path, attrName);

    for (size_t i = 0; i != count; i++)
    {
        if (arrays[i].elementSize == sizeof(T))
        {
            retval[i] = reinterpret_cast<const T*>(arrays[i].ptr);
        }
        else
        {
            retval[i] = nullptr;
        }
    }

    return retval;
}

inline std::vector<size_t> StageAtTimeInterval::getArrayAttributeSize(const Path& path, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<size_t> sizes(count);

    sIStageAtTimeInterval()->getArrayAttributeSize(sizes.data(), count, m_stageAtTimeInterval, path, attrName);

    return sizes;
}

template <typename T>
std::vector<gsl::span<const T>> StageAtTimeInterval::getArrayAttributeRd(const Path& path, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstSpanWithTypeC> arrays(count);
    std::vector<gsl::span<const T>> retval(count);

    sIStageAtTimeInterval()->getArrayAttributeWithSizeRd(arrays.data(), count, m_stageAtTimeInterval, path, attrName);

    for (size_t i = 0; i != count; i++)
    {
        if (arrays[i].elementSize != sizeof(T))
        {
            retval[i] = gsl::span<T>();
            continue;
        }

        const T* ptr = reinterpret_cast<const T*>(arrays[i].ptr);
        retval[i] = gsl::span<const T>(ptr, arrays[i].elementCount);
    }

    return retval;
}

inline std::vector<ConstArrayAsBytes> StageAtTimeInterval::getArrayAttributeRawRd(const Path& path, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstSpanWithTypeC> arrays(count);
    std::vector<ConstArrayAsBytes> retval(count);

    sIStageAtTimeInterval()->getArrayAttributeWithSizeRd(arrays.data(), count, m_stageAtTimeInterval, path, attrName);

    for (size_t i = 0; i != count; i++)
    {
        const gsl::byte* ptr = reinterpret_cast<const gsl::byte*>(arrays[i].ptr);
        retval[i].arrayBytes = gsl::span<const gsl::byte>(ptr, arrays[i].elementCount * arrays[i].elementSize);
        retval[i].bytesPerElement = arrays[i].elementSize;
        retval[i].elementType = Type(arrays[i].type);
    }

    return retval;
}

inline std::vector<RationalTime> StageAtTimeInterval::getTimestamps() const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<RationalTime> retval(count);

    sIStageAtTimeInterval()->getTimestamps(retval.data(), count, m_stageAtTimeInterval);

    return retval;
}

inline size_t StageAtTimeInterval::getTimeSampleCount() const
{
    return sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
}

inline PrimBucketList StageAtTimeInterval::findPrims(const carb::flatcache::set<AttrNameAndType>& all,
                                                     const carb::flatcache::set<AttrNameAndType>& any,
                                                     const carb::flatcache::set<AttrNameAndType>& none)
{
    PrimBucketListId primBucketListId = sIStageAtTimeInterval()->findPrims(m_stageAtTimeInterval, all, any, none);

    return { primBucketListId };
}

template <typename T>
std::vector<gsl::span<const T>> StageAtTimeInterval::getAttributeArrayRd(const PrimBucketList& primBucketList,
                                                                         size_t primBucketListIndex,
                                                                         const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstSpanC> outC(count);
    ConstSpanC* outCData = outC.data();

    sIStageAtTimeInterval()->getAttributeArrayRd(
        outCData, count, m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    std::vector<gsl::span<const T>> retval(count);
    size_t i = 0;
    for (ConstSpanC array : outC)
    {
        const T* typedElementsPtr = reinterpret_cast<const T*>(array.ptr);
        retval[i] = gsl::span<const T>(typedElementsPtr, array.elementCount);
        i++;
    }

    return retval;
}

template <typename T>
std::vector<gsl::span<const T>> StageAtTimeInterval::getAttributeArrayRdGpu(const PrimBucketList& primBucketList,
                                                                            size_t primBucketListIndex,
                                                                            const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstSpanC> outC(count);
    ConstSpanC* outCData = outC.data();

    sIStageAtTimeInterval()->getAttributeArrayRdGpu(
        outCData, count, m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    std::vector<gsl::span<const T>> retval(count);
    size_t i = 0;
    for (ConstSpanC array : outC)
    {
        const T* typedElementsPtr = reinterpret_cast<const T*>(array.ptr);
        retval[i] = gsl::span<const T>(typedElementsPtr, array.elementCount);
        i++;
    }

    return retval;
}

template <typename T>
std::vector<std::vector<gsl::span<const T>>> StageAtTimeInterval::getArrayAttributeArrayRd(
    const PrimBucketList& primBucketList, size_t primBucketListIndex, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstArrayPointersAndSizesC> outC(count);
    ConstArrayPointersAndSizesC* outCData = outC.data();

    sIStageAtTimeInterval()->getArrayAttributeArrayWithSizesRd(
        outCData, count, m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    std::vector<std::vector<gsl::span<const T>>> retval(count);
    size_t i = 0;
    for (ConstArrayPointersAndSizesC pointersAndSizes : outC)
    {
        size_t primCount = pointersAndSizes.elementCount;
        retval[i].resize(primCount);
        for (size_t j = 0; j != primCount; j++)
        {
            const T* typedElementsPtr = reinterpret_cast<const T*>(pointersAndSizes.arrayPtrs[j]);
            retval[i][j] = { typedElementsPtr, pointersAndSizes.sizes[j] };
        }
        i++;
    }

    return retval;
}

inline std::vector<gsl::span<const char>> StageAtTimeInterval::getAttributeArrayRawRd(
    const PrimBucketList& primBucketList, size_t primBucketListIndex, const Token& attrName) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstSpanC> outC(count);
    ConstSpanC* outCData = outC.data();

    sIStageAtTimeInterval()->getAttributeArrayRd(
        outCData, count, m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex, attrName);

    std::vector<gsl::span<const char>> retval(count);
    size_t i = 0;
    for (ConstSpanC array : outC)
    {
        const char* typedElementsPtr = reinterpret_cast<const char*>(array.ptr);
        retval[i] = gsl::span<const char>(typedElementsPtr, array.elementCount * array.elementSize);
        i++;
    }

    return retval;
}

inline std::vector<gsl::span<const Path>> StageAtTimeInterval::getPathArray(const PrimBucketList& primBucketList,
                                                                            size_t primBucketListIndex) const
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<ConstPathCSpan> outC(count);
    ConstPathCSpan* outCData = outC.data();

    sIStageAtTimeInterval()->getPathArray(
        outCData, count, m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex);

    std::vector<gsl::span<const Path>> retval(count);
    size_t i = 0;
    for (ConstPathCSpan arrayC : outC)
    {
        const Path* array = reinterpret_cast<const Path*>(arrayC.ptr);
        retval[i] = gsl::span<const Path>(array, arrayC.elementCount);
        i++;
    }

    return retval;
}

inline std::vector<const Connection*> StageAtTimeInterval::getConnectionRd(const Path& path, const Token& connectionName)
{
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    std::vector<const Connection*> retval(count);
    const void** retvalData = reinterpret_cast<const void**>(retval.data());
    sIStageAtTimeInterval()->getConnectionRd(retvalData, count, m_stageAtTimeInterval, path, connectionName);
    return retval;
}

inline void StageAtTimeInterval::printBucketNames() const
{
    sIStageAtTimeInterval()->printBucketNames(m_stageAtTimeInterval);
}

inline std::vector<size_t> StageAtTimeInterval::getAttributeCounts(const PrimBucketList& primBucketList,
                                                                   size_t primBucketListIndex) const
{
    std::vector<size_t> counts;
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);
    counts.resize(count);
    sIStageAtTimeInterval()->getAttributeCounts(
        m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex, count, counts.data());
    return counts;
}

inline std::pair<std::vector<std::vector<Token>>, std::vector<std::vector<Type>>> StageAtTimeInterval::getAttributeNamesAndTypes(
    const PrimBucketList& primBucketList, size_t primBucketListIndex) const
{
    std::vector<std::vector<Token>> outNames;
    std::vector<std::vector<Type>> outTypes;
    size_t count = sIStageAtTimeInterval()->getTimesampleCount(m_stageAtTimeInterval);

    std::vector<size_t> outSizes;
    outSizes.resize(count);
    sIStageAtTimeInterval()->getAttributeCounts(
        m_stageAtTimeInterval, primBucketList.m_primBucketListId, primBucketListIndex, count, outSizes.data());

    outNames.resize(count);
    outTypes.resize(count);

    // Make array of pointers to inner arrays to allow us to call
    // getAttributeNamesAndTypes, which takes a C-style 2D array
    // not a std::vector<std::vector>.
    // Also set size of inner arrays
    std::vector<Token*> outNamesPtrs(count);
    std::vector<Type*> outTypesPtrs(count);
    for (size_t i = 0; i < count; ++i)
    {
        outNames[i].resize(outSizes[i]);
        outTypes[i].resize(outSizes[i]);
        outNamesPtrs[i] = outNames[i].data();
        outTypesPtrs[i] = outTypes[i].data();
    }

    sIStageAtTimeInterval()->getAttributeNamesAndTypes(m_stageAtTimeInterval,
        primBucketList.m_primBucketListId,
        primBucketListIndex,
        count,
        outSizes.data(),
        outNamesPtrs.data(),
        outTypesPtrs.data());
    return { outNames, outTypes };
}

inline StageAtTimeInterval::~StageAtTimeInterval()
{
    sIStageAtTimeInterval()->destroy(m_stageAtTimeInterval);
}

inline void StageAtTimeInterval::exportUsd(UsdStageId usdStageId) const
{
    auto iStageAtTimeInterval = carb::getCachedInterface<carb::flatcache::IStageAtTimeInterval>();
    iStageAtTimeInterval->exportUsd(m_stageAtTimeInterval, usdStageId);
}

/**
 * @brief Linear interpolation for carb types Double3, Float3, Float4 (color)
 *        See InterpolationUsd.h for extended type support
 *
 * @details This is intended to be used internally by StageAtTime read methods in order
 *          to calculate values that were not written by StageInProgress directly.
 * 
 *          Enables the decoupling of the sim and render threads by allowing them access
 *          to ringbuffer values at various frequencies.
 */
template <typename T>
const T interpolate(const T& a, const T& b, float theta)
{
    T result = T(a * (1.0f - theta)) + T(b * theta);
    return result;
    // T result = std::lerp(a, b, theta);
}

template <>
inline const carb::Double3 interpolate(const carb::Double3& a, const carb::Double3& b, float theta)
{
    if (theta < 0.0 || theta > 1.0)
    {
        CARB_LOG_WARN_ONCE("WrapperImpl interpolate(): theta %f outside range [0.0, 1.0]", theta);
    }

    carb::Double3 result;
    double tmp = 1.0 - theta;
    result.x = (a.x * tmp) + (b.x * theta);
    result.y = (a.y * tmp) + (b.y * theta);
    result.z = (a.z * tmp) + (b.z * theta);
    return result;
}


template <>
inline const carb::Float3 interpolate(const carb::Float3& a, const carb::Float3& b, float theta)
{
    if (theta < 0.0f || theta > 1.0f)
    {
        CARB_LOG_WARN_ONCE("WrapperImpl interpolate(): theta %f outside range [0.0, 1.0]", theta);
    }

    carb::Float3 result;
    float tmp = 1.0f - theta;
    result.x = (a.x * tmp) + (b.x * theta);
    result.y = (a.y * tmp) + (b.y * theta);
    result.z = (a.z * tmp) + (b.z * theta);
    return result;
}

template <>
inline const carb::Float4 interpolate(const carb::Float4& a, const carb::Float4& b, float theta)
{
    if (theta < 0.0f || theta > 1.0f)
    {
        CARB_LOG_WARN_ONCE("WrapperImpl interpolate(): theta %f outside range [0.0, 1.0]", theta);
    }

    carb::Float4 result;
    float tmp = 1.0f - theta;
    result.x = (a.x * tmp) + (b.x * theta);
    result.y = (a.y * tmp) + (b.y * theta);
    result.z = (a.z * tmp) + (b.z * theta);
    result.w = (a.w * tmp) + (b.w * theta);
    return result;
}

template <>
inline const carb::flatcache::Token interpolate(const carb::flatcache::Token& a, const carb::flatcache::Token& b, float theta)
{
    if (theta < 0.0f || theta > 1.0f)
    {
        CARB_LOG_WARN_ONCE("WrapperImpl interpolate(): theta %f outside range [0.0, 1.0]", theta);
    }

    return theta < 0.5f ? a : b;
}

// Auxiliary function used when handling data that is not going to be interpolated (bool, string, int, uint)
// Returns pair of values from first and second sampled frame, or the value found and nullptr if data is only available
// in one frame
template <typename T>
inline optional<std::pair<optional<T>,optional<T>>> StageAtTime::getNonInterpolatableAttributeRd(const Path& path, const Token& attrName) const
{
    auto rawSamples = m_historyWindow.getAttributeRd<T>(path, attrName);
    std::vector<RationalTime> sampleTimes = m_historyWindow.getTimestamps();

    if (rawSamples.size() != sampleTimes.size())
    {
        return carb::cpp17::nullopt;
    }

    // checking that if the rawSamples are not empty, we have something valid in rawSamples[0]
    CARB_ASSERT(rawSamples.empty() || rawSamples[0]);
    
    // Communicate zero samples found
    if ( rawSamples.empty() || !rawSamples[0] )
    {
        return carb::cpp17::nullopt;
    }
    
    if (rawSamples.size() == 1)
    {
        std::pair<carb::cpp17::optional<T>, carb::cpp17::optional<T>> result(*rawSamples[0], carb::cpp17::nullopt);
        return result;
    }
    else if ( (rawSamples.size() == 2) && rawSamples[1] )
    {
        std::pair<carb::cpp17::optional<T>, carb::cpp17::optional<T>> result(*rawSamples[0], *rawSamples[1]);
        return result;
    }
    return carb::cpp17::nullopt;
}

inline uint64_t StageAtTimeInterval::writeCacheToDisk(const char* file, uint8_t* workingBuffer, uint64_t workingBufferSize) const
{
    return sIStageAtTimeInterval()->writeCacheToDisk(m_stageAtTimeInterval, file, workingBuffer, workingBufferSize);
}

inline void StageAtTimeInterval::addRefCount()
{
    return sIStageAtTimeInterval()->addRefCount(m_stageAtTimeInterval);
}

inline bool StageAtTimeInterval::removeRefCount()
{
    return sIStageAtTimeInterval()->removeRefCount(m_stageAtTimeInterval);
}

inline unsigned int StageAtTimeInterval::getRefCount()
{
    return sIStageAtTimeInterval()->getRefCount(m_stageAtTimeInterval);
}

// StageAtTime implementation starts here
// This is defined here rather than in Carbonite plugin to allow use of templates and inlining

inline ValidMirrors StageAtTime::getAttributeValidBits(const PathC& path, const TokenC& attrName) const
{
    return m_historyWindow.getAttributeValidBits(path, attrName);
}
// The method reports interpolatable data types, and is specialized as optional<pair<optional<T>,optional<T>
// in order to report non-interpolatable data types as encountered in either or both samples
template <typename T>
inline optional<T> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    auto rawSamples = m_historyWindow.getAttributeRd<T>(path, attrName);

    // Communicate zero samples found
    if (rawSamples.size() == 0)
    {
        return carb::cpp17::nullopt;
    }

    // Linear interpolation supports at most two samples
    CARB_ASSERT(rawSamples.size() <= 2);

    if (rawSamples.size() == 1)
    {
        CARB_ASSERT(rawSamples[0]);

        return *rawSamples[0];
    }
    else if (rawSamples.size() == 2)
    {
        CARB_ASSERT(rawSamples[0]);
        CARB_ASSERT(rawSamples[1]);

        // Calculate linear approximation of f(time)
        T a_f = *rawSamples[0];
        T b_f = *rawSamples[1];

        return interpolate(a_f, b_f, (float)m_theta);
    }

    return carb::cpp17::nullopt;
}

// The following functions are marked for deletion since the specified types cannot be interpolated
// StageAtTime reports the non-interpolatable types read from Flatcache as a pair<optional<T>, optional<T>>
template <>
inline optional<bool> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

template <>
inline optional<int> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

template <>
inline optional<unsigned int> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

template <>
inline optional<unsigned char> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

template <>
inline optional<int64_t> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

template <>
inline optional<uint64_t> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

template <>
inline optional<carb::flatcache::Token> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const = delete;

// Specialize StageAtTime::getAttributeRd for non-interpolatable types: bool, int, uint
// In these cases the returned type will be a pair of values from the samples found, or nullopt otherwise
template <>
inline optional<std::pair<optional<bool>, optional<bool>>> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    auto result = getNonInterpolatableAttributeRd<bool>(path, attrName);
    return result;
}

template <>
inline optional<std::pair<optional<int>, optional<int>>> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    return getNonInterpolatableAttributeRd<int>(path, attrName);
}

template <>
inline optional<std::pair<optional<unsigned int>, optional<unsigned int>>> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    return getNonInterpolatableAttributeRd<unsigned int>(path, attrName);
}

template <>
inline optional<std::pair<optional<unsigned char>, optional<unsigned char>>> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    return getNonInterpolatableAttributeRd<unsigned char>(path, attrName);
}

template <>
inline optional<std::pair<optional<int64_t>, optional<int64_t>>> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    return getNonInterpolatableAttributeRd<int64_t>(path, attrName);
}

template <>
inline optional<std::pair<optional<uint64_t>, optional<uint64_t>>> StageAtTime::getAttributeRd(const Path& path, const Token& attrName) const
{
    return getNonInterpolatableAttributeRd<uint64_t>(path, attrName);
}

template <>
inline optional<std::pair<optional<carb::flatcache::Token>, optional<carb::flatcache::Token>>> StageAtTime::getAttributeRd(
    const Path& path, const Token& attrName) const
{
    return getNonInterpolatableAttributeRd<carb::flatcache::Token>(path, attrName);
}

template <typename T>
const T* StageAtTime::getAttributeRdGpu(const Path& path, const Token& attrName) const
{
    auto rawSamples = m_historyWindow.getAttributeRdGpu<T>(path, attrName);
    std::vector<RationalTime> sampleTimes = m_historyWindow.getTimestamps();

    CARB_ASSERT(rawSamples.size() == sampleTimes.size());

    // This API doesn't have a way to communicate zero samples found
    CARB_ASSERT(rawSamples.size() != 0);
    // Linear interpolation supports at most two samples
    CARB_ASSERT(rawSamples.size() <= 2);

    if (rawSamples.size() == 1)
    {
        CARB_ASSERT(rawSamples[0]);
        return rawSamples[0];
    }
    else if (rawSamples.size() == 2)
    {
        // For GPU types there is no support for interpolation yet
        // Return first sample value instead for now
        CARB_LOG_WARN_ONCE("Support for interpolation of array attributes is not supported yet, returning first time sample instead!");
        CARB_ASSERT(rawSamples[0]);
        return rawSamples[0];
    }
    return nullptr;
}

inline size_t StageAtTime::getArrayAttributeSize(const Path& path, const Token& attrName) const
{
    auto rawSamples = m_historyWindow.getArrayAttributeSize(path, attrName);
    std::vector<RationalTime> sampleTimes = m_historyWindow.getTimestamps();

    CARB_ASSERT(rawSamples.size() == sampleTimes.size());

    // This API doesn't have a way to communicate zero samples found
    CARB_ASSERT(rawSamples.size() != 0);
    // Linear interpolation supports at most two samples
    CARB_ASSERT(rawSamples.size() <= 2);

    if (rawSamples.size() == 1)
    {
        return rawSamples[0];
    }
    else if (rawSamples.size() == 2)
    {
        // For GPU types there is no support for interpolation yet
        // Return first sample value instead for now
        return rawSamples[0];
    }
    return 0;
}

template <typename T>
inline gsl::span<const T> StageAtTime::getArrayAttributeRd(const Path& path, const Token& attrName)
{
    auto rawSamples = m_historyWindow.getArrayAttributeRd<T>(path, attrName);
    std::vector<RationalTime> sampleTimes = m_historyWindow.getTimestamps();

    CARB_ASSERT(rawSamples.size() == sampleTimes.size());

    // This API doesn't have a way to communicate zero samples found
    CARB_ASSERT(rawSamples.size() != 0);
    // Linear interpolation supports at most two samples
    CARB_ASSERT(rawSamples.size() <= 2);

    if (rawSamples.size() == 1)
    {
        return rawSamples[0];
    }
    else if (rawSamples.size() == 2)
    {
        // For array types there is no support for interpolation yet
        // Return first sample value instead for now
        CARB_LOG_WARN_ONCE("Support for interpolation of array attributes is not supported yet, returning first time sample instead!");
        return rawSamples[0];
    }
    return gsl::span<const T>();
}


/**
 * @brief Auxiliary function used by AttributeArrayResult<T> and AttributeArrayResult<std::vector<T>>
 *
 * @details Used to assess if a prim is present in both of the sampled frames
 */
inline bool checkPathCorrespondence(std::vector<gsl::span<const carb::flatcache::Path>> paths, size_t index, size_t& pos_f0, size_t& pos_f1)
{
    if (paths.size() > 1)
    {
        // in the common case, the prim exists in both frames
        if ((index < paths[1].size()) && (paths[0][index] == paths[1][index]))
        {
            pos_f0 = pos_f1 = index;
            return true;
        }

        auto pathIt = std::find(paths[1].begin(), paths[1].end(), paths[0][index]);
        if (pathIt != paths[1].end())
        {
            pos_f0 = index; // TODO: this isn't needed, can infer it
            pos_f1 = std::distance(paths[1].begin(), pathIt);

            return true;
        }
    }

    return false;
}


/**
 * @brief Returned by StageAtTime.getAttributeArrayRd
 *
 * @details Holds at most two samples (one from frame n, and one from frame n+1)
 *          checkPathCorrespondence verifies if the path in frame n exists in frame n+1
 *          If no corresponding path exists, the value will be returned and not interpolated
 */
template <typename T>
class AttributeArrayResult
{
public:
    size_t size() const
    {
        return m_samples[0].size();
    }

    bool empty() const
    {
        return (size() == 0);
    }

    std::vector<gsl::span<const T>> const* data() const
    {
        return &m_samples;
    }

    std::vector<gsl::span<const T>>* data()
    {
        return &m_samples;
    }

    T operator[](const size_t valueIndex) const
    {
        {
            if (valueIndex >= m_samples[0].size() || m_samples[0].empty())
            {
                CARB_LOG_WARN_ONCE("AttributeArrayResult[] out of bounds");
                return T();
            }

            if (m_samples.size() == 1)
            {
                return m_samples[0][valueIndex];
            }
            else if (m_samples.size() == 2)
            {
                size_t pos0, pos1;
                if (checkPathCorrespondence(m_paths, valueIndex, pos0, pos1))
                {
                    T a = (m_samples[0][pos0]);
                    T b = (m_samples[1][pos1]);

                    T result = interpolate<T>(a, b, m_theta);

                    return result;
                }

                return m_samples[0][valueIndex];
            }
        }

        return T();
    };

    std::vector<gsl::span<const carb::flatcache::Path>> m_paths;
    std::vector<gsl::span<const T>> m_samples;
    float m_theta;
};

/**
 * @brief Returned by StageAtTime.getArrayAttributeArrayRd
 *
 * @details Enables access to a vector of readily interpolated attribute values
 */
template <typename T>
class AttributeArrayResult<std::vector<T>>
{
public:
    size_t size() const
    {
        return m_samples[0].size();
    }

    bool empty() const
    {
        return (size() == 0);
    }

    std::vector<std::vector<gsl::span<const T>>> const* data() const
    {
        return m_samples;
    }

    std::vector<std::vector<gsl::span<const T>>>* data()
    {
        return m_samples;
    }

    std::vector<T> operator[](const size_t primIndex)
    {
        std::vector<T> interpolatedAttributeValues;

        if (m_samples.size() == 1)
        {
            interpolatedAttributeValues.resize(m_samples[0][primIndex].size());
            std::copy(m_samples[0][primIndex].begin(), m_samples[0][primIndex].end(), interpolatedAttributeValues.begin());

            return interpolatedAttributeValues;
        }
        else if (m_samples.size() == 2)
        {
            size_t pos0, pos1;
            if (checkPathCorrespondence(m_paths, primIndex, pos0, pos1))
            {
                auto values_f0 = m_samples[0][primIndex];
                auto values_f1 = m_samples[1][primIndex];

                interpolatedAttributeValues.reserve(values_f0.size());

                // interpolate attrib values for the requested {prim index : attrib val index}
                for (size_t valueIndex = 0; valueIndex < values_f0.size(); ++valueIndex)
                {
                    T a = (values_f0[valueIndex]);
                    T b = (values_f1[valueIndex]);
                    T result = interpolate<T>(a, b, m_theta);
                    interpolatedAttributeValues.emplace_back(result);
                }

                return interpolatedAttributeValues;
            }

            interpolatedAttributeValues.resize(m_samples[0][primIndex].size());
            std::copy(m_samples[0][primIndex].begin(), m_samples[0][primIndex].end(), interpolatedAttributeValues.begin());

            return interpolatedAttributeValues;
        }
        return std::vector<T>();  
    }

    std::vector<gsl::span<const carb::flatcache::Path>> m_paths;
    std::vector<std::vector<gsl::span<const T>>> m_samples;
    float m_theta;
};

template <typename T>
AttributeArrayResult<T> StageAtTime::getAttributeArrayRd(const PrimBucketList& primBucketList,
                                                         size_t primBucketListIndex,
                                                         const Token& attrName) const
{
    size_t sampleCount = m_historyWindow.getTimeSampleCount();

    if (sampleCount > 0)
    {
        AttributeArrayResult<T> arrAttRes;
        arrAttRes.m_samples = m_historyWindow.getAttributeArrayRd<T>(primBucketList, primBucketListIndex, attrName);
        arrAttRes.m_paths = m_historyWindow.getPathArray(primBucketList, primBucketListIndex);

        arrAttRes.m_theta = (float)m_theta;

        return arrAttRes;
    }
    else
    {
        CARB_LOG_WARN_ONCE(
            "getAttributeArrayRd %s: Data not available at time, possible dropped frame", attrName.getText());
        return AttributeArrayResult<T>();
    }
}

template <typename T>
AttributeArrayResult<T> StageAtTime::getAttributeArrayRdGpu(const PrimBucketList& primBucketList,
                                                            size_t primBucketListIndex,
                                                            const Token& attrName) const
{
    size_t sampleCount = m_historyWindow.getTimeSampleCount();

    if (sampleCount > 0)
    {
        AttributeArrayResult<T> arrAttRes;
        arrAttRes.m_samples = m_historyWindow.getAttributeArrayRdGpu<T>(primBucketList, primBucketListIndex, attrName);
        arrAttRes.m_paths = m_historyWindow.getPathArray(primBucketList, primBucketListIndex);

        arrAttRes.m_theta = (float)m_theta;

        return arrAttRes;
    }
    else
    {
        CARB_LOG_WARN_ONCE(
            "getAttributeArrayRdGpu %s: Data not available at time, possible dropped frame", attrName.getText());
        return AttributeArrayResult<T>();
    }
}

inline std::vector<gsl::span<const char>> StageAtTime::getAttributeArrayRawRd(const PrimBucketList& primBucketList,
                                                                              size_t primBucketListIndex,
                                                                              const Token& attrName) const
{
    return m_historyWindow.getAttributeArrayRawRd(primBucketList, primBucketListIndex, attrName);
}


template <typename T>
AttributeArrayResult<std::vector<T>> StageAtTime::getArrayAttributeArrayRd(
                                                                   const PrimBucketList& primBucketList,
                                                                   size_t primBucketListIndex,
                                                                   const Token& attrName) const
{
    size_t sampleCount = m_historyWindow.getTimeSampleCount();
    AttributeArrayResult<std::vector<T>> result;
   
    if (sampleCount > 0)
    {
        result.m_samples = m_historyWindow.getArrayAttributeArrayRd<T>(primBucketList, primBucketListIndex, attrName);
        result.m_paths = m_historyWindow.getPathArray(primBucketList, primBucketListIndex);
        result.m_theta = (float)m_theta;

        return result;
    }
    else
    {
        CARB_LOG_WARN_ONCE(
            "getAttributeArrayRd %s: Data not available at time, possible dropped frame", attrName.getText());
        return AttributeArrayResult<std::vector<T>>();
    }
}

inline gsl::span<const Path> StageAtTime::getPathArray(const PrimBucketList& primBucketList,
                                                       size_t primBucketListIndex) const
{
    size_t sampleCount = m_historyWindow.getTimeSampleCount();

    if (sampleCount == 1)
    {
        return m_historyWindow.getPathArray(primBucketList, primBucketListIndex)[0];
    }
    else if (sampleCount == 0)
    {
        CARB_LOG_WARN_ONCE("getPathArray: Data not available at time, possible dropped frame");
        return gsl::span<const Path>();
    }
    else if (sampleCount == 2)
    {
        // TODO: make this correct when prims are being added and deleted
        // To do this we need to make a new array out:
        //    out[i] = in0[i]            , if in0[i] == in1[i]
        //           = kUninitializedPath, otherwise
        return m_historyWindow.getPathArray(primBucketList, primBucketListIndex)[0];
#if 0
        gsl::span<const Path> in0 = m_historyWindow.getPathArray(primBucketList, primBucketListIndex)[0];
        gsl::span<const Path> in1 = m_historyWindow.getPathArray(primBucketList, primBucketListIndex)[1];
        std::vector<Path> multiframePaths;

        for (size_t i = 0; i < in0.size(); ++i)
            in0[i] == in1[i] ? multiframePaths.emplace_back(in0[i]) : multiframePaths.emplace_back(flatcache::kUninitializedPath);

        return multiframePaths;
#endif
    }

    return gsl::span<const Path>();
}

inline std::vector<const Connection*> StageAtTime::getConnectionRd(const Path& path, const Token& connectionName)
{
    return m_historyWindow.getConnectionRd(path, connectionName);
}

inline void StageAtTime::printBucketNames() const
{
    m_historyWindow.printBucketNames();
}

inline size_t StageAtTime::getAttributeCount(const PrimBucketList& primBucketList, size_t primBucketListIndex) const
{
    std::vector<size_t> counts = m_historyWindow.getAttributeCounts(primBucketList, primBucketListIndex);
    if (counts.size() == 1)
    {
        return counts[0];
    }
    // Perform a set intersection to get a valid count size;
    if (counts.size() == 2)
    {
        //
        // TODO: The attributes are internally sorted vectors, see flatcache::set.
        // Ideally we'd make a C-ABI type that makes it clear that these are sorted,
        // wrap with flatcache::set in the C++ wrapper and then use the standard library set intersection.
        //
        auto namesAndTypes = m_historyWindow.getAttributeNamesAndTypes(primBucketList, primBucketListIndex);
        const std::vector<std::vector<Token>>& names = namesAndTypes.first;
        const std::vector<std::vector<Type>>& types = namesAndTypes.second;
        std::vector<Token> intersection;

        // Perform a set intersection but we need to track the types as we intersect
        const std::vector<Token>& workingNames = names[0].size() < names[1].size() ? names[0] : names[1];
        const std::vector<Type>& workingTypes = names[0].size() < names[1].size() ? types[0] : types[1];
        const std::vector<Token>& testingNames = names[0].size() < names[1].size() ? names[1] : names[0];
        const std::vector<Type>& testingTypes = names[0].size() < names[1].size() ? types[1] : types[0];

        // Since attribute vectors are sorted we can track last spotted locations to be more efficient.
        size_t last = 0;
        for (size_t i = 0; i < workingNames.size(); ++i)
        {
            for (size_t j = last; j < testingNames.size(); ++j)
            {
                if (workingNames[i] == testingNames[j])
                {
                    if (workingTypes[i] == testingTypes[j])
                    {
                        intersection.push_back(workingNames[i]);
                    }
                    // Store hit location to start next search
                    last = j;
                    break;
                }
            }
        }
        return intersection.size();
    }
    return 0;
}

inline std::pair<std::vector<Token>, std::vector<Type>> StageAtTime::getAttributeNamesAndTypes(
    const PrimBucketList& primBucketList, size_t primBucketListIndex) const
{
    std::vector<Token> outNames;
    std::vector<Type> outTypes;

    std::vector<std::vector<Token>> names;
    std::vector<std::vector<Type>> types;

    std::tie(names, types) = m_historyWindow.getAttributeNamesAndTypes(primBucketList, primBucketListIndex);
    if (names.size() == 1)
    {
        outNames = std::move(names[0]);
        outTypes = std::move(types[0]);
    }
    if (names.size() == 2)
    {
        // Assuming that the invariant that names and types of the same slot are the same count holds.
        outNames.reserve(std::min(names[0].size(), names[1].size()));
        outTypes.reserve(std::min(types[0].size(), types[1].size()));

        // Perform a set intersection but we need to track the types as we intersect
        std::vector<Token>& workingNames = names[0].size() < names[1].size() ? names[0] : names[1];
        std::vector<Type>& workingTypes = names[0].size() < names[1].size() ? types[0] : types[1];
        std::vector<Token>& testingNames = names[0].size() < names[1].size() ? names[1] : names[0];
        std::vector<Type>& testingTypes = names[0].size() < names[1].size() ? types[1] : types[0];

        // Since attribute vectors are sorted we can track last spotted locations to be more efficient.
        size_t last = 0;
        for (size_t i = 0; i < workingNames.size(); ++i)
        {
            for (size_t j = last; j < testingNames.size(); ++j)
            {
                if (workingNames[i] == testingNames[j])
                {
                    if (workingTypes[i] == testingTypes[j])
                    {
                        outNames.push_back(workingNames[i]);
                        outTypes.push_back(workingTypes[i]);
                    }
                    // Store hit location to start next search
                    last = j;
                    break;
                }
            }
        }
    }
    return { outNames, outTypes };
}


inline uint64_t StageAtTime::writeCacheToDisk(const char* file, uint8_t* workingBuffer, uint64_t workingBufferSize) const
{
    size_t sampleCount = m_historyWindow.getTimeSampleCount();

    if (sampleCount != 1)
    {
        CARB_LOG_ERROR_ONCE("Can't call StageAtTime::WriteCacheToDisk for interpolated values");
        return 0;
    }

    return m_historyWindow.writeCacheToDisk(file, workingBuffer, workingBufferSize);
}

inline void StageAtTime::addRefCount()
{
    m_historyWindow.addRefCount();
}

inline bool StageAtTime::removeRefCount()
{
    return m_historyWindow.removeRefCount();
}

inline unsigned int StageAtTime::getRefCount()
{
    return m_historyWindow.getRefCount();
}

// StageWithHistory implementation starts here

inline StageWithHistory::StageWithHistory(UsdStageId usdStageId, size_t historyFrameCount, RationalTime simPeriod, bool withCuda)
{
    auto iStageWithHistory = carb::getCachedInterface<carb::flatcache::IStageWithHistory>();
    m_stageWithHistory = iStageWithHistory->create2(usdStageId, historyFrameCount, simPeriod, withCuda);
    m_usdStageId = usdStageId;
}

inline StageWithHistory::~StageWithHistory()
{
    auto iStageWithHistory = carb::getCachedInterface<carb::flatcache::IStageWithHistory>();
    iStageWithHistory->destroy(m_usdStageId);
}

inline ListenerId StageWithHistory::createListener()
{
    auto iChangeTrackerConfig = carb::getCachedInterface<carb::flatcache::IStageWithHistory>();
    ListenerId newId = iChangeTrackerConfig->createListener();
    return newId;
}

// Templated methods do not get compiled unless they are instantiated.
// The following code is not intended to be executed, it just instantiates each
// templated method once to make sure that they compile.
inline void instantiationTest(StageInProgress& stage,
                              StageAtTimeInterval& stageAtInterval,
                              StageAtTime& stageAtTime,
                              const Path& path,
                              const Token& attrName)
{
    int* x0 = stage.getAttribute<int>(path, attrName);
    CARB_UNUSED(x0);
    const int* x1 = stage.getAttributeRd<int>(path, attrName);
    CARB_UNUSED(x1);
    int* x2 = stage.getAttributeWr<int>(path, attrName);
    CARB_UNUSED(x2);
    gsl::span<int> x3 = stage.getArrayAttribute<int>(path, attrName);
    CARB_UNUSED(x3);
    gsl::span<const int> x4 = stage.getArrayAttributeRd<int>(path, attrName);
    CARB_UNUSED(x4);
    gsl::span<int> x5 = stage.getArrayAttributeWr<int>(path, attrName);
    CARB_UNUSED(x5);
    PrimBucketList pbl = stage.findPrims({}, {}, {});
    gsl::span<int> x6 = stage.getAttributeArray<int>(pbl, 0, attrName);
    CARB_UNUSED(x6);
    std::vector<const int*> x7 = stageAtInterval.getAttributeRd<int>(path, attrName);
    CARB_UNUSED(x7);
    std::vector<gsl::span<const int>> x8 = stageAtInterval.getAttributeArrayRd<int>(pbl, 0, attrName);
    CARB_UNUSED(x8);
    optional<float> x9 = stageAtTime.getAttributeRd<float>(path, attrName);
    CARB_UNUSED(x9);
    optional<std::pair<optional<int>, optional<int>>> x10 = stageAtTime.getAttributeRd <std::pair<optional<int>, optional<int>>>(path, attrName);
    CARB_UNUSED(x10);
    carb::flatcache::AttributeArrayResult<int> x11 = stageAtTime.getAttributeArrayRd<int>(pbl, 0, attrName);
    CARB_UNUSED(x11);
    carb::flatcache::AttributeArrayResult<std::vector<int>> x12 = stageAtTime.getArrayAttributeArrayRd<int>(pbl, 0, attrName);
    CARB_UNUSED(x12);
}

} // namespace flatcache
} // namespace carb
