<commit_before>/*************************************************************************
 *
 *  $RCSfile: XMLPropertyBackpatcher.cxx,v $
 *
 *  $Revision: 1.2 $
 *
 *  last change: $Author: dvo $ $Date: 2000-09-28 18:27:13 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef _COM_SUN_STAR_BEANS_XPROPERTYSET_HPP_
#include <com/sun/star/beans/XPropertySet.hpp>
#endif

#ifndef _COM_SUN_STAR_UNO_REFERENCE_H_
#include <com/sun/star/uno/Reference.h>
#endif

#ifndef _RTL_USTRING
#include <rtl/ustring>
#endif

#ifndef _TOOLS_DEBUG_HXX
#include <tools/debug.hxx>
#endif

#ifndef _XMLOFF_XMLPROPERTYBACKPATCHER_HXX
#include "XMLPropertyBackpatcher.hxx"
#endif

using ::rtl::OUString;
using ::std::vector;
using ::std::map;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Any;
using ::com::sun::star::beans::XPropertySet;

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const ::rtl::OUString& sPropName) :
        sPropertyName(sPropName),
        bDefaultHandling(sal_False),
        bPreserveProperty(sal_False),
        sPreservePropertyName()
{
}

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const OUString& sPropName,
    const OUString& sPreserveName,
    sal_Bool bDefault,
    A aDef) :
        sPropertyName(sPropName),
        bDefaultHandling(bDefault),
        aDefault(aDef),
        sPreservePropertyName(sPreserveName),
        bPreserveProperty(sPreserveName.getLength()>0)
{
}

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const sal_Char* pPropName) :
        sPropertyName(),
        bDefaultHandling(sal_False),
        bPreserveProperty(sal_False),
        sPreservePropertyName()
{
    DBG_ASSERT(pPropName != NULL, "need property name");
    sPropertyName = OUString::createFromAscii(pPropName);
}

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const sal_Char* pPropName,
    const sal_Char* pPreservePropName,
    sal_Bool bDefault,
    A aDef) :
        sPropertyName(),
        sPreservePropertyName(),
        bDefaultHandling(bDefault),
        bPreserveProperty(pPreservePropName != NULL),
        aDefault(aDef)
{
    DBG_ASSERT(pPropName != NULL, "need property name");
    sPropertyName = OUString::createFromAscii(pPropName);
    if (pPreservePropName != NULL)
    {
        sPreservePropertyName = OUString::createFromAscii(pPreservePropName);
    }
}

template<class A>
XMLPropertyBackpatcher<A>::~XMLPropertyBackpatcher()
{
    SetDefault();
}

template<class A>
void XMLPropertyBackpatcher<A>::ResolveId(
    const OUString& sName,
    A aValue)
{
    // insert ID into ID map
    aIDMap[sName] = aValue;

    // backpatch old references, if backpatch list exists
    if (aBackpatchListMap.count(sName))
    {
        // aah, we have a backpatch list!
        BackpatchListType* pList =
            (BackpatchListType*)aBackpatchListMap[sName];

        // a) remove list from list map
        aBackpatchListMap.erase(sName);

        // b) for every item, set SequenceNumber
        //    (and preserve Property, if appropriate)
        Any aAny;
        aAny <<= aValue;
        if (bPreserveProperty)
        {
            // preserve version
            for(BackpatchListType::iterator aIter = pList->begin();
                aIter != pList->end();
                aIter++)
            {
                Reference<XPropertySet> xProp = (*aIter);
                Any aPres = xProp->getPropertyValue(sPreservePropertyName);
                xProp->setPropertyValue(sPropertyName, aAny);
                xProp->setPropertyValue(sPreservePropertyName, aPres);
            }
        }
        else
        {
            // without preserve
            for(BackpatchListType::iterator aIter = pList->begin();
                aIter != pList->end();
                aIter++)
            {
                (*aIter)->setPropertyValue(sPropertyName, aAny);
            }
        }

        // c) delete list
        delete pList;
    }
    // else: no backpatch list -> then we're finished
}

template<class A>
void XMLPropertyBackpatcher<A>::SetProperty(
    const Reference<XPropertySet> & xPropSet,
    const OUString& sName)
{
    Reference<XPropertySet> xNonConstPropSet(xPropSet);
    SetProperty(xNonConstPropSet, sName);
}

template<class A>
void XMLPropertyBackpatcher<A>::SetProperty(
    Reference<XPropertySet> & xPropSet,
    const OUString& sName)
{
    if (aIDMap.count(sName))
    {
        // we know this ID -> set property
        Any aAny;
        aAny <<= aIDMap[sName];
        xPropSet->setPropertyValue(sPropertyName, aAny);
    }
    else
    {
        // ID unknown -> into backpatch list for later fixup
        if (! aBackpatchListMap.count(sName))
        {
            // create backpatch list for this name
            BackpatchListType* pTmp = new BackpatchListType() ;
            aBackpatchListMap[sName] = (void*)pTmp;
        }

        // insert footnote
        ((BackpatchListType*)aBackpatchListMap[sName])->push_back(xPropSet);
    }
}

template<class A>
void XMLPropertyBackpatcher<A>::SetDefault()
{
    if (bDefaultHandling)
    {
        // not implemented yet
    }
}

// force instantiation of templates
template XMLPropertyBackpatcher<sal_Int16>;
template XMLPropertyBackpatcher<OUString>;
<commit_msg>- fixed: wrong syntax for explicit template instantiation<commit_after>/*************************************************************************
 *
 *  $RCSfile: XMLPropertyBackpatcher.cxx,v $
 *
 *  $Revision: 1.3 $
 *
 *  last change: $Author: dvo $ $Date: 2000-10-04 21:16:49 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef _COM_SUN_STAR_BEANS_XPROPERTYSET_HPP_
#include <com/sun/star/beans/XPropertySet.hpp>
#endif

#ifndef _COM_SUN_STAR_UNO_REFERENCE_H_
#include <com/sun/star/uno/Reference.h>
#endif

#ifndef _RTL_USTRING
#include <rtl/ustring>
#endif

#ifndef _TOOLS_DEBUG_HXX
#include <tools/debug.hxx>
#endif

#ifndef _XMLOFF_XMLPROPERTYBACKPATCHER_HXX
#include "XMLPropertyBackpatcher.hxx"
#endif

using ::rtl::OUString;
using ::std::vector;
using ::std::map;
using ::com::sun::star::uno::Reference;
using ::com::sun::star::uno::Any;
using ::com::sun::star::beans::XPropertySet;

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const ::rtl::OUString& sPropName) :
        sPropertyName(sPropName),
        bDefaultHandling(sal_False),
        bPreserveProperty(sal_False),
        sPreservePropertyName()
{
}

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const OUString& sPropName,
    const OUString& sPreserveName,
    sal_Bool bDefault,
    A aDef) :
        sPropertyName(sPropName),
        bDefaultHandling(bDefault),
        aDefault(aDef),
        sPreservePropertyName(sPreserveName),
        bPreserveProperty(sPreserveName.getLength()>0)
{
}

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const sal_Char* pPropName) :
        sPropertyName(),
        bDefaultHandling(sal_False),
        bPreserveProperty(sal_False),
        sPreservePropertyName()
{
    DBG_ASSERT(pPropName != NULL, "need property name");
    sPropertyName = OUString::createFromAscii(pPropName);
}

template<class A>
XMLPropertyBackpatcher<A>::XMLPropertyBackpatcher(
    const sal_Char* pPropName,
    const sal_Char* pPreservePropName,
    sal_Bool bDefault,
    A aDef) :
        sPropertyName(),
        sPreservePropertyName(),
        bDefaultHandling(bDefault),
        bPreserveProperty(pPreservePropName != NULL),
        aDefault(aDef)
{
    DBG_ASSERT(pPropName != NULL, "need property name");
    sPropertyName = OUString::createFromAscii(pPropName);
    if (pPreservePropName != NULL)
    {
        sPreservePropertyName = OUString::createFromAscii(pPreservePropName);
    }
}

template<class A>
XMLPropertyBackpatcher<A>::~XMLPropertyBackpatcher()
{
    SetDefault();
}

template<class A>
void XMLPropertyBackpatcher<A>::ResolveId(
    const OUString& sName,
    A aValue)
{
    // insert ID into ID map
    aIDMap[sName] = aValue;

    // backpatch old references, if backpatch list exists
    if (aBackpatchListMap.count(sName))
    {
        // aah, we have a backpatch list!
        BackpatchListType* pList =
            (BackpatchListType*)aBackpatchListMap[sName];

        // a) remove list from list map
        aBackpatchListMap.erase(sName);

        // b) for every item, set SequenceNumber
        //    (and preserve Property, if appropriate)
        Any aAny;
        aAny <<= aValue;
        if (bPreserveProperty)
        {
            // preserve version
            for(BackpatchListType::iterator aIter = pList->begin();
                aIter != pList->end();
                aIter++)
            {
                Reference<XPropertySet> xProp = (*aIter);
                Any aPres = xProp->getPropertyValue(sPreservePropertyName);
                xProp->setPropertyValue(sPropertyName, aAny);
                xProp->setPropertyValue(sPreservePropertyName, aPres);
            }
        }
        else
        {
            // without preserve
            for(BackpatchListType::iterator aIter = pList->begin();
                aIter != pList->end();
                aIter++)
            {
                (*aIter)->setPropertyValue(sPropertyName, aAny);
            }
        }

        // c) delete list
        delete pList;
    }
    // else: no backpatch list -> then we're finished
}

template<class A>
void XMLPropertyBackpatcher<A>::SetProperty(
    const Reference<XPropertySet> & xPropSet,
    const OUString& sName)
{
    Reference<XPropertySet> xNonConstPropSet(xPropSet);
    SetProperty(xNonConstPropSet, sName);
}

template<class A>
void XMLPropertyBackpatcher<A>::SetProperty(
    Reference<XPropertySet> & xPropSet,
    const OUString& sName)
{
    if (aIDMap.count(sName))
    {
        // we know this ID -> set property
        Any aAny;
        aAny <<= aIDMap[sName];
        xPropSet->setPropertyValue(sPropertyName, aAny);
    }
    else
    {
        // ID unknown -> into backpatch list for later fixup
        if (! aBackpatchListMap.count(sName))
        {
            // create backpatch list for this name
            BackpatchListType* pTmp = new BackpatchListType() ;
            aBackpatchListMap[sName] = (void*)pTmp;
        }

        // insert footnote
        ((BackpatchListType*)aBackpatchListMap[sName])->push_back(xPropSet);
    }
}

template<class A>
void XMLPropertyBackpatcher<A>::SetDefault()
{
    if (bDefaultHandling)
    {
        // not implemented yet
    }
}

// force instantiation of templates
template class XMLPropertyBackpatcher<sal_Int16>;
template class XMLPropertyBackpatcher<OUString>;

<|endoftext|>


<commit_before>#include <maya/MFnMesh.h>
#include <maya/MFnMeshData.h>
#include <maya/MEulerRotation.h>
#include <maya/MQuaternion.h>
#include <maya/MFnArrayAttrsData.h>
#include <maya/MArrayDataBuilder.h>

#include "Asset.h"
#include "AssetNode.h"
#include "OutputInstancerObject.h"
#include "util.h"

OutputInstancerObject::OutputInstancerObject(
        int assetId,
        int objectId,
        Asset* objectControl
        ) :
    OutputObject(
            assetId,
            objectId,
            objectControl
          ),
    myGeoInfo(HAPI_GeoInfo_Create())
{
    //update();
}

OutputInstancerObject::~OutputInstancerObject() {}

OutputObject::ObjectType
OutputInstancerObject::type()
{
    return OutputObject::OBJECT_TYPE_INSTANCER;
}

MStringArray
OutputInstancerObject::getAttributeStringData(HAPI_AttributeOwner owner, MString name)
{
    return Util::getAttributeStringData(myAssetId, myObjectId, 0, 0, owner, name);
}

void
OutputInstancerObject::update()
{
    try
    {
        HAPI_Result hstat = HAPI_RESULT_SUCCESS;
        hstat = HAPI_GetGeoInfo(myAssetId, myObjectId, 0, &myGeoInfo);
        Util::checkHAPIStatus(hstat);
    }
    catch (HAPIError& e)
    {
        cerr << e.what() << endl;
        return;
    }

    if(myNeverBuilt || myGeoInfo.hasGeoChanged)
    {
        // clear the arrays
        myInstancedObjectNames.clear();
        myInstancedObjectIndices.clear();
        myUniqueInstObjNames.clear();
        myHoudiniInstanceAttribute.clear();
        myHoudiniNameAttribute.clear();

        try
        {
            if(myGeoInfo.partCount <= 0)
                return;

            HAPI_Result hstat = HAPI_RESULT_SUCCESS;
            hstat = HAPI_GetPartInfo(myAssetId, myObjectId, 0, 0, &myPartInfo);
            Util::checkHAPIStatus(hstat);
        }
        catch (HAPIError& e)
        {
            cerr << e.what() << endl;
        }

        if(myObjectInfo.objectToInstanceId >= 0)
        {
            myInstancedObjectIndices = MIntArray(myPartInfo.pointCount, 0);
            return;
        }

        // fill array of size pointCount of instanced names
        MStringArray instanceAttrs = getAttributeStringData(HAPI_ATTROWNER_POINT, "instance");
        for(unsigned int i=0; i<instanceAttrs.length(); i++)
        {
            MStringArray splitObjName;
            instanceAttrs[i].split('/', splitObjName);
            myInstancedObjectNames.append(splitObjName[splitObjName.length()-1]);
            myHoudiniInstanceAttribute.append(instanceAttrs[i]);
        }

        MStringArray nameAttrs = getAttributeStringData(HAPI_ATTROWNER_POINT, "name");
        for(unsigned int ii = 0; ii < nameAttrs.length(); ii++)
        {
            myHoudiniNameAttribute.append(nameAttrs[ii]);
        }

        // get a list of unique instanced names, and compute the object indices that would
        // be passed to Maya instancer
        for(unsigned int i = 0; i< myInstancedObjectNames.length(); ++i)
        {
            bool duplicate = false;
            unsigned int j = 0;
            for(; j< myUniqueInstObjNames.length(); ++j)
            {
                if(myUniqueInstObjNames[j] == myInstancedObjectNames[i])
                {
                    duplicate = true;
                    break;
                }
            }
            if(!duplicate)
                myUniqueInstObjNames.append(myInstancedObjectNames[i]);
            myInstancedObjectIndices.append((int) j);
        }

        // Workaround a crash where we can't determine the object to instance.
        if(!myInstancedObjectNames.length())
        {
            myInstancedObjectIndices = MIntArray(myPartInfo.pointCount, -1);
        }
    }
}

MIntArray
OutputInstancerObject::getInstancedObjIds()
{
    MIntArray ret;
    if(myObjectInfo.objectToInstanceId >= 0)
        ret.append(myObjectInfo.objectToInstanceId);
    return ret;
}

MStringArray
OutputInstancerObject::getUniqueInstObjNames()
{
    return myUniqueInstObjNames;
}

MStatus
//OutputInstancerObject::compute(const MPlug& plug, MDataBlock& data)
OutputInstancerObject::compute(
        MDataHandle& handle,
        bool &needToSyncOutputs
        )
{
    update();

    if(myGeoInfo.partCount <= 0)
        return MS::kFailure;

    if(myNeverBuilt || myGeoInfo.hasGeoChanged)
    {
        MDataHandle instancerDataHandle = handle.child(AssetNode::outputInstancerData);
        MArrayDataHandle instancedObjectNamesHandle = handle.child(AssetNode::outputInstancedObjectNames);
        MArrayDataHandle houdiniInstanceAttributeHandle = handle.child(AssetNode::outputHoudiniInstanceAttribute);
        MArrayDataHandle houdiniNameAttributeHandle = handle.child(AssetNode::outputHoudiniNameAttribute);
        MArrayDataHandle instanceTransformHandle = handle.child(AssetNode::outputInstanceTransform);

        //MDataHandle instHandle = data.outputValue(instancerDataPlug);
        MFnArrayAttrsData fnAAD;
        MObject instOutput = fnAAD.create();
        MVectorArray positions = fnAAD.vectorArray("position");
        MVectorArray rotations = fnAAD.vectorArray("rotation");
        MVectorArray scales = fnAAD.vectorArray("scale");
        MIntArray objectIndices = fnAAD.intArray("objectIndex");

        unsigned int size = myPartInfo.pointCount;
        HAPI_Transform * instTransforms = new HAPI_Transform[size];
        HAPI_GetInstanceTransforms(myAssetId, myObjectInfo.id, 0, HAPI_SRT, instTransforms, 0, size);

        MArrayDataBuilder houdiniInstanceAttributeBuilder = houdiniInstanceAttributeHandle.builder();
        MArrayDataBuilder houdiniNameAttributeBuilder = houdiniNameAttributeHandle.builder();
        MArrayDataBuilder instanceTransformBuilder = instanceTransformHandle.builder();

        for(unsigned int j=0; j<size; j++)
        {
            HAPI_Transform it = instTransforms[j];
            MVector p(it.position[0], it.position[1], it.position[2]);
            MVector r = MQuaternion(it.rotationQuaternion[0],
                    it.rotationQuaternion[1], it.rotationQuaternion[2],
                    it.rotationQuaternion[3]).asEulerRotation().asVector();
            MVector s(it.scale[0], it.scale[1], it.scale[2]);

            int objIndex = myInstancedObjectIndices[j];

            positions.append(p);
            rotations.append(r);
            scales.append(s);
            objectIndices.append(objIndex);

            if(myHoudiniInstanceAttribute.length() == size)
            {
                MDataHandle intanceAttributeHandle = houdiniInstanceAttributeBuilder.addElement(j);
                intanceAttributeHandle.set(myHoudiniInstanceAttribute[j]);
            }

            if(myHoudiniNameAttribute.length() == size)
            {
                MDataHandle nameAttributeHandle = houdiniNameAttributeBuilder.addElement(j);
                nameAttributeHandle.set(myHoudiniNameAttribute[j]);
            }

            MDataHandle transformHandle = instanceTransformBuilder.addElement(j);
            MDataHandle translateHandle = transformHandle.child(AssetNode::outputInstanceTranslate);
            MDataHandle rotateHandle = transformHandle.child(AssetNode::outputInstanceRotate);
            MDataHandle scaleHandle = transformHandle.child(AssetNode::outputInstanceScale);

            MDataHandle txHandle = translateHandle.child(AssetNode::outputInstanceTranslateX);
            txHandle.set(p.x);
            MDataHandle tyHandle = translateHandle.child(AssetNode::outputInstanceTranslateY);
            tyHandle.set(p.y);
            MDataHandle tzHandle = translateHandle.child(AssetNode::outputInstanceTranslateZ);
            tzHandle.set(p.z);

            MDataHandle rxHandle = rotateHandle.child(AssetNode::outputInstanceRotateX);
            rxHandle.set(r.x);
            MDataHandle ryHandle = rotateHandle.child(AssetNode::outputInstanceRotateY);
            ryHandle.set(r.y);
            MDataHandle rzHandle = rotateHandle.child(AssetNode::outputInstanceRotateZ);
            rzHandle.set(r.z);

            MDataHandle sxHandle = scaleHandle.child(AssetNode::outputInstanceScaleX);
            sxHandle.set(s.x);
            MDataHandle syHandle = scaleHandle.child(AssetNode::outputInstanceScaleY);
            syHandle.set(s.y);
            MDataHandle szHandle = scaleHandle.child(AssetNode::outputInstanceScaleZ);
            szHandle.set(s.z);
        }

        houdiniInstanceAttributeHandle.set(houdiniInstanceAttributeBuilder);
        houdiniInstanceAttributeHandle.setAllClean();

        houdiniNameAttributeHandle.set(houdiniNameAttributeBuilder);
        houdiniNameAttributeHandle.setAllClean();

        instanceTransformHandle.set(instanceTransformBuilder);
        instanceTransformHandle.setAllClean();

        delete[] instTransforms;

        instancerDataHandle.set(instOutput);

        MArrayDataBuilder builder = instancedObjectNamesHandle.builder();
        if(myObjectInfo.objectToInstanceId >= 0)
        {
            // instancing a single object
            OutputObject* objToInstance = myObjectControl->findObjectById(myObjectInfo.objectToInstanceId);
            MString name = objToInstance->getName();

            MDataHandle h = builder.addElement(0);
            h.set(name);
        } else
        {
            // instancing multiple objects
            for(unsigned int i=0; i< myUniqueInstObjNames.length(); i++)
            {
                MDataHandle h = builder.addElement(i);
                h.set(myUniqueInstObjNames[i]);
            }

            // clean up extra elements
            int builderSizeCheck = builder.elementCount();
            if(builderSizeCheck > (int) myUniqueInstObjNames.length())
            {
                for(int i= myUniqueInstObjNames.length(); i<builderSizeCheck; i++)
                {
                    builder.removeElement(i);
                }
            }
        }
        instancedObjectNamesHandle.set(builder);
        instancedObjectNamesHandle.setAllClean();

        handle.setClean();
        instancerDataHandle.setClean();
        //data.setClean(plug);
        //data.setClean(instancerDataPlug);

        myNeverBuilt = false;
    }

    return MS::kSuccess;
}
<commit_msg>Use HAPI_GetObjects to get instance object name<commit_after>#include <maya/MFnMesh.h>
#include <maya/MFnMeshData.h>
#include <maya/MEulerRotation.h>
#include <maya/MQuaternion.h>
#include <maya/MFnArrayAttrsData.h>
#include <maya/MArrayDataBuilder.h>

#include "Asset.h"
#include "AssetNode.h"
#include "OutputInstancerObject.h"
#include "util.h"

OutputInstancerObject::OutputInstancerObject(
        int assetId,
        int objectId,
        Asset* objectControl
        ) :
    OutputObject(
            assetId,
            objectId,
            objectControl
          ),
    myGeoInfo(HAPI_GeoInfo_Create())
{
    //update();
}

OutputInstancerObject::~OutputInstancerObject() {}

OutputObject::ObjectType
OutputInstancerObject::type()
{
    return OutputObject::OBJECT_TYPE_INSTANCER;
}

MStringArray
OutputInstancerObject::getAttributeStringData(HAPI_AttributeOwner owner, MString name)
{
    return Util::getAttributeStringData(myAssetId, myObjectId, 0, 0, owner, name);
}

void
OutputInstancerObject::update()
{
    try
    {
        HAPI_Result hstat = HAPI_RESULT_SUCCESS;
        hstat = HAPI_GetGeoInfo(myAssetId, myObjectId, 0, &myGeoInfo);
        Util::checkHAPIStatus(hstat);
    }
    catch (HAPIError& e)
    {
        cerr << e.what() << endl;
        return;
    }

    if(myNeverBuilt || myGeoInfo.hasGeoChanged)
    {
        // clear the arrays
        myInstancedObjectNames.clear();
        myInstancedObjectIndices.clear();
        myUniqueInstObjNames.clear();
        myHoudiniInstanceAttribute.clear();
        myHoudiniNameAttribute.clear();

        try
        {
            if(myGeoInfo.partCount <= 0)
                return;

            HAPI_Result hstat = HAPI_RESULT_SUCCESS;
            hstat = HAPI_GetPartInfo(myAssetId, myObjectId, 0, 0, &myPartInfo);
            Util::checkHAPIStatus(hstat);
        }
        catch (HAPIError& e)
        {
            cerr << e.what() << endl;
        }

        if(myObjectInfo.objectToInstanceId >= 0)
        {
            myInstancedObjectIndices = MIntArray(myPartInfo.pointCount, 0);
            return;
        }

        // fill array of size pointCount of instanced names
        MStringArray instanceAttrs = getAttributeStringData(HAPI_ATTROWNER_POINT, "instance");
        for(unsigned int i=0; i<instanceAttrs.length(); i++)
        {
            MStringArray splitObjName;
            instanceAttrs[i].split('/', splitObjName);
            myInstancedObjectNames.append(splitObjName[splitObjName.length()-1]);
            myHoudiniInstanceAttribute.append(instanceAttrs[i]);
        }

        MStringArray nameAttrs = getAttributeStringData(HAPI_ATTROWNER_POINT, "name");
        for(unsigned int ii = 0; ii < nameAttrs.length(); ii++)
        {
            myHoudiniNameAttribute.append(nameAttrs[ii]);
        }

        // get a list of unique instanced names, and compute the object indices that would
        // be passed to Maya instancer
        for(unsigned int i = 0; i< myInstancedObjectNames.length(); ++i)
        {
            bool duplicate = false;
            unsigned int j = 0;
            for(; j< myUniqueInstObjNames.length(); ++j)
            {
                if(myUniqueInstObjNames[j] == myInstancedObjectNames[i])
                {
                    duplicate = true;
                    break;
                }
            }
            if(!duplicate)
                myUniqueInstObjNames.append(myInstancedObjectNames[i]);
            myInstancedObjectIndices.append((int) j);
        }

        // Workaround a crash where we can't determine the object to instance.
        if(!myInstancedObjectNames.length())
        {
            myInstancedObjectIndices = MIntArray(myPartInfo.pointCount, -1);
        }
    }
}

MIntArray
OutputInstancerObject::getInstancedObjIds()
{
    MIntArray ret;
    if(myObjectInfo.objectToInstanceId >= 0)
        ret.append(myObjectInfo.objectToInstanceId);
    return ret;
}

MStringArray
OutputInstancerObject::getUniqueInstObjNames()
{
    return myUniqueInstObjNames;
}

MStatus
//OutputInstancerObject::compute(const MPlug& plug, MDataBlock& data)
OutputInstancerObject::compute(
        MDataHandle& handle,
        bool &needToSyncOutputs
        )
{
    update();

    if(myGeoInfo.partCount <= 0)
        return MS::kFailure;

    if(myNeverBuilt || myGeoInfo.hasGeoChanged)
    {
        MDataHandle instancerDataHandle = handle.child(AssetNode::outputInstancerData);
        MArrayDataHandle instancedObjectNamesHandle = handle.child(AssetNode::outputInstancedObjectNames);
        MArrayDataHandle houdiniInstanceAttributeHandle = handle.child(AssetNode::outputHoudiniInstanceAttribute);
        MArrayDataHandle houdiniNameAttributeHandle = handle.child(AssetNode::outputHoudiniNameAttribute);
        MArrayDataHandle instanceTransformHandle = handle.child(AssetNode::outputInstanceTransform);

        //MDataHandle instHandle = data.outputValue(instancerDataPlug);
        MFnArrayAttrsData fnAAD;
        MObject instOutput = fnAAD.create();
        MVectorArray positions = fnAAD.vectorArray("position");
        MVectorArray rotations = fnAAD.vectorArray("rotation");
        MVectorArray scales = fnAAD.vectorArray("scale");
        MIntArray objectIndices = fnAAD.intArray("objectIndex");

        unsigned int size = myPartInfo.pointCount;
        HAPI_Transform * instTransforms = new HAPI_Transform[size];
        HAPI_GetInstanceTransforms(myAssetId, myObjectInfo.id, 0, HAPI_SRT, instTransforms, 0, size);

        MArrayDataBuilder houdiniInstanceAttributeBuilder = houdiniInstanceAttributeHandle.builder();
        MArrayDataBuilder houdiniNameAttributeBuilder = houdiniNameAttributeHandle.builder();
        MArrayDataBuilder instanceTransformBuilder = instanceTransformHandle.builder();

        for(unsigned int j=0; j<size; j++)
        {
            HAPI_Transform it = instTransforms[j];
            MVector p(it.position[0], it.position[1], it.position[2]);
            MVector r = MQuaternion(it.rotationQuaternion[0],
                    it.rotationQuaternion[1], it.rotationQuaternion[2],
                    it.rotationQuaternion[3]).asEulerRotation().asVector();
            MVector s(it.scale[0], it.scale[1], it.scale[2]);

            int objIndex = myInstancedObjectIndices[j];

            positions.append(p);
            rotations.append(r);
            scales.append(s);
            objectIndices.append(objIndex);

            if(myHoudiniInstanceAttribute.length() == size)
            {
                MDataHandle intanceAttributeHandle = houdiniInstanceAttributeBuilder.addElement(j);
                intanceAttributeHandle.set(myHoudiniInstanceAttribute[j]);
            }

            if(myHoudiniNameAttribute.length() == size)
            {
                MDataHandle nameAttributeHandle = houdiniNameAttributeBuilder.addElement(j);
                nameAttributeHandle.set(myHoudiniNameAttribute[j]);
            }

            MDataHandle transformHandle = instanceTransformBuilder.addElement(j);
            MDataHandle translateHandle = transformHandle.child(AssetNode::outputInstanceTranslate);
            MDataHandle rotateHandle = transformHandle.child(AssetNode::outputInstanceRotate);
            MDataHandle scaleHandle = transformHandle.child(AssetNode::outputInstanceScale);

            MDataHandle txHandle = translateHandle.child(AssetNode::outputInstanceTranslateX);
            txHandle.set(p.x);
            MDataHandle tyHandle = translateHandle.child(AssetNode::outputInstanceTranslateY);
            tyHandle.set(p.y);
            MDataHandle tzHandle = translateHandle.child(AssetNode::outputInstanceTranslateZ);
            tzHandle.set(p.z);

            MDataHandle rxHandle = rotateHandle.child(AssetNode::outputInstanceRotateX);
            rxHandle.set(r.x);
            MDataHandle ryHandle = rotateHandle.child(AssetNode::outputInstanceRotateY);
            ryHandle.set(r.y);
            MDataHandle rzHandle = rotateHandle.child(AssetNode::outputInstanceRotateZ);
            rzHandle.set(r.z);

            MDataHandle sxHandle = scaleHandle.child(AssetNode::outputInstanceScaleX);
            sxHandle.set(s.x);
            MDataHandle syHandle = scaleHandle.child(AssetNode::outputInstanceScaleY);
            syHandle.set(s.y);
            MDataHandle szHandle = scaleHandle.child(AssetNode::outputInstanceScaleZ);
            szHandle.set(s.z);
        }

        houdiniInstanceAttributeHandle.set(houdiniInstanceAttributeBuilder);
        houdiniInstanceAttributeHandle.setAllClean();

        houdiniNameAttributeHandle.set(houdiniNameAttributeBuilder);
        houdiniNameAttributeHandle.setAllClean();

        instanceTransformHandle.set(instanceTransformBuilder);
        instanceTransformHandle.setAllClean();

        delete[] instTransforms;

        instancerDataHandle.set(instOutput);

        MArrayDataBuilder builder = instancedObjectNamesHandle.builder();
        if(myObjectInfo.objectToInstanceId >= 0)
        {
            // instancing a single object
            HAPI_ObjectInfo instanceObjectInfo;
            HAPI_GetObjects(
                    myAssetId,
                    &instanceObjectInfo,
                    myObjectInfo.objectToInstanceId, 1
                    );
            MString name = Util::getString(instanceObjectInfo.nameSH);

            MDataHandle h = builder.addElement(0);
            h.set(name);
        } else
        {
            // instancing multiple objects
            for(unsigned int i=0; i< myUniqueInstObjNames.length(); i++)
            {
                MDataHandle h = builder.addElement(i);
                h.set(myUniqueInstObjNames[i]);
            }

            // clean up extra elements
            int builderSizeCheck = builder.elementCount();
            if(builderSizeCheck > (int) myUniqueInstObjNames.length())
            {
                for(int i= myUniqueInstObjNames.length(); i<builderSizeCheck; i++)
                {
                    builder.removeElement(i);
                }
            }
        }
        instancedObjectNamesHandle.set(builder);
        instancedObjectNamesHandle.setAllClean();

        handle.setClean();
        instancerDataHandle.setClean();
        //data.setClean(plug);
        //data.setClean(instancerDataPlug);

        myNeverBuilt = false;
    }

    return MS::kSuccess;
}
<|endoftext|>


<commit_before>/**
 * \file dog_box_approx.cpp
 *
 * Difference of Gaussians approximated using box filters
 * - convert RGB image to V or HSV
 * - compute first box filter
 *   - compute x-y overlapped summed area table
 *   - compute two different box filters using different box radii
 * - apply 2nd order x box filter on each output
 * - apply 2nd order y box filter on each output
 *   - compute the difference between the two in the finite difference operator
 */

#include <iostream>
#include <Halide.h>

#include "recfilter.h"
#include "iir_coeff.h"

using namespace Halide;

// Difference operator to compute 1D x double box filter from second order intergal image
Expr diff_op_x(Func F, Expr x, Expr y, int w, int h, int B, int channel);

// Difference operator to compute 1D y double box filter from second order intergal image
Expr diff_op_y(Func F, Expr x, Expr y, int w, int h, int B, int channel);

// Difference operator to compute 2D xy single box filter from summed area table
Expr diff_op_xy(Func F, Expr x, Expr y, int w, int h, int B);

int main(int argc, char** argv) {
    Arguments args(argc, argv);

    bool nosched   = args.noschedule;
    int iter       = args.iterations;
    int tile_width = args.block;
    int width      = args.width;
    int height     = args.width;

    int sigma1 = 1.0f;
    int sigma2 = 2.0f;

    // box filter radii for approximating Gaussians of given sigma with 3 iterations
    int B1 = gaussian_box_filter(sigma1, 3);
    int B2 = gaussian_box_filter(sigma2, 3);

    Image<int16_t> image = generate_random_image<int16_t>(width,height);

    // pad the image with zeros at borders
    int pad = std::max(3*B1+3, 3*B2+3);
    for (int k=0; k<image.channels(); k++) {
        for (int i=0; i<image.width(); i++) {
            for (int j=0; j<image.height(); j++) {
                if (i<pad || i>width-pad || j<pad || j>height-pad) {
                    image(i,j,k) = 0.0f;
                }
            }
        }
    }

    RecFilterDim x("x", width);
    RecFilterDim y("y", height);

    // Compute V of HSV from RGB input
    RecFilter V("V");
    // V(x,y) = Cast::make(type_of<float>(), max(image(x,y,0), max(image(x,y,1),image(x,y,2))));
    V(x,y) = Internal::Cast::make(type_of<float>(), image(x,y));

    // summed table from V channel
    RecFilter SAT("SAT");
    SAT(x,y) = V.as_func()(x,y);
    SAT.add_filter(+x, {1.0f, 1.0f});
    SAT.add_filter(+y, {1.0f, 1.0f});

    // copmute 1 iteration of box filters from this result
    RecFilter Box1("Box1");
    Box1(x,y) = Tuple(diff_op_xy(SAT.as_func(), x, y, width, height, B1), diff_op_xy(SAT.as_func(), x, y, width, height, B2));

    // compute 2nd order x box filter, calculate the 2nd order intergal image first on both outputs
    RecFilter SAT2x("SAT2x");
    SAT2x(x,y) = Tuple(Box1.as_func()(x,y)[0], Box1.as_func()(x,y)[1]);
    SAT2x.add_filter(+x, {1.0f, 2.0f, -1.0f});

    // complete the 2nd order x box filter by difference operator
    RecFilter Box2x("Box2x");
    Box2x(x,y) = Tuple(diff_op_x(SAT2x.as_func(), x, y, width, height, B1, 0), diff_op_x(SAT2x.as_func(), x, y, width, height, B2, 1));

    // compute 2nd order y box filter, calculate the 2nd order intergal image first on both outputs
    RecFilter SAT2y("SAT2y");
    SAT2y(x,y) = Tuple(Box2x.as_func()(x,y)[0], Box2x.as_func()(x,y)[1]);
    SAT2y.add_filter(+y, {1.0f, 2.0f, -1.0f});

    // complete 2nd order y box filter, subtract the result and take square as final result
    RecFilter DoG("DoG");
    DoG(x,y) = diff_op_y(SAT2y.as_func(), x, y, width, height, B1, 0) - diff_op_y(SAT2y.as_func(), x, y, width, height, B2, 1);

    // -------------------------------------------------------------------------
    // tile all the intgral image functions

    SAT.split_all_dimensions(tile_width);
    SAT2x.split_all_dimensions(tile_width);
    SAT2y.split_all_dimensions(tile_width);

    // -------------------------------------------------------------------------
    // schedules

    // Identical schedules for the three pointwise operations
    // these are not recursive filters, so it is just easier to
    // write schedule for them using Halide API

    Var xo("xo"), xi("xi"), xii("xii"), u(x.var());
    Var yo("yo"), yi("yi"), yii("yii"), v(y.var());

    Box1.as_func().compute_root()
        .split  (u, xo, xi, tile_width)
        .split  (v, yo, yi, tile_width)
        .split  (yi,yi, yii,8)
        .unroll (yii)
        .reorder(yii,xi,yi,xo,yo)
        .gpu    (xo,yo,xi,yi);

    Box2x.as_func().compute_root()
        .split  (u, xo, xi, tile_width)
        .split  (v, yo, yi, tile_width)
        .split  (yi,yi, yii, 8)
        .unroll (yii)
        .reorder(yii,xi,yi,xo,yo)
        .gpu    (xo,yo,xi,yi);

    DoG.as_func().compute_root()
        .split  (u, xo, xi, tile_width)
        .split  (v, yo, yi, tile_width)
        .split  (yi,yi, yii,8)
        .unroll (yii)
        .reorder(yii,xi,yi,xo,yo)
        .gpu    (xo,yo,xi,yi);

    // automatic schedules for recursive filter pipelines
    if (!nosched) {
        SAT  .gpu_auto_schedule(128);
        SAT2x.gpu_auto_schedule(128);
        SAT2y.gpu_auto_schedule(128);
    }
    else {

        // euivalnet manual schedules

        int ws       = 32;
        int unroll_w = ws/4;
        int intra_tiles_per_warp = ws/2;
        int inter_tiles_per_warp = 4;

        // first box filter
        {
            RecFilter F = SAT;

            F.intra_schedule(1).compute_locally()
                .reorder_storage(F.inner(), F.outer())
                .unroll         (F.inner_scan())
                .split          (F.inner(1), unroll_w)
                .unroll         (F.inner(1).split_var())
                .reorder        (F.inner_scan(), F.inner(1).split_var(), F.inner(), F.outer())
                .gpu_threads    (F.inner(0), F.inner(1))
                .gpu_blocks     (F.outer(0), F.outer(1));

            F.intra_schedule(2).compute_locally()
                .unroll     (F.inner_scan())
                .split      (F.outer(0), intra_tiles_per_warp)
                .reorder    (F.inner(),  F.inner_scan(), F.tail(), F.outer(0).split_var(), F.outer())
                .fuse       (F.tail(), F.inner(0))
                .gpu_threads(F.tail(), F.outer(0).split_var())
                .gpu_blocks (F.outer(0), F.outer(1));

            F.inter_schedule().compute_globally()
                .reorder_storage(F.inner(), F.tail(), F.outer())
                .unroll         (F.outer_scan())
                .split          (F.outer(0), inter_tiles_per_warp)
                .reorder        (F.outer_scan(), F.tail(), F.outer(0).split_var(), F.inner(), F.outer())
                .gpu_threads    (F.inner(0), F.outer(0).split_var())
                .gpu_blocks     (F.outer(0));

        }

        // second order 2 box filter along x
        {
            RecFilter sat_x  = SAT2x;

            sat_x.intra_schedule().compute_locally()
                .split      (sat_x.full(0), ws, sat_x.inner())
                .split      (sat_x.inner(1), unroll_w)
                .unroll     (sat_x.inner(1).split_var())
                .unroll     (sat_x.inner_scan())
                .reorder    (sat_x.inner_scan(), sat_x.inner(1).split_var(), sat_x.tail(), sat_x.inner(), sat_x.outer(), sat_x.full())
                .gpu_threads(sat_x.inner(0), sat_x.inner(1))
                .gpu_blocks (sat_x.outer(0), sat_x.full(0));

            sat_x.inter_schedule().compute_globally()
                .reorder_storage(sat_x.full(0), sat_x.tail(), sat_x.outer(0))
                .split          (sat_x.full(0), ws, sat_x.inner())
                .unroll         (sat_x.outer_scan())
                .split          (sat_x.full(0), inter_tiles_per_warp)
                .reorder        (sat_x.outer_scan(), sat_x.tail(), sat_x.full(0).split_var(), sat_x.inner(), sat_x.full(0))
                .gpu_threads    (sat_x.inner(0), sat_x.full(0).split_var())
                .gpu_blocks     (sat_x.full(0));
        }

        // slightly different schedule for y IIR filter
        {
            RecFilter sat_y  = SAT2y;

            sat_y.intra_schedule().compute_locally()
                .reorder_storage(sat_y.full(0), sat_y.inner(), sat_y.outer(0))
                .split      (sat_y.full(0), ws)
                .split      (sat_y.inner(0), unroll_w)
                .unroll     (sat_y.inner(0).split_var())
                .unroll     (sat_y.inner_scan())
                .reorder    (sat_y.inner_scan(), sat_y.inner(0).split_var(), sat_y.inner(0), sat_y.full(0).split_var(), sat_y.full(0), sat_y.outer(0))
                .gpu_threads(sat_y.full(0).split_var(), sat_y.inner(0))
                .gpu_blocks (sat_y.full(0), sat_y.outer(0));

            sat_y.inter_schedule().compute_globally()
                .reorder_storage(sat_y.full(0), sat_y.tail(), sat_y.outer(0))
                .split          (sat_y.full(0), ws, sat_y.inner())
                .unroll         (sat_y.outer_scan())
                .split          (sat_y.full(0), inter_tiles_per_warp)
                .reorder        (sat_y.outer_scan(), sat_y.tail(), sat_y.full(0).split_var(), sat_y.inner(), sat_y.full(0))
                .gpu_threads    (sat_y.inner(0), sat_y.full(0).split_var())
                .gpu_blocks     (sat_y.full(0));
        }
    }

    DoG.profile(iter);

    return 0;
}

Expr diff_op_x(Func F, Expr x, Expr y, int w, int h, int B, int c) {
    Expr e = (F(min(x+B,w-1), y)[c] - 2.0f*F(max(x-1,0), y)[c] + F(max(x-2*B-2,0), y)[c]) / float(2*B+1);
    return e;
}

Expr diff_op_y(Func F, Expr x, Expr y, int w, int h, int B, int c) {
    Expr e = (F(x, min(y+B,h-1))[c] - 2.0f*F(x, max(y-1,0))[c] + F(x, max(y-2*B-2,0))[c]) / float(2*B+1);
    return e;
}

// Difference operator to compute 2D xy single box filter from summed area table
Expr diff_op_xy(Func F, Expr x, Expr y, int w, int h, int B) {
    Expr e =(1.0f * F(clamp(x+B+0, 0, w-1), clamp(y+B+0, 0, h-1)) +
            -1.0f * F(clamp(x+B+0, 0, w-1), clamp(y-B-1, 0, h-1)) +
            1.0f * F(clamp(x-B-1, 0, w-1), clamp(y-B-1, 0, h-1)) +
            -1.0f * F(clamp(x-B-1, 0, w-1), clamp(y+B+0, 0, h-1))) / std::pow(2*B+1,2);
    return e;
}
<commit_msg>Code cleanup in difference of gaussian<commit_after>/**
 * \file diff_gauss.cpp
 *
 * Difference of Gaussians approximated using box filters
 * - convert RGB image to V or HSV
 * - compute first box filter
 *   - compute x-y overlapped summed area table
 *   - compute two different box filters using different box radii
 * - apply 2nd order x box filter on each output
 * - apply 2nd order y box filter on each output
 *   - compute the difference between the two in the finite difference operator
 *
 * \todo imporve numerical stability by dividing the image by 2r before computing
 *   and integral image instead of computing the integral image and then dividing
 *   during finite differencing
 */

#include <iostream>
#include <Halide.h>

#include <recfilter.h>
#include <iir_coeff.h>

using namespace Halide;

/// Difference operator to compute 1D x double box filter from 2nd order intergal image
Expr diff_op_x(Func F, Expr x, Expr y, int w, int h, int B, int channel);

/// Difference operator to compute 1D y double box filter from 2nd order intergal image
Expr diff_op_y(Func F, Expr x, Expr y, int w, int h, int B, int channel);

/// Difference operator to compute 2D xy single box filter from summed area table
Expr diff_op_xy(Func F, Expr x, Expr y, int w, int h, int B);

/// Manual schedule for difference of Gaussians, this is not used in the benchmark
/// and only illustrates what the automatic scheduler does under the hood, these can
/// used instead of the auto schedules
void manual_schedules(void);

int main(int argc, char** argv) {
    Arguments args(argc, argv);

    int iter       = args.iterations;
    int tile_width = args.block;
    int width      = args.width;
    int height     = args.width;

    // box filter radii for approximating Gaussians of given sigma with 3 iterations
    int sigma1 = 1.0f;
    int sigma2 = 2.0f;
    int B1 = gaussian_box_filter(sigma1, 3);
    int B2 = gaussian_box_filter(sigma2, 3);

    Image<int16_t> image = generate_random_image<int16_t>(width,height);

    // pad the image with zeros at borders
    int pad = std::max(3*B1+3, 3*B2+3);
    for (int k=0; k<image.channels(); k++) {
        for (int i=0; i<image.width(); i++) {
            for (int j=0; j<image.height(); j++) {
                if (i<pad || i>width-pad || j<pad || j>height-pad) {
                    image(i,j,k) = 0.0f;
                }
            }
        }
    }

    RecFilterDim x("x", width);
    RecFilterDim y("y", height);

    // Compute V of HSV from RGB input
    RecFilter V("V");
    // V(x,y) = Cast::make(type_of<float>(), max(image(x,y,0), max(image(x,y,1),image(x,y,2))));
    V(x,y) = Internal::Cast::make(type_of<float>(), image(x,y));

    // summed table from V channel
    RecFilter SAT("SAT");
    SAT(x,y) = V.as_func()(x,y);
    SAT.add_filter(+x, {1.0f, 1.0f});
    SAT.add_filter(+y, {1.0f, 1.0f});

    // compute 1 iteration of box filters from this result
    RecFilter Box1("Box1");
    Box1(x,y) = Tuple(diff_op_xy(SAT.as_func(), x, y, width, height, B1), diff_op_xy(SAT.as_func(), x, y, width, height, B2));

    // compute 2nd order x box filter, calculate the 2nd order intergal image first on both outputs
    RecFilter SAT2x("SAT2x");
    SAT2x(x,y) = Tuple(Box1.as_func()(x,y)[0], Box1.as_func()(x,y)[1]);
    SAT2x.add_filter(+x, {1.0f, 2.0f, -1.0f});

    // complete the 2nd order x box filter by difference operator
    RecFilter Box2x("Box2x");
    Box2x(x,y) = Tuple(diff_op_x(SAT2x.as_func(), x, y, width, height, B1, 0), diff_op_x(SAT2x.as_func(), x, y, width, height, B2, 1));

    // compute 2nd order y box filter, calculate the 2nd order intergal image first on both outputs
    RecFilter SAT2y("SAT2y");
    SAT2y(x,y) = Tuple(Box2x.as_func()(x,y)[0], Box2x.as_func()(x,y)[1]);
    SAT2y.add_filter(+y, {1.0f, 2.0f, -1.0f});

    // complete 2nd order y box filter, subtract the result and take square as final result
    RecFilter DoG("DoG");
    DoG(x,y) = diff_op_y(SAT2y.as_func(), x, y, width, height, B1, 0) - diff_op_y(SAT2y.as_func(), x, y, width, height, B2, 1);

    // -------------------------------------------------------------------------
    // tile all the intgral image functions

    SAT  .split_all_dimensions(tile_width);
    SAT2x.split_all_dimensions(tile_width);
    SAT2y.split_all_dimensions(tile_width);

    // -------------------------------------------------------------------------
    // schedules

    SAT  .gpu_auto_schedule(128);
    SAT2x.gpu_auto_schedule(128);
    SAT2y.gpu_auto_schedule(128);

    Box1 .gpu_auto_schedule(128, tile_width);
    Box2x.gpu_auto_schedule(128, tile_width);
    DoG  .gpu_auto_schedule(128, tile_width);

    // -------------------------------------------------------------------------
    // compile and profile
    DoG.profile(iter);

    return EXIT_SUCCESS;
}

Expr diff_op_x(Func F, Expr x, Expr y, int w, int h, int B, int c) {
    Expr e = (F(min(x+B,w-1), y)[c] - 2.0f*F(max(x-1,0), y)[c] + F(max(x-2*B-2,0), y)[c]) / float(2*B+1);
    return e;
}

Expr diff_op_y(Func F, Expr x, Expr y, int w, int h, int B, int c) {
    Expr e = (F(x, min(y+B,h-1))[c] - 2.0f*F(x, max(y-1,0))[c] + F(x, max(y-2*B-2,0))[c]) / float(2*B+1);
    return e;
}

// Difference operator to compute 2D xy single box filter from summed area table
Expr diff_op_xy(Func F, Expr x, Expr y, int w, int h, int B) {
    Expr e =(1.0f * F(clamp(x+B+0, 0, w-1), clamp(y+B+0, 0, h-1)) +
            -1.0f * F(clamp(x+B+0, 0, w-1), clamp(y-B-1, 0, h-1)) +
            1.0f * F(clamp(x-B-1, 0, w-1), clamp(y-B-1, 0, h-1)) +
            -1.0f * F(clamp(x-B-1, 0, w-1), clamp(y+B+0, 0, h-1))) / std::pow(2*B+1,2);
    return e;
}

void manual_schedules(void) {
    int ws       = 32;
    int unroll_w = ws/4;
    int intra_tiles_per_warp = ws/2;
    int inter_tiles_per_warp = 4;

    Var xo("xo"), xi("xi"), xii("xii"), u(x.var());
    Var yo("yo"), yi("yi"), yii("yii"), v(y.var());

    // first box filter
    {
        RecFilter F = SAT;

        F.intra_schedule(1).compute_locally()
            .reorder_storage(F.inner(), F.outer())
            .unroll         (F.inner_scan())
            .split          (F.inner(1), unroll_w)
            .unroll         (F.inner(1).split_var())
            .reorder        (F.inner_scan(), F.inner(1).split_var(), F.inner(), F.outer())
            .gpu_threads    (F.inner(0), F.inner(1))
            .gpu_blocks     (F.outer(0), F.outer(1));

        F.intra_schedule(2).compute_locally()
            .unroll     (F.inner_scan())
            .split      (F.outer(0), intra_tiles_per_warp)
            .reorder    (F.inner(),  F.inner_scan(), F.tail(), F.outer(0).split_var(), F.outer())
            .fuse       (F.tail(), F.inner(0))
            .gpu_threads(F.tail(), F.outer(0).split_var())
            .gpu_blocks (F.outer(0), F.outer(1));

        F.inter_schedule().compute_globally()
            .reorder_storage(F.inner(), F.tail(), F.outer())
            .unroll         (F.outer_scan())
            .split          (F.outer(0), inter_tiles_per_warp)
            .reorder        (F.outer_scan(), F.tail(), F.outer(0).split_var(), F.inner(), F.outer())
            .gpu_threads    (F.inner(0), F.outer(0).split_var())
            .gpu_blocks     (F.outer(0));
    }

    // second order 2 box filter along x
    {
        RecFilter sat_x  = SAT2x;

        sat_x.intra_schedule().compute_locally()
            .split      (sat_x.full(0), ws, sat_x.inner())
            .split      (sat_x.inner(1), unroll_w)
            .unroll     (sat_x.inner(1).split_var())
            .unroll     (sat_x.inner_scan())
            .reorder    (sat_x.inner_scan(), sat_x.inner(1).split_var(), sat_x.tail(), sat_x.inner(), sat_x.outer(), sat_x.full())
            .gpu_threads(sat_x.inner(0), sat_x.inner(1))
            .gpu_blocks (sat_x.outer(0), sat_x.full(0));

        sat_x.inter_schedule().compute_globally()
            .reorder_storage(sat_x.full(0), sat_x.tail(), sat_x.outer(0))
            .split          (sat_x.full(0), ws, sat_x.inner())
            .unroll         (sat_x.outer_scan())
            .split          (sat_x.full(0), inter_tiles_per_warp)
            .reorder        (sat_x.outer_scan(), sat_x.tail(), sat_x.full(0).split_var(), sat_x.inner(), sat_x.full(0))
            .gpu_threads    (sat_x.inner(0), sat_x.full(0).split_var())
            .gpu_blocks     (sat_x.full(0));
    }

    // slightly different schedule for y IIR filter
    {
        RecFilter sat_y  = SAT2y;

        sat_y.intra_schedule().compute_locally()
            .reorder_storage(sat_y.full(0), sat_y.inner(), sat_y.outer(0))
            .split      (sat_y.full(0), ws)
            .split      (sat_y.inner(0), unroll_w)
            .unroll     (sat_y.inner(0).split_var())
            .unroll     (sat_y.inner_scan())
            .reorder    (sat_y.inner_scan(), sat_y.inner(0).split_var(), sat_y.inner(0), sat_y.full(0).split_var(), sat_y.full(0), sat_y.outer(0))
            .gpu_threads(sat_y.full(0).split_var(), sat_y.inner(0))
            .gpu_blocks (sat_y.full(0), sat_y.outer(0));

        sat_y.inter_schedule().compute_globally()
            .reorder_storage(sat_y.full(0), sat_y.tail(), sat_y.outer(0))
            .split          (sat_y.full(0), ws, sat_y.inner())
            .unroll         (sat_y.outer_scan())
            .split          (sat_y.full(0), inter_tiles_per_warp)
            .reorder        (sat_y.outer_scan(), sat_y.tail(), sat_y.full(0).split_var(), sat_y.inner(), sat_y.full(0))
            .gpu_threads    (sat_y.inner(0), sat_y.full(0).split_var())
            .gpu_blocks     (sat_y.full(0));
    }

    {
        Box1.as_func().compute_root()
            .split  (u, xo, xi, tile_width)
            .split  (v, yo, yi, tile_width)
            .split  (yi,yi, yii,8)
            .unroll (yii)
            .reorder(yii,xi,yi,xo,yo)
            .gpu    (xo,yo,xi,yi);

        Box2x.as_func().compute_root()
            .split  (u, xo, xi, tile_width)
            .split  (v, yo, yi, tile_width)
            .split  (yi,yi, yii, 8)
            .unroll (yii)
            .reorder(yii,xi,yi,xo,yo)
            .gpu    (xo,yo,xi,yi);

        DoG.as_func().compute_root()
            .split  (u, xo, xi, tile_width)
            .split  (v, yo, yi, tile_width)
            .split  (yi,yi, yii,8)
            .unroll (yii)
            .reorder(yii,xi,yi,xo,yo)
            .gpu    (xo,yo,xi,yi);
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <chrono>
#include <unordered_map>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>

#include <seastar/core/timer.hh>
#include <seastar/core/gate.hh>

#include "utils/exceptions.hh"

namespace bi = boost::intrusive;

namespace utils {
// Simple variant of the "LoadingCache" used for permissions in origin.

typedef lowres_clock loading_cache_clock_type;
typedef bi::list_base_hook<bi::link_mode<bi::auto_unlink>> auto_unlink_list_hook;

template<typename Tp, typename Key, typename Hash, typename EqualPred>
class timestamped_val : public auto_unlink_list_hook, public bi::unordered_set_base_hook<bi::store_hash<true>> {
public:
    typedef bi::list<timestamped_val, bi::constant_time_size<false>> lru_list_type;
    typedef Key key_type;
    typedef Tp value_type;

private:
    std::experimental::optional<Tp> _opt_value;
    loading_cache_clock_type::time_point _loaded;
    loading_cache_clock_type::time_point _last_read;
    lru_list_type& _lru_list; /// MRU item is at the front, LRU - at the back
    Key _key;

public:
    struct key_eq {
       bool operator()(const Key& k, const timestamped_val& c) const {
           return EqualPred()(k, c.key());
       }

       bool operator()(const timestamped_val& c, const Key& k) const {
           return EqualPred()(c.key(), k);
       }
    };

    timestamped_val(lru_list_type& lru_list, const Key& key)
        : _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
        , _lru_list(lru_list)
        , _key(key) {}

    timestamped_val(lru_list_type& lru_list, Key&& key)
        : _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
        , _lru_list(lru_list)
        , _key(std::move(key)) {}

    timestamped_val(const timestamped_val&) = default;
    timestamped_val(timestamped_val&&) = default;

    // Make sure copy/move-assignments don't go through the template below
    timestamped_val& operator=(const timestamped_val&) = default;
    timestamped_val& operator=(timestamped_val&) = default;
    timestamped_val& operator=(timestamped_val&&) = default;

    template <typename U>
    timestamped_val& operator=(U&& new_val) {
        _opt_value = std::forward<U>(new_val);
        _loaded = loading_cache_clock_type::now();
        return *this;
    }

    const Tp& value() {
        _last_read = loading_cache_clock_type::now();
        touch();
        return _opt_value.value();
    }

    explicit operator bool() const noexcept {
        return bool(_opt_value);
    }

    loading_cache_clock_type::time_point last_read() const noexcept {
        return _last_read;
    }

    loading_cache_clock_type::time_point loaded() const noexcept {
        return _loaded;
    }

    const Key& key() const {
        return _key;
    }

    friend bool operator==(const timestamped_val& a, const timestamped_val& b){
        return EqualPred()(a.key(), b.key());
    }

    friend std::size_t hash_value(const timestamped_val& v) {
        return Hash()(v.key());
    }

private:
    /// Set this item as the most recently used item.
    /// The MRU item is going to be at the front of the _lru_list, the LRU item - at the back.
    void touch() noexcept {
        auto_unlink_list_hook::unlink();
        _lru_list.push_front(*this);
    }
};

class shared_mutex {
private:
    lw_shared_ptr<semaphore> _mutex_ptr;

public:
    shared_mutex() : _mutex_ptr(make_lw_shared<semaphore>(1)) {}
    semaphore& get() const noexcept {
        return *_mutex_ptr;
    }
};

template<typename Key,
         typename Tp,
         typename Hash = std::hash<Key>,
         typename EqualPred = std::equal_to<Key>,
         typename Alloc = std::allocator<timestamped_val<Tp, Key, Hash, EqualPred>>,
         typename SharedMutexMapAlloc = std::allocator<std::pair<const Key, shared_mutex>>>
class loading_cache {
private:
    typedef timestamped_val<Tp, Key, Hash, EqualPred> ts_value_type;
    typedef bi::unordered_set<ts_value_type, bi::power_2_buckets<true>, bi::compare_hash<true>> set_type;
    typedef std::unordered_map<Key, shared_mutex, Hash, EqualPred, SharedMutexMapAlloc> write_mutex_map_type;
    typedef typename ts_value_type::lru_list_type lru_list_type;
    typedef typename set_type::bucket_traits bi_set_bucket_traits;

    static constexpr int initial_num_buckets = 256;
    static constexpr int max_num_buckets = 1024 * 1024;

public:
    typedef Tp value_type;
    typedef Key key_type;
    typedef typename set_type::iterator iterator;

    template<typename Func>
    loading_cache(size_t max_size, std::chrono::milliseconds expiry, std::chrono::milliseconds refresh, logging::logger& logger, Func&& load)
                : _buckets(initial_num_buckets)
                , _set(bi_set_bucket_traits(_buckets.data(), _buckets.size()))
                , _max_size(max_size)
                , _expiry(expiry)
                , _refresh(refresh)
                , _logger(logger)
                , _load(std::forward<Func>(load)) {

        // If expiration period is zero - caching is disabled
        if (!caching_enabled()) {
            return;
        }

        // Sanity check: if expiration period is given then non-zero refresh period and maximal size are required
        if (_refresh == std::chrono::milliseconds(0) || _max_size == 0) {
            throw exceptions::configuration_exception("loading_cache: caching is enabled but refresh period and/or max_size are zero");
        }

        _timer_period = std::min(_expiry, _refresh);
        _timer.set_callback([this] { on_timer(); });
        _timer.arm(_timer_period);
    }

    ~loading_cache() {
        _set.clear_and_dispose([] (ts_value_type* ptr) { loading_cache::destroy_ts_value(ptr); });
    }

    future<Tp> get(const Key& k) {
        // If caching is disabled - always load in the foreground
        if (!caching_enabled()) {
            return _load(k);
        }

        // If the key is not in the cache yet, then find_or_create() is going to
        // create a new uninitialized value in the map. If the value is already
        // in the cache (the fast path) simply return the value. Otherwise, take
        // the mutex and try to load the value (the slow path).
        iterator ts_value_it = find_or_create(k);
        if (*ts_value_it) {
            return make_ready_future<Tp>(ts_value_it->value());
        } else {
            return slow_load(k);
        }
    }

    future<> stop() {
        return _timer_reads_gate.close().finally([this] { _timer.cancel(); });
    }

private:
    bool caching_enabled() const {
        return _expiry != std::chrono::milliseconds(0);
    }

    /// Look for the entry with the given key. It it doesn't exist - create a new one and add it to the _set.
    ///
    /// \param k The key to look for
    ///
    /// \return An iterator to the value with the given key (always dirrerent from _set.end())
    template <typename KeyType>
    iterator find_or_create(KeyType&& k) {
        iterator i = _set.find(k, Hash(), typename ts_value_type::key_eq());
        if (i == _set.end()) {
            ts_value_type* new_ts_val = Alloc().allocate(1);
            new(new_ts_val) ts_value_type(_lru_list, std::forward<KeyType>(k));
            auto p = _set.insert(*new_ts_val);
            i = p.first;
        }

        return i;
    }

    static void destroy_ts_value(ts_value_type* val) {
        val->~ts_value_type();
        Alloc().deallocate(val, 1);
    }

    future<Tp> slow_load(const Key& k) {
        // If the key is not in the cache yet, then _write_mutex_map[k] is going
        // to create a new value with the initialized mutex. The mutex is going
        // to serialize the producers and only the first one is going to
        // actually issue a load operation and initialize the value with the
        // received result. The rest are going to see (and read) the initialized
        // value when they enter the critical section.
        shared_mutex sm = _write_mutex_map[k];
        return with_semaphore(sm.get(), 1, [this, k] {
            iterator ts_value_it = find_or_create(k);
            if (*ts_value_it) {
                return make_ready_future<Tp>(ts_value_it->value());
            }
            _logger.trace("{}: storing the value for the first time", k);
            return _load(k).then([this, k] (Tp t) {
                // we have to "re-read" the _set here because the value may have been evicted by now
                iterator ts_value_it = find_or_create(std::move(k));
                *ts_value_it = std::move(t);
                return make_ready_future<Tp>(ts_value_it->value());
            });
        }).finally([sm] {});
    }

    future<> reload(ts_value_type& ts_val) {
        return _load(ts_val.key()).then_wrapped([this, &ts_val] (auto&& f) {
            // The exceptions are related to the load operation itself.
            // We should ignore them for the background reads - if
            // they persist the value will age and will be reloaded in
            // the forground. If the foreground READ fails the error
            // will be propagated up to the user and will fail the
            // corresponding query.
            try {
                ts_val = f.get0();
            } catch (std::exception& e) {
                _logger.debug("{}: reload failed: {}", ts_val.key(), e.what());
            } catch (...) {
                _logger.debug("{}: reload failed: unknown error", ts_val.key());
            }
        });
    }

    void erase(iterator it) {
        _set.erase_and_dispose(it, [] (ts_value_type* ptr) { loading_cache::destroy_ts_value(ptr); });
        // no need to delete the item from _lru_list - it's auto-deleted
    }

    void drop_expired() {
        auto now = loading_cache_clock_type::now();
        _lru_list.remove_and_dispose_if([now, this] (const ts_value_type& v) {
            using namespace std::chrono;
            // An entry should be discarded if it hasn't been reloaded for too long or nobody cares about it anymore
            auto since_last_read = now - v.last_read();
            auto since_loaded = now - v.loaded();
            if (_expiry < since_last_read || _expiry < since_loaded) {
                _logger.trace("drop_expired(): {}: dropping the entry: _expiry {},  ms passed since: loaded {} last_read {}", v.key(), _expiry.count(), duration_cast<milliseconds>(since_loaded).count(), duration_cast<milliseconds>(since_last_read).count());
                return true;
            }
            return false;
        }, [this] (ts_value_type* p) {
            erase(_set.iterator_to(*p));
        });
    }

    // Shrink the cache to the _max_size discarding the least recently used items
    void shrink() {
        if (_set.size() > _max_size) {
            auto num_items_to_erase = _set.size() - _max_size;
            for (size_t i = 0; i < num_items_to_erase; ++i) {
                using namespace std::chrono;
                ts_value_type& ts_val = *_lru_list.rbegin();
                _logger.trace("shrink(): {}: dropping the entry: ms since last_read {}", ts_val.key(), duration_cast<milliseconds>(loading_cache_clock_type::now() - ts_val.last_read()).count());
                erase(_set.iterator_to(ts_val));
            }
        }
    }

    void rehash() {
        size_t new_buckets_count = 0;

        // Don't grow or shrink too fast even if there is a steep drop/growth in the number of elements in the set.
        // Exponential growth/backoff should be good enough.
        //
        // Try to keep the load factor between 0.25 and 1.0.
        if (_set.size() < _current_buckets_count / 4) {
            new_buckets_count = _current_buckets_count / 4;
        } else if (_set.size() > _current_buckets_count) {
            new_buckets_count = _current_buckets_count * 2;
        }

        if (new_buckets_count < initial_num_buckets || new_buckets_count > max_num_buckets) {
            return;
        }

        std::vector<typename set_type::bucket_type> new_buckets(new_buckets_count);
        _set.rehash(bi_set_bucket_traits(new_buckets.data(), new_buckets.size()));
        _logger.trace("rehash(): buckets count changed: {} -> {}", _current_buckets_count, new_buckets_count);

        _buckets.swap(new_buckets);
        _current_buckets_count = new_buckets_count;
    }

    void on_timer() {
        _logger.trace("on_timer(): start");

        auto timer_start_tp = loading_cache_clock_type::now();

        // Clear all cached mutexes
        _write_mutex_map.clear();

        // Clean up items that were not touched for the whole _expiry period.
        drop_expired();

        // Remove the least recently used items if map is too big.
        shrink();

        // check if rehashing is needed and do it if it is.
        rehash();

        // Reload all those which vlaue needs to be reloaded.
        with_gate(_timer_reads_gate, [this, timer_start_tp] {
            return parallel_for_each(_set.begin(), _set.end(), [this, curr_time = timer_start_tp] (auto& ts_val) {
                _logger.trace("on_timer(): {}: checking the value age", ts_val.key());
                if (ts_val && ts_val.loaded() + _refresh < curr_time) {
                    _logger.trace("on_timer(): {}: reloading the value", ts_val.key());
                    return this->reload(ts_val);
                }
                return now();
            }).finally([this, timer_start_tp] {
                _logger.trace("on_timer(): rearming");
                _timer.arm(timer_start_tp + _timer_period);
            });
        });
    }

    std::vector<typename set_type::bucket_type> _buckets;
    size_t _current_buckets_count = initial_num_buckets;
    set_type _set;
    write_mutex_map_type _write_mutex_map;
    lru_list_type _lru_list;
    size_t _max_size;
    std::chrono::milliseconds _expiry;
    std::chrono::milliseconds _refresh;
    loading_cache_clock_type::duration _timer_period;
    logging::logger& _logger;
    std::function<future<Tp>(const Key&)> _load;
    timer<loading_cache_clock_type> _timer;
    seastar::gate _timer_reads_gate;
};

}

<commit_msg>utils::loading_cache: rework on top of utils::loading_shared_values<commit_after>/*
 * Copyright (C) 2016 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <chrono>
#include <unordered_map>
#include <boost/intrusive/list.hpp>
#include <boost/intrusive/unordered_set.hpp>

#include <seastar/core/timer.hh>
#include <seastar/core/gate.hh>

#include "exceptions/exceptions.hh"
#include "utils/loading_shared_values.hh"
#include "log.hh"

namespace bi = boost::intrusive;

namespace utils {

using loading_cache_clock_type = seastar::lowres_clock;
using auto_unlink_list_hook = bi::list_base_hook<bi::link_mode<bi::auto_unlink>>;

template<typename Tp, typename Key, typename Hash, typename EqualPred, typename LoadingSharedValuesStats>
class timestamped_val {
public:
    using value_type = Tp;
    using loading_values_type = typename utils::loading_shared_values<Key, timestamped_val, Hash, EqualPred, LoadingSharedValuesStats, 256>;
    class lru_entry;

private:
    value_type _value;
    loading_cache_clock_type::time_point _loaded;
    loading_cache_clock_type::time_point _last_read;
    lru_entry* _lru_entry_ptr = nullptr; /// MRU item is at the front, LRU - at the back

public:
    timestamped_val(value_type val)
        : _value(std::move(val))
        , _loaded(loading_cache_clock_type::now())
        , _last_read(_loaded)
    {}
    timestamped_val(timestamped_val&&) = default;

    timestamped_val& operator=(value_type new_val) {
        assert(_lru_entry_ptr);

        _value = std::move(new_val);
        _loaded = loading_cache_clock_type::now();
        return *this;
    }

    value_type& value() noexcept {
        touch();
        return _value;
    }

    loading_cache_clock_type::time_point last_read() const noexcept {
        return _last_read;
    }

    loading_cache_clock_type::time_point loaded() const noexcept {
        return _loaded;
    }

    bool ready() const noexcept {
        return _lru_entry_ptr;
    }

private:
    void touch() noexcept {
        assert(_lru_entry_ptr);
        _last_read = loading_cache_clock_type::now();
        _lru_entry_ptr->touch();
    }

    void set_anchor_back_reference(lru_entry* lru_entry_ptr) noexcept {
        _lru_entry_ptr = lru_entry_ptr;
    }
};

/// \brief This is and LRU list entry which is also an anchor for a loading_cache value.
template<typename Tp, typename Key, typename Hash, typename EqualPred, typename LoadingSharedValuesStats>
class timestamped_val<Tp, Key, Hash, EqualPred, LoadingSharedValuesStats>::lru_entry : public auto_unlink_list_hook {
private:
    using ts_value_type = timestamped_val<Tp, Key, Hash, EqualPred, LoadingSharedValuesStats>;
    using loading_values_type = typename ts_value_type::loading_values_type;

public:
    using lru_list_type = bi::list<lru_entry, bi::constant_time_size<false>>;
    using timestamped_val_ptr = typename loading_values_type::entry_ptr;

private:
    timestamped_val_ptr _ts_val_ptr;
    lru_list_type& _lru_list;

public:
    lru_entry(timestamped_val_ptr ts_val, lru_list_type& lru_list)
        : _ts_val_ptr(std::move(ts_val))
        , _lru_list(lru_list)
    {
        _ts_val_ptr->set_anchor_back_reference(this);
    }

    ~lru_entry() {
        _ts_val_ptr->set_anchor_back_reference(nullptr);
    }

    /// Set this item as the most recently used item.
    /// The MRU item is going to be at the front of the _lru_list, the LRU item - at the back.
    void touch() noexcept {
        auto_unlink_list_hook::unlink();
        _lru_list.push_front(*this);
    }

    const Key& key() const noexcept {
        return loading_values_type::to_key(_ts_val_ptr);
    }

    timestamped_val& timestamped_value() noexcept { return *_ts_val_ptr; }
    const timestamped_val& timestamped_value() const noexcept { return *_ts_val_ptr; }
    timestamped_val_ptr timestamped_value_ptr() noexcept { return _ts_val_ptr; }
};

enum class loading_cache_reload_enabled { no, yes };

template<typename Key,
         typename Tp,
         typename Hash = std::hash<Key>,
         typename EqualPred = std::equal_to<Key>,
         typename LoadingSharedValuesStats = utils::do_nothing_loading_shared_values_stats,
         typename Alloc = std::allocator<typename timestamped_val<Tp, Key, Hash, EqualPred, LoadingSharedValuesStats>::lru_entry>>
class loading_cache {
private:
    using ts_value_type = timestamped_val<Tp, Key, Hash, EqualPred, LoadingSharedValuesStats>;
    using loading_values_type = typename ts_value_type::loading_values_type;
    using timestamped_val_ptr = typename loading_values_type::entry_ptr;
    using ts_value_lru_entry = typename ts_value_type::lru_entry;
    using set_iterator = typename loading_values_type::iterator;
    using lru_list_type = typename ts_value_lru_entry::lru_list_type;

public:
    using value_type = Tp;
    using key_type = Key;

    template<typename Func>
    loading_cache(size_t max_size, std::chrono::milliseconds expiry, std::chrono::milliseconds refresh, logging::logger& logger, Func&& load)
                : _max_size(max_size)
                , _expiry(expiry)
                , _refresh(refresh)
                , _logger(logger)
                , _load(std::forward<Func>(load)) {

        // If expiration period is zero - caching is disabled
        if (!caching_enabled()) {
            return;
        }

        // Sanity check: if expiration period is given then non-zero refresh period and maximal size are required
        if (_refresh == std::chrono::milliseconds(0) || _max_size == 0) {
            throw exceptions::configuration_exception("loading_cache: caching is enabled but refresh period and/or max_size are zero");
        }

        _timer_period = std::min(_expiry, _refresh);
        _timer.set_callback([this] { on_timer(); });
        _timer.arm(_timer_period);
    }

    ~loading_cache() {
        _lru_list.erase_and_dispose(_lru_list.begin(), _lru_list.end(), [] (ts_value_lru_entry* ptr) { loading_cache::destroy_ts_value(ptr); });
    }

    future<Tp> get(const Key& k) {
        // If caching is disabled - always load in the foreground
        if (!caching_enabled()) {
            return _load(k);
        }

        return _loading_values.get_or_load(k, [this] (const Key& k) {
            return _load(k).then([this] (value_type val) {
                return ts_value_type(std::move(val));
            });
        }).then([this, k] (timestamped_val_ptr ts_val_ptr) {
            // check again since it could have already been inserted and initialized
            if (!ts_val_ptr->ready()) {
                _logger.trace("{}: storing the value for the first time", k);

                ts_value_lru_entry* new_lru_entry = Alloc().allocate(1);
                new(new_lru_entry) ts_value_lru_entry(std::move(ts_val_ptr), _lru_list);

                // This will "touch" the entry and add it to the LRU list.
                return make_ready_future<Tp>(new_lru_entry->timestamped_value_ptr()->value());
            }

            return make_ready_future<Tp>(ts_val_ptr->value());
        });
    }

    future<> stop() {
        return _timer_reads_gate.close().finally([this] { _timer.cancel(); });
    }

private:
    set_iterator set_find(const Key& k) noexcept {
        set_iterator it = _loading_values.find(k);
        set_iterator end_it = set_end();

        if (it == end_it || !it->ready()) {
            return end_it;
        }
        return it;
    }

    set_iterator set_end() noexcept {
        return _loading_values.end();
    }

    set_iterator set_begin() noexcept {
        return _loading_values.begin();
    }

    bool caching_enabled() const {
        return _expiry != std::chrono::milliseconds(0);
    }

    static void destroy_ts_value(ts_value_lru_entry* val) {
        val->~ts_value_lru_entry();
        Alloc().deallocate(val, 1);
    }

    future<> reload(ts_value_lru_entry& lru_entry) {
        return _load(lru_entry.key()).then_wrapped([this, key = lru_entry.key()] (auto&& f) mutable {
            // if the entry has been evicted by now - simply end here
            set_iterator it = set_find(key);
            if (it == set_end()) {
                _logger.trace("{}: entry was dropped during the reload", key);
                return make_ready_future<>();
            }

            // The exceptions are related to the load operation itself.
            // We should ignore them for the background reads - if
            // they persist the value will age and will be reloaded in
            // the forground. If the foreground READ fails the error
            // will be propagated up to the user and will fail the
            // corresponding query.
            try {
                *it = f.get0();
            } catch (std::exception& e) {
                _logger.debug("{}: reload failed: {}", key, e.what());
            } catch (...) {
                _logger.debug("{}: reload failed: unknown error", key);
            }

            return make_ready_future<>();
        });
    }

    void drop_expired() {
        auto now = loading_cache_clock_type::now();
        _lru_list.remove_and_dispose_if([now, this] (const ts_value_lru_entry& lru_entry) {
            using namespace std::chrono;
            // An entry should be discarded if it hasn't been reloaded for too long or nobody cares about it anymore
            const ts_value_type& v = lru_entry.timestamped_value();
            auto since_last_read = now - v.last_read();
            auto since_loaded = now - v.loaded();
            if (_expiry < since_last_read || _expiry < since_loaded) {
                _logger.trace("drop_expired(): {}: dropping the entry: _expiry {},  ms passed since: loaded {} last_read {}", lru_entry.key(), _expiry.count(), duration_cast<milliseconds>(since_loaded).count(), duration_cast<milliseconds>(since_last_read).count());
                return true;
            }
            return false;
        }, [this] (ts_value_lru_entry* p) {
            loading_cache::destroy_ts_value(p);
        });
    }

    // Shrink the cache to the _max_size discarding the least recently used items
    void shrink() {
        if (_loading_values.size() > _max_size) {
            auto num_items_to_erase = _loading_values.size() - _max_size;
            for (size_t i = 0; i < num_items_to_erase; ++i) {
                using namespace std::chrono;
                auto it = _lru_list.rbegin();
                // This may happen if there are pending insertions into the loading_shared_values that hasn't been yet finalized.
                // In this case the number of elements in the _lru_list will be less than the _loading_values.size().
                if (_lru_list.rbegin() == _lru_list.rend()) {
                    return;
                }
                ts_value_lru_entry& lru_entry = *it;
                _logger.trace("shrink(): {}: dropping the entry: ms since last_read {}", lru_entry.key(), duration_cast<milliseconds>(loading_cache_clock_type::now() - lru_entry.timestamped_value().last_read()).count());
                loading_cache::destroy_ts_value(&lru_entry);
            }
        }
    }

    // Try to bring the load factors of the _loading_values into a known range.
    void periodic_rehash() noexcept {
        try {
            _loading_values.rehash();
        } catch (...) {
            // if rehashing fails - continue with the current buckets array
        }
    }

    void on_timer() {
        _logger.trace("on_timer(): start");

        // Clean up items that were not touched for the whole _expiry period.
        drop_expired();

        // Remove the least recently used items if map is too big.
        shrink();

        // check if rehashing is needed and do it if it is.
        periodic_rehash();

        // Reload all those which vlaue needs to be reloaded.
        with_gate(_timer_reads_gate, [this] {
            return parallel_for_each(_lru_list.begin(), _lru_list.end(), [this] (ts_value_lru_entry& lru_entry) {
                _logger.trace("on_timer(): {}: checking the value age", lru_entry.key());
                if (lru_entry.timestamped_value().loaded() + _refresh < loading_cache_clock_type::now()) {
                    _logger.trace("on_timer(): {}: reloading the value", lru_entry.key());
                    return this->reload(lru_entry);
                }
                return now();
            }).finally([this] {
                _logger.trace("on_timer(): rearming");
                _timer.arm(loading_cache_clock_type::now() + _timer_period);
            });
        });
    }

    loading_values_type _loading_values;
    lru_list_type _lru_list;
    size_t _max_size = 0;
    std::chrono::milliseconds _expiry;
    std::chrono::milliseconds _refresh;
    loading_cache_clock_type::duration _timer_period;
    logging::logger& _logger;
    std::function<future<Tp>(const Key&)> _load;
    timer<loading_cache_clock_type> _timer;
    seastar::gate _timer_reads_gate;
};

}

<|endoftext|>