<commit_before>/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * 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
 * OpenSceneGraph Public License for more details.
*/

#include <osgSim/LightPointNode>
#include <osgSim/LightPointSystem>

#include "LightPointDrawable.h"
#include "LightPointSpriteDrawable.h"

#include <osg/Timer>
#include <osg/BoundingBox>
#include <osg/BlendFunc>
#include <osg/Material>
#include <osg/PointSprite>

#include <osgUtil/CullVisitor>

#include <typeinfo>

namespace osgSim
{

osg::StateSet* getSingletonLightPointSystemSet()
{
    static osg::ref_ptr<osg::StateSet> s_stateset = 0;
    if (!s_stateset)
    {
        s_stateset = new osg::StateSet;
        // force light point nodes to be drawn after everything else by picking a rendering bin number after
        // the transparent bin.
        s_stateset->setRenderBinDetails(20,"DepthSortedBin");
    }
    return s_stateset.get();
}

LightPointNode::LightPointNode():
    _minPixelSize(0.0f),
    _maxPixelSize(30.0f),
    _maxVisibleDistance2(FLT_MAX),
    _lightSystem(0),
    _pointSprites(false)
{
    setStateSet(getSingletonLightPointSystemSet());
}

/** Copy constructor using CopyOp to manage deep vs shallow copy.*/
LightPointNode::LightPointNode(const LightPointNode& lpn,const osg::CopyOp& copyop):
    osg::Node(lpn,copyop),
    _bbox(lpn._bbox),
    _lightPointList(lpn._lightPointList),
    _minPixelSize(lpn._minPixelSize),
    _maxPixelSize(lpn._maxPixelSize),
    _maxVisibleDistance2(lpn._maxVisibleDistance2),
    _lightSystem(lpn._lightSystem),
    _pointSprites(lpn._pointSprites)
{
}

unsigned int LightPointNode::addLightPoint(const LightPoint& lp)
{
    unsigned int num = _lightPointList.size();
    _lightPointList.push_back(lp);
    dirtyBound();
    return num;
}

void LightPointNode::removeLightPoint(unsigned int pos)
{
    if (pos<_lightPointList.size())
    {
        _lightPointList.erase(_lightPointList.begin()+pos);
        dirtyBound();
    }
    dirtyBound();
}

osg::BoundingSphere LightPointNode::computeBound() const
{
    osg::BoundingSphere bsphere;
    bsphere.init();
    _bbox.init();

    if (_lightPointList.empty())
    {
        return bsphere;
    }

    LightPointList::const_iterator itr;
    for(itr=_lightPointList.begin();
        itr!=_lightPointList.end();
        ++itr)
    {
        _bbox.expandBy(itr->_position);
    }

    bsphere.set(_bbox.center(),0.0f);

    for(itr=_lightPointList.begin();
        itr!=_lightPointList.end();
        ++itr)
    {
        osg::Vec3 dv(itr->_position-bsphere.center());
        float radius = dv.length()+itr->_radius;
        if (bsphere.radius()<radius) bsphere.radius()=radius;
    }

    bsphere.radius()+=1.0f;
    return bsphere;
}

void LightPointNode::traverse(osg::NodeVisitor& nv)
{
    if (_lightPointList.empty())
    {
        // no light points so no op.
        return;
    }

    //#define USE_TIMER
    #ifdef USE_TIMER
    osg::Timer timer;
    osg::Timer_t t1=0,t2=0,t3=0,t4=0,t5=0,t6=0,t7=0,t8=0;
    #endif

#ifdef USE_TIMER
    t1 = timer.tick();
#endif

    osgUtil::CullVisitor* cv = nv.asCullVisitor();

#ifdef USE_TIMER
    t2 = timer.tick();
#endif

    // should we disable small feature culling here?
    if (cv /*&& !cv->isCulled(_bbox)*/)
    {

        osg::Matrix matrix = *(cv->getModelViewMatrix());
        osg::RefMatrix& projection = *(cv->getProjectionMatrix());
        osgUtil::StateGraph* rg = cv->getCurrentStateGraph();

        if (rg->leaves_empty())
        {
            // this is first leaf to be added to StateGraph
            // and therefore should not already know current render bin,
            // so need to add it.
            cv->getCurrentRenderBin()->addStateGraph(rg);
        }

#ifdef USE_TIMER
        t3 = timer.tick();
#endif

        LightPointDrawable* drawable = NULL;
        osg::Referenced* object = rg->getUserData();
        if (object)
        {
            if (typeid(*object)==typeid(LightPointDrawable))
            {
                // resuse the user data attached to the render graph.
                drawable = static_cast<LightPointDrawable*>(object);

            }
            else if (typeid(*object)==typeid(LightPointSpriteDrawable))
            {
                drawable = static_cast<LightPointSpriteDrawable*>(object);
            }
            else
            {
                // will need to replace UserData.
                OSG_WARN << "Warning: Replacing osgUtil::StateGraph::_userData to support osgSim::LightPointNode, may have undefined results."<<std::endl;
            }
        }

        if (!drawable)
        {
            drawable = _pointSprites ? new LightPointSpriteDrawable : new LightPointDrawable;
            rg->setUserData(drawable);

            if (cv->getFrameStamp())
            {
                drawable->setSimulationTime(cv->getFrameStamp()->getSimulationTime());
            }
        }

        // search for a drawable in the RenderLeaf list equal to the attached the one attached to StateGraph user data
        // as this will be our special light point drawable.
        osgUtil::StateGraph::LeafList::iterator litr;
        for(litr = rg->_leaves.begin();
            litr != rg->_leaves.end() && (*litr)->_drawable.get()!=drawable;
            ++litr)
        {}

        if (litr == rg->_leaves.end())
        {
            // haven't found the drawable added in the RenderLeaf list, therefore this may be the
            // first time through LightPointNode in this frame, so need to add drawable into the StateGraph RenderLeaf list
            // and update its time signatures.

            drawable->reset();
            rg->addLeaf(new osgUtil::RenderLeaf(drawable,&projection,NULL,FLT_MAX));

            // need to update the drawable's frame count.
            if (cv->getFrameStamp())
            {
                drawable->updateSimulationTime(cv->getFrameStamp()->getSimulationTime());
            }

        }

#ifdef USE_TIMER
        t4 = timer.tick();
#endif

#ifdef USE_TIMER
        t7 = timer.tick();
#endif

        if (cv->getComputeNearFarMode() != osgUtil::CullVisitor::DO_NOT_COMPUTE_NEAR_FAR)
            cv->updateCalculatedNearFar(matrix,_bbox);

        const float minimumIntensity = 1.0f/256.0f;
        const osg::Vec3 eyePoint = cv->getEyeLocal();

        double time=drawable->getSimulationTime();
        double timeInterval=drawable->getSimulationTimeInterval();

        const osg::Polytope clipvol(cv->getCurrentCullingSet().getFrustum());
        const bool computeClipping = false;//(clipvol.getCurrentMask()!=0);

        //LightPointDrawable::ColorPosition cp;
        for(LightPointList::iterator itr=_lightPointList.begin();
            itr!=_lightPointList.end();
            ++itr)
        {
            const LightPoint& lp = *itr;

            if (!lp._on) continue;

            const osg::Vec3& position = lp._position;

            // skip light point if it is not contianed in the view frustum.
            if (computeClipping && !clipvol.contains(position)) continue;

            // delta vector between eyepoint and light point.
            osg::Vec3 dv(eyePoint-position);

            float intensity = (_lightSystem.valid()) ? _lightSystem->getIntensity() : lp._intensity;

            // slip light point if its intensity is 0.0 or negative.
            if (intensity<=minimumIntensity) continue;

            // (SIB) Clip on distance, if close to limit, add transparency
            float distanceFactor = 1.0f;
            if (_maxVisibleDistance2!=FLT_MAX)
            {
                if (dv.length2()>_maxVisibleDistance2) continue;
                else if (_maxVisibleDistance2 > 0)
                    distanceFactor = 1.0f - osg::square(dv.length2() / _maxVisibleDistance2);
            }

            osg::Vec4 color = lp._color;

            // check the sector.
            if (lp._sector.valid())
            {
                intensity *= (*lp._sector)(dv);

                // skip light point if it is intensity is 0.0 or negative.
                if (intensity<=minimumIntensity) continue;

            }

            // temporary accounting of intensity.
            //color *= intensity;

            // check the blink sequence.
            bool doBlink = lp._blinkSequence.valid();
            if (doBlink && _lightSystem.valid())
                doBlink = (_lightSystem->getAnimationState() == LightPointSystem::ANIMATION_ON);

            if (doBlink)
            {
                osg::Vec4 bs = lp._blinkSequence->color(time,timeInterval);
                color[0] *= bs[0];
                color[1] *= bs[1];
                color[2] *= bs[2];
                color[3] *= bs[3];
            }

            // if alpha value is less than the min intentsity then skip
            if (color[3]<=minimumIntensity) continue;

            float pixelSize = cv->pixelSize(position,lp._radius);

            //            cout << "pixelsize = "<<pixelSize<<endl;

            // adjust pixel size to account for intensity.
            if (intensity!=1.0) pixelSize *= sqrt(intensity);

            // adjust alpha to account for max range (Fade on distance)
            color[3] *= distanceFactor;

            // round up to the minimum pixel size if required.
            float orgPixelSize = pixelSize;
            if (pixelSize<_minPixelSize) pixelSize = _minPixelSize;

            osg::Vec3 xpos(position*matrix);

            if (lp._blendingMode==LightPoint::BLENDED)
            {
                if (pixelSize<1.0f)
                {
                    // need to use alpha blending...
                    color[3] *= pixelSize;
                    // color[3] *= osg::square(pixelSize);

                    if (color[3]<=minimumIntensity) continue;

                    drawable->addBlendedLightPoint(0, xpos,color);
                }
                else if (pixelSize<_maxPixelSize)
                {

                    unsigned int lowerBoundPixelSize = (unsigned int)pixelSize;
                    float remainder = osg::square(pixelSize-(float)lowerBoundPixelSize);

                    // (SIB) Add transparency if pixel is clamped to minpixelsize
                    if (orgPixelSize<_minPixelSize)
                        color[3] *= (2.0/3.0) + (1.0/3.0) * sqrt(orgPixelSize / pixelSize);

                    drawable->addBlendedLightPoint(lowerBoundPixelSize-1, xpos,color);
                    color[3] *= remainder;
                    drawable->addBlendedLightPoint(lowerBoundPixelSize, xpos,color);
                }
                else // use a billboard geometry.
                {
                    drawable->addBlendedLightPoint((unsigned int)(_maxPixelSize-1.0), xpos,color);
                }
            }
            else // ADDITIVE blending.
            {
                if (pixelSize<1.0f)
                {
                    // need to use alpha blending...
                    color[3] *= pixelSize;
                    // color[3] *= osg::square(pixelSize);

                    if (color[3]<=minimumIntensity) continue;

                    drawable->addAdditiveLightPoint(0, xpos,color);
                }
                else if (pixelSize<_maxPixelSize)
                {

                    unsigned int lowerBoundPixelSize = (unsigned int)pixelSize;
                    float remainder = osg::square(pixelSize-(float)lowerBoundPixelSize);

                    // (SIB) Add transparency if pixel is clamped to minpixelsize
                    if (orgPixelSize<_minPixelSize)
                        color[3] *= (2.0/3.0) + (1.0/3.0) * sqrt(orgPixelSize / pixelSize);

                    float alpha = color[3];
                    color[3] = alpha*(1.0f-remainder);
                    drawable->addAdditiveLightPoint(lowerBoundPixelSize-1, xpos,color);
                    color[3] = alpha*remainder;
                    drawable->addAdditiveLightPoint(lowerBoundPixelSize, xpos,color);
                }
                else // use a billboard geometry.
                {
                    drawable->addAdditiveLightPoint((unsigned int)(_maxPixelSize-1.0), xpos,color);
                }
            }
        }

#ifdef USE_TIMER
        t8 = timer.tick();
#endif

    }
#ifdef USE_TIMER
    cout << "compute"<<endl;
    cout << "  t2-t1="<<t2-t1<<endl;
    cout << "  t4-t3="<<t4-t3<<endl;
    cout << "  t6-t5="<<t6-t5<<endl;
    cout << "  t8-t7="<<t8-t7<<endl;
    cout << "_lightPointList.size()="<<_lightPointList.size()<<endl;
    cout << "  t8-t7/size = "<<(float)(t8-t7)/(float)_lightPointList.size()<<endl;
#endif
}

} // end of namespace
<commit_msg>fix compile error if OSGUTIL_RENDERBACKEND_USE_REF_PTR not defined in include/osgUtil/RenderLeaf<commit_after>/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * 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
 * OpenSceneGraph Public License for more details.
*/

#include <osgSim/LightPointNode>
#include <osgSim/LightPointSystem>

#include "LightPointDrawable.h"
#include "LightPointSpriteDrawable.h"

#include <osg/Timer>
#include <osg/BoundingBox>
#include <osg/BlendFunc>
#include <osg/Material>
#include <osg/PointSprite>

#include <osgUtil/CullVisitor>

#include <typeinfo>

namespace osgSim
{

osg::StateSet* getSingletonLightPointSystemSet()
{
    static osg::ref_ptr<osg::StateSet> s_stateset = 0;
    if (!s_stateset)
    {
        s_stateset = new osg::StateSet;
        // force light point nodes to be drawn after everything else by picking a rendering bin number after
        // the transparent bin.
        s_stateset->setRenderBinDetails(20,"DepthSortedBin");
    }
    return s_stateset.get();
}

LightPointNode::LightPointNode():
    _minPixelSize(0.0f),
    _maxPixelSize(30.0f),
    _maxVisibleDistance2(FLT_MAX),
    _lightSystem(0),
    _pointSprites(false)
{
    setStateSet(getSingletonLightPointSystemSet());
}

/** Copy constructor using CopyOp to manage deep vs shallow copy.*/
LightPointNode::LightPointNode(const LightPointNode& lpn,const osg::CopyOp& copyop):
    osg::Node(lpn,copyop),
    _bbox(lpn._bbox),
    _lightPointList(lpn._lightPointList),
    _minPixelSize(lpn._minPixelSize),
    _maxPixelSize(lpn._maxPixelSize),
    _maxVisibleDistance2(lpn._maxVisibleDistance2),
    _lightSystem(lpn._lightSystem),
    _pointSprites(lpn._pointSprites)
{
}

unsigned int LightPointNode::addLightPoint(const LightPoint& lp)
{
    unsigned int num = _lightPointList.size();
    _lightPointList.push_back(lp);
    dirtyBound();
    return num;
}

void LightPointNode::removeLightPoint(unsigned int pos)
{
    if (pos<_lightPointList.size())
    {
        _lightPointList.erase(_lightPointList.begin()+pos);
        dirtyBound();
    }
    dirtyBound();
}

osg::BoundingSphere LightPointNode::computeBound() const
{
    osg::BoundingSphere bsphere;
    bsphere.init();
    _bbox.init();

    if (_lightPointList.empty())
    {
        return bsphere;
    }

    LightPointList::const_iterator itr;
    for(itr=_lightPointList.begin();
        itr!=_lightPointList.end();
        ++itr)
    {
        _bbox.expandBy(itr->_position);
    }

    bsphere.set(_bbox.center(),0.0f);

    for(itr=_lightPointList.begin();
        itr!=_lightPointList.end();
        ++itr)
    {
        osg::Vec3 dv(itr->_position-bsphere.center());
        float radius = dv.length()+itr->_radius;
        if (bsphere.radius()<radius) bsphere.radius()=radius;
    }

    bsphere.radius()+=1.0f;
    return bsphere;
}

void LightPointNode::traverse(osg::NodeVisitor& nv)
{
    if (_lightPointList.empty())
    {
        // no light points so no op.
        return;
    }

    //#define USE_TIMER
    #ifdef USE_TIMER
    osg::Timer timer;
    osg::Timer_t t1=0,t2=0,t3=0,t4=0,t5=0,t6=0,t7=0,t8=0;
    #endif

#ifdef USE_TIMER
    t1 = timer.tick();
#endif

    osgUtil::CullVisitor* cv = nv.asCullVisitor();

#ifdef USE_TIMER
    t2 = timer.tick();
#endif

    // should we disable small feature culling here?
    if (cv /*&& !cv->isCulled(_bbox)*/)
    {

        osg::Matrix matrix = *(cv->getModelViewMatrix());
        osg::RefMatrix& projection = *(cv->getProjectionMatrix());
        osgUtil::StateGraph* rg = cv->getCurrentStateGraph();

        if (rg->leaves_empty())
        {
            // this is first leaf to be added to StateGraph
            // and therefore should not already know current render bin,
            // so need to add it.
            cv->getCurrentRenderBin()->addStateGraph(rg);
        }

#ifdef USE_TIMER
        t3 = timer.tick();
#endif

        LightPointDrawable* drawable = NULL;
        osg::Referenced* object = rg->getUserData();
        if (object)
        {
            if (typeid(*object)==typeid(LightPointDrawable))
            {
                // resuse the user data attached to the render graph.
                drawable = static_cast<LightPointDrawable*>(object);

            }
            else if (typeid(*object)==typeid(LightPointSpriteDrawable))
            {
                drawable = static_cast<LightPointSpriteDrawable*>(object);
            }
            else
            {
                // will need to replace UserData.
                OSG_WARN << "Warning: Replacing osgUtil::StateGraph::_userData to support osgSim::LightPointNode, may have undefined results."<<std::endl;
            }
        }

        if (!drawable)
        {
            drawable = _pointSprites ? new LightPointSpriteDrawable : new LightPointDrawable;
            rg->setUserData(drawable);

            if (cv->getFrameStamp())
            {
                drawable->setSimulationTime(cv->getFrameStamp()->getSimulationTime());
            }
        }

        // search for a drawable in the RenderLeaf list equal to the attached the one attached to StateGraph user data
        // as this will be our special light point drawable.
        osgUtil::StateGraph::LeafList::iterator litr;
        for(litr = rg->_leaves.begin();
            litr != rg->_leaves.end() && (*litr)->getDrawable()!=drawable;
            ++litr)
        {}

        if (litr == rg->_leaves.end())
        {
            // haven't found the drawable added in the RenderLeaf list, therefore this may be the
            // first time through LightPointNode in this frame, so need to add drawable into the StateGraph RenderLeaf list
            // and update its time signatures.

            drawable->reset();
            rg->addLeaf(new osgUtil::RenderLeaf(drawable,&projection,NULL,FLT_MAX));

            // need to update the drawable's frame count.
            if (cv->getFrameStamp())
            {
                drawable->updateSimulationTime(cv->getFrameStamp()->getSimulationTime());
            }

        }

#ifdef USE_TIMER
        t4 = timer.tick();
#endif

#ifdef USE_TIMER
        t7 = timer.tick();
#endif

        if (cv->getComputeNearFarMode() != osgUtil::CullVisitor::DO_NOT_COMPUTE_NEAR_FAR)
            cv->updateCalculatedNearFar(matrix,_bbox);

        const float minimumIntensity = 1.0f/256.0f;
        const osg::Vec3 eyePoint = cv->getEyeLocal();

        double time=drawable->getSimulationTime();
        double timeInterval=drawable->getSimulationTimeInterval();

        const osg::Polytope clipvol(cv->getCurrentCullingSet().getFrustum());
        const bool computeClipping = false;//(clipvol.getCurrentMask()!=0);

        //LightPointDrawable::ColorPosition cp;
        for(LightPointList::iterator itr=_lightPointList.begin();
            itr!=_lightPointList.end();
            ++itr)
        {
            const LightPoint& lp = *itr;

            if (!lp._on) continue;

            const osg::Vec3& position = lp._position;

            // skip light point if it is not contianed in the view frustum.
            if (computeClipping && !clipvol.contains(position)) continue;

            // delta vector between eyepoint and light point.
            osg::Vec3 dv(eyePoint-position);

            float intensity = (_lightSystem.valid()) ? _lightSystem->getIntensity() : lp._intensity;

            // slip light point if its intensity is 0.0 or negative.
            if (intensity<=minimumIntensity) continue;

            // (SIB) Clip on distance, if close to limit, add transparency
            float distanceFactor = 1.0f;
            if (_maxVisibleDistance2!=FLT_MAX)
            {
                if (dv.length2()>_maxVisibleDistance2) continue;
                else if (_maxVisibleDistance2 > 0)
                    distanceFactor = 1.0f - osg::square(dv.length2() / _maxVisibleDistance2);
            }

            osg::Vec4 color = lp._color;

            // check the sector.
            if (lp._sector.valid())
            {
                intensity *= (*lp._sector)(dv);

                // skip light point if it is intensity is 0.0 or negative.
                if (intensity<=minimumIntensity) continue;

            }

            // temporary accounting of intensity.
            //color *= intensity;

            // check the blink sequence.
            bool doBlink = lp._blinkSequence.valid();
            if (doBlink && _lightSystem.valid())
                doBlink = (_lightSystem->getAnimationState() == LightPointSystem::ANIMATION_ON);

            if (doBlink)
            {
                osg::Vec4 bs = lp._blinkSequence->color(time,timeInterval);
                color[0] *= bs[0];
                color[1] *= bs[1];
                color[2] *= bs[2];
                color[3] *= bs[3];
            }

            // if alpha value is less than the min intentsity then skip
            if (color[3]<=minimumIntensity) continue;

            float pixelSize = cv->pixelSize(position,lp._radius);

            //            cout << "pixelsize = "<<pixelSize<<endl;

            // adjust pixel size to account for intensity.
            if (intensity!=1.0) pixelSize *= sqrt(intensity);

            // adjust alpha to account for max range (Fade on distance)
            color[3] *= distanceFactor;

            // round up to the minimum pixel size if required.
            float orgPixelSize = pixelSize;
            if (pixelSize<_minPixelSize) pixelSize = _minPixelSize;

            osg::Vec3 xpos(position*matrix);

            if (lp._blendingMode==LightPoint::BLENDED)
            {
                if (pixelSize<1.0f)
                {
                    // need to use alpha blending...
                    color[3] *= pixelSize;
                    // color[3] *= osg::square(pixelSize);

                    if (color[3]<=minimumIntensity) continue;

                    drawable->addBlendedLightPoint(0, xpos,color);
                }
                else if (pixelSize<_maxPixelSize)
                {

                    unsigned int lowerBoundPixelSize = (unsigned int)pixelSize;
                    float remainder = osg::square(pixelSize-(float)lowerBoundPixelSize);

                    // (SIB) Add transparency if pixel is clamped to minpixelsize
                    if (orgPixelSize<_minPixelSize)
                        color[3] *= (2.0/3.0) + (1.0/3.0) * sqrt(orgPixelSize / pixelSize);

                    drawable->addBlendedLightPoint(lowerBoundPixelSize-1, xpos,color);
                    color[3] *= remainder;
                    drawable->addBlendedLightPoint(lowerBoundPixelSize, xpos,color);
                }
                else // use a billboard geometry.
                {
                    drawable->addBlendedLightPoint((unsigned int)(_maxPixelSize-1.0), xpos,color);
                }
            }
            else // ADDITIVE blending.
            {
                if (pixelSize<1.0f)
                {
                    // need to use alpha blending...
                    color[3] *= pixelSize;
                    // color[3] *= osg::square(pixelSize);

                    if (color[3]<=minimumIntensity) continue;

                    drawable->addAdditiveLightPoint(0, xpos,color);
                }
                else if (pixelSize<_maxPixelSize)
                {

                    unsigned int lowerBoundPixelSize = (unsigned int)pixelSize;
                    float remainder = osg::square(pixelSize-(float)lowerBoundPixelSize);

                    // (SIB) Add transparency if pixel is clamped to minpixelsize
                    if (orgPixelSize<_minPixelSize)
                        color[3] *= (2.0/3.0) + (1.0/3.0) * sqrt(orgPixelSize / pixelSize);

                    float alpha = color[3];
                    color[3] = alpha*(1.0f-remainder);
                    drawable->addAdditiveLightPoint(lowerBoundPixelSize-1, xpos,color);
                    color[3] = alpha*remainder;
                    drawable->addAdditiveLightPoint(lowerBoundPixelSize, xpos,color);
                }
                else // use a billboard geometry.
                {
                    drawable->addAdditiveLightPoint((unsigned int)(_maxPixelSize-1.0), xpos,color);
                }
            }
        }

#ifdef USE_TIMER
        t8 = timer.tick();
#endif

    }
#ifdef USE_TIMER
    cout << "compute"<<endl;
    cout << "  t2-t1="<<t2-t1<<endl;
    cout << "  t4-t3="<<t4-t3<<endl;
    cout << "  t6-t5="<<t6-t5<<endl;
    cout << "  t8-t7="<<t8-t7<<endl;
    cout << "_lightPointList.size()="<<_lightPointList.size()<<endl;
    cout << "  t8-t7/size = "<<(float)(t8-t7)/(float)_lightPointList.size()<<endl;
#endif
}

} // end of namespace
<|endoftext|>


<commit_before>/*
* this file is part of the oxygen gtk engine
* Copyright (c) 2010 Hugo Pereira Da Costa <hugo@oxygen-icons.org>
*
* inspired notably from kdelibs/kdeui/color/kcolorutils.h
* Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Zack Rusin <zack@kde.org>
*
* This  library is free  software; you can  redistribute it and/or
* modify it  under  the terms  of the  GNU Lesser  General  Public
* License  as published  by the Free  Software  Foundation; either
* version 2 of the License, or( at your option ) any later version.
*
* 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., 51  Franklin St, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/

#include "oxygenapplicationname.h"
#include "oxygengtkutils.h"
#include "config.h"

#include <fstream>
#include <iostream>
#include <sstream>

namespace Oxygen
{

    //__________________________________________________________________________
    void ApplicationName::initialize( void )
    {

        // get application name from gtk
        const std::string gtkAppName( fromGtk() );

        // get application name from pid
        const std::string pidAppName( fromPid( getpid() ) );

        #if OXYGEN_DEBUG
        std::cerr << "ApplicationName::initialize -"
            << " from pid: " << pidAppName
            << " from gtk: " << gtkAppName
            << std::endl;
        #endif

        // initialize to unknown
        _name = Unknown;

        if( pidAppName == "opera" ) _name = Opera;
        else if( gtkAppName == "eclipse" || gtkAppName == "Eclipse" ) _name = Eclipse;
        else if( pidAppName == "java" ) {

            if( !( gtkAppName.empty() || gtkAppName == "<unknown>" ) ) _name = JavaSwt;
            else _name = Java;

        } else if( gtkAppName == "acroread" ) _name = Acrobat;
        else if( gtkAppName == "soffice" ) _name = OpenOffice;
        else if( gtkAppName == "gimp" ) _name = Gimp;
        else if(
            gtkAppName == "chromium" ||
            gtkAppName == "chromium-browser" ||
            gtkAppName == "google-chrome" ) _name = GoogleChrome;
        else {

            // tag all mozilla-like applications (XUL)
            static const std::string XulAppNames[] =
            {
                "firefox",
                "thunderbird",
                "seamonkey",
                "iceweasel",
                "icecat",
                "icedove",
                "xulrunner",
                "komodo",
                ""
            };

            for( unsigned int index = 0; !XulAppNames[index].empty(); ++index )
            {
                if( gtkAppName.find( XulAppNames[index] ) == 0 )
                {
                    _name = XUL;
                    break;
                }
            }
        }

        #if OXYGEN_DEBUG
        std::cerr << "ApplicationName::initialize -"
            << " from pid: " << pidAppName
            << " from gtk: " << gtkAppName
            << " internal: " << *this
            << std::endl;
        #endif

    }

    //__________________________________________________________________________
    bool ApplicationName::isGtkDialogWidget( GtkWidget* widget ) const
    {
        GtkWidget* parent( gtk_widget_get_toplevel( widget ) );

        // check parent
        return parent && GTK_IS_DIALOG( parent );
    }

    //__________________________________________________________________________
    bool ApplicationName::useFlatBackground( GtkWidget* widget ) const
    {

        // check application name
        if( !(
            isXul() ||
            isAcrobat() ||
            isJavaSwt() ||
            isGoogleChrome() ||
            isEclipse() ) ) return false;

        // check for Gtk dialog type
        if( widget && isGtkDialogWidget( widget ) ) return false;

        // return true in all other cases
        return true;

    }

    //__________________________________________________________________________
    std::string ApplicationName::fromGtk( void ) const
    {
        if( const char* gtkAppName = g_get_prgname() ) return gtkAppName;
        else return "";
    }

    //__________________________________________________________________________
    std::string ApplicationName::fromPid( int pid ) const
    {

        // generate /proc filename
        std::ostringstream filename;
        filename << "/proc/" << pid << "/cmdline";

        // try read file
        std::ifstream in( filename.str().c_str() );
        if( !in ) return std::string();

        /*
        somehow std::getline gets some extra crap (non char) from the procfile
        one has to use ifstream::getline, and pass it a fixed size line
        */
        char lineC[1024];
        in.getline( lineC, 1024, '\n' );
        std::string line( lineC );

        // get position of last "/" character, and truncate accordingly
        const size_t pos = line.rfind( '/' );
        if( pos == std::string::npos ) return line;
        else return line.substr( pos+1 );

    }

    //__________________________________________________________________________
    std::ostream& operator << ( std::ostream& out, const ApplicationName& app )
    {
        switch( app._name )
        {
            default:
            case Unknown: out << "Unknown"; break;
            case Acrobat: out << "Acrobat"; break;
            case XUL: out << "XUL (Mozilla)"; break;
            case Gimp: out << "Gimp"; break;
            case OpenOffice: out << "OpenOffice"; break;
            case GoogleChrome: out << "GoogleChrome"; break;
            case Opera: out << "Opera"; break;
            case Java: out << "Java"; break;
            case JavaSwt: out << "JavaSwt"; break;
            case Eclipse: out << "Eclipse"; break;
        }

        return out;
    }
}
<commit_msg>Add env var to override application name detection<commit_after>/*
* this file is part of the oxygen gtk engine
* Copyright (c) 2010 Hugo Pereira Da Costa <hugo@oxygen-icons.org>
*
* inspired notably from kdelibs/kdeui/color/kcolorutils.h
* Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
* Copyright (C) 2007 Thomas Zander <zander@kde.org>
* Copyright (C) 2007 Zack Rusin <zack@kde.org>
*
* This  library is free  software; you can  redistribute it and/or
* modify it  under  the terms  of the  GNU Lesser  General  Public
* License  as published  by the Free  Software  Foundation; either
* version 2 of the License, or( at your option ) any later version.
*
* 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., 51  Franklin St, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/

#include "oxygenapplicationname.h"
#include "oxygengtkutils.h"
#include "config.h"

#include <fstream>
#include <iostream>
#include <sstream>
#include <cstdlib>

namespace Oxygen
{

    //__________________________________________________________________________
    void ApplicationName::initialize( void )
    {

        // get application name from gtk
        std::string gtkAppName( fromGtk() );

        // get application name from pid
        std::string pidAppName( fromPid( getpid() ) );

        #if OXYGEN_DEBUG
        std::cerr << "ApplicationName::initialize -"
            << " from pid: " << pidAppName
            << " from gtk: " << gtkAppName
            << std::endl;
        #endif

        // initialize to unknown
        _name = Unknown;

        // Way to override appname detection
        const char* envAppName(getenv("OXYGEN_APPLICATION_NAME_OVERRIDE"));
        if(envAppName)
        {
            gtkAppName=envAppName;
            pidAppName=envAppName;
        }

        if( pidAppName == "opera" ) _name = Opera;
        else if( gtkAppName == "eclipse" || gtkAppName == "Eclipse" ) _name = Eclipse;
        else if( pidAppName == "java" ) {

            if( !( gtkAppName.empty() || gtkAppName == "<unknown>" ) ) _name = JavaSwt;
            else _name = Java;

        } else if( gtkAppName == "acroread" ) _name = Acrobat;
        else if( gtkAppName == "soffice" ) _name = OpenOffice;
        else if( gtkAppName == "gimp" ) _name = Gimp;
        else if(
            gtkAppName == "chromium" ||
            gtkAppName == "chromium-browser" ||
            gtkAppName == "google-chrome" ) _name = GoogleChrome;
        else {

            // tag all mozilla-like applications (XUL)
            static const std::string XulAppNames[] =
            {
                "firefox",
                "thunderbird",
                "seamonkey",
                "iceweasel",
                "icecat",
                "icedove",
                "xulrunner",
                "komodo",
                ""
            };

            for( unsigned int index = 0; !XulAppNames[index].empty(); ++index )
            {
                if( gtkAppName.find( XulAppNames[index] ) == 0 )
                {
                    _name = XUL;
                    break;
                }
            }
        }

        #if OXYGEN_DEBUG
        std::cerr << "ApplicationName::initialize -"
            << " from pid: " << pidAppName
            << " from gtk: " << gtkAppName
            << " internal: " << *this
            << std::endl;
        #endif

    }

    //__________________________________________________________________________
    bool ApplicationName::isGtkDialogWidget( GtkWidget* widget ) const
    {
        GtkWidget* parent( gtk_widget_get_toplevel( widget ) );

        // check parent
        return parent && GTK_IS_DIALOG( parent );
    }

    //__________________________________________________________________________
    bool ApplicationName::useFlatBackground( GtkWidget* widget ) const
    {

        // check application name
        if( !(
            isXul() ||
            isAcrobat() ||
            isJavaSwt() ||
            isGoogleChrome() ||
            isEclipse() ) ) return false;

        // check for Gtk dialog type
        if( widget && isGtkDialogWidget( widget ) ) return false;

        // return true in all other cases
        return true;

    }

    //__________________________________________________________________________
    std::string ApplicationName::fromGtk( void ) const
    {
        if( const char* gtkAppName = g_get_prgname() ) return gtkAppName;
        else return "";
    }

    //__________________________________________________________________________
    std::string ApplicationName::fromPid( int pid ) const
    {

        // generate /proc filename
        std::ostringstream filename;
        filename << "/proc/" << pid << "/cmdline";

        // try read file
        std::ifstream in( filename.str().c_str() );
        if( !in ) return std::string();

        /*
        somehow std::getline gets some extra crap (non char) from the procfile
        one has to use ifstream::getline, and pass it a fixed size line
        */
        char lineC[1024];
        in.getline( lineC, 1024, '\n' );
        std::string line( lineC );

        // get position of last "/" character, and truncate accordingly
        const size_t pos = line.rfind( '/' );
        if( pos == std::string::npos ) return line;
        else return line.substr( pos+1 );

    }

    //__________________________________________________________________________
    std::ostream& operator << ( std::ostream& out, const ApplicationName& app )
    {
        switch( app._name )
        {
            default:
            case Unknown: out << "Unknown"; break;
            case Acrobat: out << "Acrobat"; break;
            case XUL: out << "XUL (Mozilla)"; break;
            case Gimp: out << "Gimp"; break;
            case OpenOffice: out << "OpenOffice"; break;
            case GoogleChrome: out << "GoogleChrome"; break;
            case Opera: out << "Opera"; break;
            case Java: out << "Java"; break;
            case JavaSwt: out << "JavaSwt"; break;
            case Eclipse: out << "Eclipse"; break;
        }

        return out;
    }
}
<|endoftext|>


<commit_before>/*!
 *  Copyright (c) 2018 by Contributors
 *
 * Lower warp memory to use local memory
 * and shuffle intrinsics.
 *
 * \file lower_warp_memory.cc
 */
// Thanks to Andrew Adams and Vinod Grover for
// explaining the concept of warp shuffle.
#include <tvm/ir.h>
#include <tvm/ir_mutator.h>
#include <tvm/ir_visitor.h>
#include <tvm/ir_pass.h>
#include <unordered_set>
#include "./ir_util.h"
#include "../arithmetic/compute_expr.h"
#include "../runtime/thread_storage_scope.h"

namespace tvm {
namespace ir {

// Rewrite Rule
//
// There is no special warp memory in most GPUs.
// Instead, we can stripe the data into threads
// and store the data into local memory.
//
// This requires us to do the following rewriting:
// - Rewrite allocation to use local memory.
// - Rewrite store of warp memory to local store.
// - Rewrite load of waro memory to local plus a shuffle.
//
// Define a generic shuffle instrinsic warp_shuffle(data, warp_index).
// We can use the following rewriting rule
//
// Before rewrite,
//
//   alloc warp warp_mem[n * warp_size * m]
//   store warp_mem[m * warp_index + (warp_size * m) * y + x]
//   load warp_mem[m * z + (warp_size * m) * y + x]
//   subject to x \in [0, m), y \in [0, n)
//
// After rewrite:
//
//   alloc local local_mem[n * m]
//   store warp_mem[m * y + x]
//   warp_shuffle(load warp_mem[m * y + x], z)
//   subject to (m * y + x) is invariant to warp_index

// Algorithm
//
// To implement this rewrite rule, we can do the follow step:
// For each warp memory alloc
// - Use linear pattern detector on load index to find m
// - Deduce n given warp_size and alloc size
// - Now that we have m, n, warp_size, we can proceed with the rewrite

// Visitor to find m in pattern
// store warp_mem[m * warp_index + (warp_size * m) * y + x]
class WarpStoreCoeffFinder : private IRVisitor {
 public:
  WarpStoreCoeffFinder(const Variable* buffer,
                       Var warp_index)
      : buffer_(buffer), warp_index_(warp_index) {
  }
  // find the warp co-efficient in the statement given the warp size
  int Find(const Stmt& stmt) {
    this->Visit(stmt);
    return warp_coeff_;
  }

 private:
  /// Visitor implementation
  void Visit_(const Store *op) final {
    if (op->buffer_var.get() == buffer_) {
      if (op->value.type().lanes() == 1) {
        UpdatePattern(op->index);
      } else {
        Expr base;
        CHECK(GetRamp1Base(op->index, op->value.type().lanes(), &base))
            << "LowerWarpMemory failed due to store index=" << op->index
            << ", can only handle continuous store";
        UpdatePattern(base);
      }
    } else {
      IRVisitor::Visit_(op);
    }
  }

  void UpdatePattern(const Expr& index) {
    Array<Expr> m =
        arith::DetectLinearEquation(index, {warp_index_});
    CHECK_EQ(m.size(), 2U)
        << "LowerWarpMemory failed due to store index=" << index;
    int coeff;
    CHECK(arith::GetConstInt(ir::Simplify(m[0]), &coeff) && coeff > 0)
        << "LowerWarpMemory failed due to store index=" << index
        << ", require positive constant coefficient on warp index";
    if (warp_coeff_ != 0) {
      CHECK_EQ(warp_coeff_, coeff)
          << "LowerWarpMemory failed due to two different store coefficient to warp index";
    } else {
      warp_coeff_ = coeff;
    }
  }

  // The buffer variable
  const Variable* buffer_;
  // the warp index
  Var warp_index_;
  // the coefficient
  int warp_coeff_{0};
};

// Visitor to find the warp index
class WarpIndexFinder : private IRVisitor {
 public:
  explicit WarpIndexFinder(int warp_size)
      : warp_size_(warp_size) {
  }
  // find the warp co-efficient in the statement given the warp size
  IterVar Find(const Stmt& stmt) {
    this->Visit(stmt);
    CHECK(warp_index_.defined())
        << "Cannot find warp index(threadIdx.x) within the scope of warp memory";
    return warp_index_;
  }

 private:
  void Visit(const NodeRef &node) final {
    if (warp_index_.defined()) return;
    IRVisitor::Visit(node);
  }

  /// Visitor implementation
  void Visit_(const AttrStmt *op) final {
    if (op->attr_key == attr::thread_extent) {
      IterVar iv(op->node.node_);
      if (iv->thread_tag == "threadIdx.x") {
        int value;
        CHECK(arith::GetConstInt(op->value, &value) &&
              value == warp_size_)
            << "Expect threadIdx.x 's size to be equal to warp size("
            << warp_size_ << ")" << " to enable warp memory"
            << " but get " << op->value << " instead";
        warp_index_ = iv;
      }
    }
    IRVisitor::Visit_(op);
  }
  // warp size
  int warp_size_{0};
  // the warp index
  IterVar warp_index_{nullptr};
};
// Mutator to change the read pattern
class WarpAccessRewriter : protected IRMutator {
 public:
  explicit WarpAccessRewriter(int warp_size)
      : warp_size_(warp_size) {}
  // Rewrite the allocate statement which transforms
  // warp memory to local memory.
  Stmt Rewrite(const Allocate* op, const Stmt& stmt) {
    buffer_ = op->buffer_var.get();
    int alloc_size = op->constant_allocation_size();
    CHECK_GT(alloc_size, 0)
        << "warp memory only support constant alloc size";
    alloc_size *= op->type.lanes();
    warp_index_ = WarpIndexFinder(warp_size_).Find(op->body)->var;
    warp_coeff_ = WarpStoreCoeffFinder(
        buffer_, warp_index_).Find(op->body);
    CHECK_EQ(alloc_size % (warp_size_ * warp_coeff_), 0)
        << "Warp memory must be multiple of warp size";
    warp_group_ = alloc_size / (warp_size_ * warp_coeff_);
    return Allocate::make(
        op->buffer_var,
        op->type,
        {make_const(Int(32), alloc_size / warp_size_)},
        op->condition,
        this->Mutate(op->body));
  }

 protected:
  Expr Mutate_(const Variable* op, const Expr& expr) {
    CHECK(op != buffer_)
        << "Cannot access address of warp memory directly";
    return IRMutator::Mutate_(op, expr);
  }

  Stmt Mutate_(const Store* op, const Stmt& stmt) {
    if (op->buffer_var.get() == buffer_) {
      Expr local_index, group;
      std::tie(local_index, group) = SplitIndexByGroup(op->index);
      return Store::make(op->buffer_var, op->value, local_index, op->predicate);
    } else {
      return IRMutator::Mutate_(op, stmt);
    }
  }

  Expr Mutate_(const Load* op, const Expr& expr) {
    if (op->buffer_var.get() == buffer_) {
      Expr local_index, group;
      std::tie(local_index, group) = SplitIndexByGroup(op->index);
      // invariance: local index must do not contain warp id
      CHECK(!ExprUseVar(local_index, {warp_index_.get()}))
          << "LowerWarpMemory failed to rewrite load to shuffle for index "
          << op->index << " local_index=" << local_index;
      Expr load_value = Load::make(
          op->type, op->buffer_var, local_index, op->predicate);
      return Call::make(load_value.type(),
                        intrinsic::tvm_warp_shuffle,
                        {load_value, group},
                        Call::Intrinsic);
    } else {
      return IRMutator::Mutate_(op, expr);
    }
  }
  // Split the index to the two component
  // <local_index, source_index>
  // local index is the index in the local
  // source index is the corresponding source index
  // in this access pattern.
  std::pair<Expr, Expr> SplitIndexByGroup(const Expr& index) {
    if (index.type().lanes() != 1) {
      Expr base, local_index, group;
      CHECK(GetRamp1Base(index, index.type().lanes(), &base));
      std::tie(local_index, group) = SplitIndexByGroup(base);
      local_index =
          Ramp::make(local_index, make_const(local_index.type(), 1), index.type().lanes());
      return std::make_pair(local_index, group);
    }
    Expr m = make_const(index.type(), warp_coeff_);
    Range rng = Range::make_by_min_extent(
        make_zero(index.type()), make_const(index.type(), warp_size_));
    Map<Var, Range> vrange({{warp_index_, rng}});

    // simple case, warp index is on the highest.
    if (warp_group_ == 1) {
      Expr x = Simplify(index % m, vrange);
      Expr z = Simplify(index / m, vrange);
      return std::make_pair(x, z);
    } else {
      Expr x = Simplify(index % m, vrange);
      Expr y = index / make_const(index.type(), warp_coeff_ * warp_size_);
      y = y * m + x;
      Expr z = index % make_const(index.type(), warp_coeff_ * warp_size_) / m;
      return std::make_pair(Simplify(y, vrange), Simplify(z, vrange));
    }
  }

 private:
  // the warp size
  int warp_size_{0};
  // The buffer variable
  const Variable* buffer_;
  // Warp index
  Var warp_index_;
  // the coefficient m
  int warp_coeff_{0};
  // the coefficient n
  int warp_group_{0};
};

// Mutator to change the read pattern
class WarpMemoryRewriter : private IRMutator {
 public:
  explicit WarpMemoryRewriter(int warp_size)
      : warp_size_(warp_size) {
  }

  Stmt Rewrite(Stmt stmt) {
    if (warp_size_ == 1) return stmt;
    return this->Mutate(stmt);
  }

 private:
  Stmt Mutate_(const Allocate* op, const Stmt& stmt) {
    if (warp_buffer_.count(op->buffer_var.get())) {
      WarpAccessRewriter rewriter(warp_size_);
      return rewriter.Rewrite(op, stmt);
    } else {
      return IRMutator::Mutate_(op, stmt);
    }
  }

  Stmt Mutate_(const AttrStmt* op, const Stmt& stmt) {
    using runtime::StorageScope;
    if (op->attr_key == attr::storage_scope) {
      const Variable* buf = op->node.as<Variable>();
      StorageScope scope = StorageScope::make(op->value.as<StringImm>()->value);
      if (scope.rank == runtime::StorageRank::kWarp) {
        warp_buffer_.insert(buf);
        Stmt ret = IRMutator::Mutate_(op, stmt);
        op = ret.as<AttrStmt>();
        return AttrStmt::make(
            op->node, op->attr_key, StringImm::make("local"), op->body);
      }
    }
    return IRMutator::Mutate_(op, stmt);
  }

  int warp_size_{0};
  std::unordered_set<const Variable*> warp_buffer_;
};

LoweredFunc
LowerWarpMemory(LoweredFunc f, int warp_size) {
  CHECK_EQ(f->func_type, kDeviceFunc);
  auto n = std::make_shared<LoweredFuncNode>(*f.operator->());
  n->body = WarpMemoryRewriter(warp_size).Rewrite(n->body);
  return LoweredFunc(n);
}

}  // namespace ir
}  // namespace tvm
<commit_msg>[PASS] More reliable error message for lower warp (#1065)<commit_after>/*!
 *  Copyright (c) 2018 by Contributors
 *
 * Lower warp memory to use local memory
 * and shuffle intrinsics.
 *
 * \file lower_warp_memory.cc
 */
// Thanks to Andrew Adams and Vinod Grover for
// explaining the concept of warp shuffle.
#include <tvm/ir.h>
#include <tvm/ir_mutator.h>
#include <tvm/ir_visitor.h>
#include <tvm/ir_pass.h>
#include <unordered_set>
#include "./ir_util.h"
#include "../arithmetic/compute_expr.h"
#include "../runtime/thread_storage_scope.h"

namespace tvm {
namespace ir {

// Rewrite Rule
//
// There is no special warp memory in most GPUs.
// Instead, we can stripe the data into threads
// and store the data into local memory.
//
// This requires us to do the following rewriting:
// - Rewrite allocation to use local memory.
// - Rewrite store of warp memory to local store.
// - Rewrite load of waro memory to local plus a shuffle.
//
// Define a generic shuffle instrinsic warp_shuffle(data, warp_index).
// We can use the following rewriting rule
//
// Before rewrite,
//
//   alloc warp warp_mem[n * warp_size * m]
//   store warp_mem[m * warp_index + (warp_size * m) * y + x]
//   load warp_mem[m * z + (warp_size * m) * y + x]
//   subject to x \in [0, m), y \in [0, n)
//
// After rewrite:
//
//   alloc local local_mem[n * m]
//   store warp_mem[m * y + x]
//   warp_shuffle(load warp_mem[m * y + x], z)
//   subject to (m * y + x) is invariant to warp_index

// Algorithm
//
// To implement this rewrite rule, we can do the follow step:
// For each warp memory alloc
// - Use linear pattern detector on load index to find m
// - Deduce n given warp_size and alloc size
// - Now that we have m, n, warp_size, we can proceed with the rewrite

// Visitor to find m in pattern
// store warp_mem[m * warp_index + (warp_size * m) * y + x]
class WarpStoreCoeffFinder : private IRVisitor {
 public:
  WarpStoreCoeffFinder(const Variable* buffer,
                       Var warp_index)
      : buffer_(buffer), warp_index_(warp_index) {
  }
  // find the warp co-efficient in the statement given the warp size
  int Find(const Stmt& stmt) {
    this->Visit(stmt);
    return warp_coeff_;
  }

 private:
  /// Visitor implementation
  void Visit_(const Store *op) final {
    if (op->buffer_var.get() == buffer_) {
      if (op->value.type().lanes() == 1) {
        UpdatePattern(op->index);
      } else {
        Expr base;
        CHECK(GetRamp1Base(op->index, op->value.type().lanes(), &base))
            << "LowerWarpMemory failed due to store index=" << op->index
            << ", can only handle continuous store";
        UpdatePattern(base);
      }
    } else {
      IRVisitor::Visit_(op);
    }
  }

  void UpdatePattern(const Expr& index) {
    Array<Expr> m =
        arith::DetectLinearEquation(index, {warp_index_});
    CHECK_EQ(m.size(), 2U)
        << "LowerWarpMemory failed due to store index=" << index;
    int coeff;
    Expr mcoeff = ir::Simplify(m[0]);

    CHECK(arith::GetConstInt(mcoeff, &coeff) && coeff > 0)
        << "LowerWarpMemory failed due to store index=" << index
        << ", require positive constant coefficient on warp index " << warp_index_
        << " but get " << mcoeff;

    if (warp_coeff_ != 0) {
      CHECK_EQ(warp_coeff_, coeff)
          << "LowerWarpMemory failed due to two different store coefficient to warp index";
    } else {
      warp_coeff_ = coeff;
    }
  }

  // The buffer variable
  const Variable* buffer_;
  // the warp index
  Var warp_index_;
  // the coefficient
  int warp_coeff_{0};
};

// Visitor to find the warp index
class WarpIndexFinder : private IRVisitor {
 public:
  explicit WarpIndexFinder(int warp_size)
      : warp_size_(warp_size) {
  }
  // find the warp co-efficient in the statement given the warp size
  IterVar Find(const Stmt& stmt) {
    this->Visit(stmt);
    CHECK(warp_index_.defined())
        << "Cannot find warp index(threadIdx.x) within the scope of warp memory";
    return warp_index_;
  }

 private:
  /// Visitor implementation
  void Visit_(const AttrStmt *op) final {
    if (op->attr_key == attr::thread_extent) {
      IterVar iv(op->node.node_);
      if (iv->thread_tag == "threadIdx.x") {
        int value;
        CHECK(arith::GetConstInt(op->value, &value) &&
              value == warp_size_)
            << "Expect threadIdx.x 's size to be equal to warp size("
            << warp_size_ << ")" << " to enable warp memory"
            << " but get " << op->value << " instead";
        if (warp_index_.defined()) {
          CHECK(warp_index_.same_as(iv))
              << "Find two instance of " << warp_index_->thread_tag
              << " in the same kernel. "
              << "Please create it using thread_axis once and reuse the axis "
              << "across multiple binds in the same kernel";
        } else {
          warp_index_ = iv;
        }
      }
    }
    IRVisitor::Visit_(op);
  }
  // warp size
  int warp_size_{0};
  // the warp index
  IterVar warp_index_{nullptr};
};
// Mutator to change the read pattern
class WarpAccessRewriter : protected IRMutator {
 public:
  explicit WarpAccessRewriter(int warp_size)
      : warp_size_(warp_size) {}
  // Rewrite the allocate statement which transforms
  // warp memory to local memory.
  Stmt Rewrite(const Allocate* op, const Stmt& stmt) {
    buffer_ = op->buffer_var.get();
    int alloc_size = op->constant_allocation_size();
    CHECK_GT(alloc_size, 0)
        << "warp memory only support constant alloc size";
    alloc_size *= op->type.lanes();
    warp_index_ = WarpIndexFinder(warp_size_).Find(op->body)->var;
    warp_coeff_ = WarpStoreCoeffFinder(
        buffer_, warp_index_).Find(op->body);
    CHECK_EQ(alloc_size % (warp_size_ * warp_coeff_), 0)
        << "Warp memory must be multiple of warp size";
    warp_group_ = alloc_size / (warp_size_ * warp_coeff_);
    return Allocate::make(
        op->buffer_var,
        op->type,
        {make_const(Int(32), alloc_size / warp_size_)},
        op->condition,
        this->Mutate(op->body));
  }

 protected:
  Expr Mutate_(const Variable* op, const Expr& expr) {
    CHECK(op != buffer_)
        << "Cannot access address of warp memory directly";
    return IRMutator::Mutate_(op, expr);
  }

  Stmt Mutate_(const Store* op, const Stmt& stmt) {
    if (op->buffer_var.get() == buffer_) {
      Expr local_index, group;
      std::tie(local_index, group) = SplitIndexByGroup(op->index);
      return Store::make(op->buffer_var, op->value, local_index, op->predicate);
    } else {
      return IRMutator::Mutate_(op, stmt);
    }
  }

  Expr Mutate_(const Load* op, const Expr& expr) {
    if (op->buffer_var.get() == buffer_) {
      Expr local_index, group;
      std::tie(local_index, group) = SplitIndexByGroup(op->index);
      // invariance: local index must do not contain warp id
      CHECK(!ExprUseVar(local_index, {warp_index_.get()}))
          << "LowerWarpMemory failed to rewrite load to shuffle for index "
          << op->index << " local_index=" << local_index;
      Expr load_value = Load::make(
          op->type, op->buffer_var, local_index, op->predicate);
      return Call::make(load_value.type(),
                        intrinsic::tvm_warp_shuffle,
                        {load_value, group},
                        Call::Intrinsic);
    } else {
      return IRMutator::Mutate_(op, expr);
    }
  }
  // Split the index to the two component
  // <local_index, source_index>
  // local index is the index in the local
  // source index is the corresponding source index
  // in this access pattern.
  std::pair<Expr, Expr> SplitIndexByGroup(const Expr& index) {
    if (index.type().lanes() != 1) {
      Expr base, local_index, group;
      CHECK(GetRamp1Base(index, index.type().lanes(), &base));
      std::tie(local_index, group) = SplitIndexByGroup(base);
      local_index =
          Ramp::make(local_index, make_const(local_index.type(), 1), index.type().lanes());
      return std::make_pair(local_index, group);
    }
    Expr m = make_const(index.type(), warp_coeff_);
    Range rng = Range::make_by_min_extent(
        make_zero(index.type()), make_const(index.type(), warp_size_));
    Map<Var, Range> vrange({{warp_index_, rng}});

    // simple case, warp index is on the highest.
    if (warp_group_ == 1) {
      Expr x = Simplify(index % m, vrange);
      Expr z = Simplify(index / m, vrange);
      return std::make_pair(x, z);
    } else {
      Expr x = Simplify(index % m, vrange);
      Expr y = index / make_const(index.type(), warp_coeff_ * warp_size_);
      y = y * m + x;
      Expr z = index % make_const(index.type(), warp_coeff_ * warp_size_) / m;
      return std::make_pair(Simplify(y, vrange), Simplify(z, vrange));
    }
  }

 private:
  // the warp size
  int warp_size_{0};
  // The buffer variable
  const Variable* buffer_;
  // Warp index
  Var warp_index_;
  // the coefficient m
  int warp_coeff_{0};
  // the coefficient n
  int warp_group_{0};
};

// Mutator to change the read pattern
class WarpMemoryRewriter : private IRMutator {
 public:
  explicit WarpMemoryRewriter(int warp_size)
      : warp_size_(warp_size) {
  }

  Stmt Rewrite(Stmt stmt) {
    if (warp_size_ == 1) return stmt;
    return this->Mutate(stmt);
  }

 private:
  Stmt Mutate_(const Allocate* op, const Stmt& stmt) {
    if (warp_buffer_.count(op->buffer_var.get())) {
      WarpAccessRewriter rewriter(warp_size_);
      return rewriter.Rewrite(op, stmt);
    } else {
      return IRMutator::Mutate_(op, stmt);
    }
  }

  Stmt Mutate_(const AttrStmt* op, const Stmt& stmt) {
    using runtime::StorageScope;
    if (op->attr_key == attr::storage_scope) {
      const Variable* buf = op->node.as<Variable>();
      StorageScope scope = StorageScope::make(op->value.as<StringImm>()->value);
      if (scope.rank == runtime::StorageRank::kWarp) {
        warp_buffer_.insert(buf);
        Stmt ret = IRMutator::Mutate_(op, stmt);
        op = ret.as<AttrStmt>();
        return AttrStmt::make(
            op->node, op->attr_key, StringImm::make("local"), op->body);
      }
    }
    return IRMutator::Mutate_(op, stmt);
  }

  int warp_size_{0};
  std::unordered_set<const Variable*> warp_buffer_;
};

LoweredFunc
LowerWarpMemory(LoweredFunc f, int warp_size) {
  CHECK_EQ(f->func_type, kDeviceFunc);
  auto n = std::make_shared<LoweredFuncNode>(*f.operator->());
  n->body = WarpMemoryRewriter(warp_size).Rewrite(n->body);
  return LoweredFunc(n);
}

}  // namespace ir
}  // namespace tvm
<|endoftext|>


<commit_before>/**
 * \author Freek Stulp
 *
 * This file is part of DmpBbo, a set of libraries and programs for the
 * black-box optimization of dynamical movement primitives.
 * Copyright (C) 2022 Freek Stulp
 *
 * DmpBbo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * DmpBbo 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 DmpBbo.  If not, see <http://www.gnu.org/licenses/>.
 */

#define EIGEN_RUNTIME_NO_MALLOC  // Enable runtime tests for allocations

#include <eigen3/Eigen/Core>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <string>

#include "dynamicalsystems/DynamicalSystem.hpp"
#include "eigenutils/eigen_realtime_check.hpp"

using namespace std;
using namespace DmpBbo;
using namespace Eigen;
using namespace nlohmann;

int main(int n_args, char** args)
{
  string directory = "../../../demos//json/";

  vector<string> filenames;
  for (string system : {"Exponential", "Sigmoid", "SpringDamper"})
    for (string dim : {"1D", "2D"})
      filenames.push_back(system + "System_" + dim + ".json");
  filenames.push_back("TimeSystem.json");

  for (string filename : filenames) {
    filename = directory + filename;
    cout << "================================================================="
         << endl;
    cout << filename << endl;

    cout << "===============" << endl;
    ifstream file(filename);
    json j = json::parse(file);
    cout << j << endl;

    cout << "===============" << endl;
    DynamicalSystem* d = j.get<DynamicalSystem*>();
    cout << *d << endl;

    VectorXd x(d->dim(), 1);
    VectorXd x_updated(d->dim(), 1);
    VectorXd xd(d->dim(), 1);

    double dt = 0.01;
    for (string integration_method : {"Euler", "Runge-Kutta", "Default"}) {
      cout << "===============" << endl;
      cout << "Integrating with: " << integration_method << endl;
      d->integrateStart(x, xd);

      ENTERING_REAL_TIME_CRITICAL_CODE
      if (integration_method == "Euler") {
        for (int t = 1; t < 10; t++)
          d->integrateStepEuler(dt, x, x_updated, xd);
      } else if (integration_method == "Runge-Kutta") {
        for (int t = 1; t < 10; t++)
          d->integrateStepRungeKutta(dt, x, x_updated, xd);
      } else {
        for (int t = 1; t < 10; t++) d->integrateStep(dt, x, x_updated, xd);
      }
      EXITING_REAL_TIME_CRITICAL_CODE
    }
  }

  return 0;
}
<commit_msg>Input args for demo<commit_after>/**
 * \author Freek Stulp
 *
 * This file is part of DmpBbo, a set of libraries and programs for the
 * black-box optimization of dynamical movement primitives.
 * Copyright (C) 2022 Freek Stulp
 *
 * DmpBbo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * DmpBbo 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 DmpBbo.  If not, see <http://www.gnu.org/licenses/>.
 */

#define EIGEN_RUNTIME_NO_MALLOC  // Enable runtime tests for allocations

#include <eigen3/Eigen/Core>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <string>

#include "dynamicalsystems/DynamicalSystem.hpp"
#include "eigenutils/eigen_realtime_check.hpp"

using namespace std;
using namespace DmpBbo;
using namespace Eigen;
using namespace nlohmann;

int main(int n_args, char** args)
{
  // ./exec  => read json from default files, do not write output
  // ./exec <input json 1> .. <input json N>

  vector<string> filenames;

  if (n_args==1) {
    // No directories and filename provided, add the files from the json directory
    string input_directory = "../../../demos/json/";
    for (string system : {"Exponential", "Sigmoid", "SpringDamper"})
      for (string dim : {"1D", "2D"})
        filenames.push_back(input_directory + system + "System_" + dim + ".json");
    filenames.push_back(input_directory+"TimeSystem.json");
    filenames.push_back(input_directory+"TimeSystemCountDown.json");

  } else {
    for (int i_args=1; i_args<n_args; i_args++)
      filenames.push_back(args[i_args]);

  }

  for (string filename : filenames) {
    cout << "========================================================" << endl;
    cout << filename << endl;

    cout << "===============" << endl;
    ifstream file(filename);
    json j = json::parse(file);
    cout << j << endl;

    cout << "===============" << endl;
    DynamicalSystem* d = j.get<DynamicalSystem*>();
    cout << *d << endl;

    VectorXd x(d->dim(), 1);
    VectorXd x_updated(d->dim(), 1);
    VectorXd xd(d->dim(), 1);

    double dt = 0.01;
    for (string integration_method : {"Euler", "Runge-Kutta", "Default"}) {
      cout << "===============" << endl;
      cout << "Integrating with: " << integration_method << endl;
      d->integrateStart(x, xd);

      ENTERING_REAL_TIME_CRITICAL_CODE
      if (integration_method == "Euler") {
        for (int t = 1; t < 10; t++)
          d->integrateStepEuler(dt, x, x_updated, xd);
      } else if (integration_method == "Runge-Kutta") {
        for (int t = 1; t < 10; t++)
          d->integrateStepRungeKutta(dt, x, x_updated, xd);
      } else {
        for (int t = 1; t < 10; t++)
          d->integrateStep(dt, x, x_updated, xd);
      }
      EXITING_REAL_TIME_CRITICAL_CODE
    }
  }

  return 0;
}
<|endoftext|>


<commit_before>/**
 * @file
 *
 * @brief Write key sets using yaml-cpp
 *
 * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
 */

#include "write.hpp"
#include "yaml-cpp/yaml.h"

#include <kdbassert.h>
#include <kdbease.h>
#include <kdblogger.h>
#include <kdbplugin.h>

#include <fstream>

using namespace std;
using namespace kdb;

namespace
{

using KeySetPair = pair<KeySet, KeySet>;

/**
 * @brief This function checks if `element` is an array element of `parent`.
 *
 * @pre The key `child` must be below `parent`.
 *
 * @param parent This parameter specifies a parent key.
 * @param keys This variable stores a direct or indirect child of `parent`.
 *
 * @retval true If `element` is an array element
 * @retval false Otherwise
 */
bool isArrayElementOf (Key const & parent, Key const & child)
{
	char const * relative = elektraKeyGetRelativeName (*child, *parent);
	auto offsetIndex = ckdb::elektraArrayValidateBaseNameString (relative);
	if (offsetIndex <= 0) return false;
	// Skip `#`, underscores and digits
	relative += 2 * offsetIndex;
	// The next character has to be the separation char (`/`) or end of string
	if (relative[0] != '\0' && relative[0] != '/') return false;

	return true;
}

/**
 * @brief This function determines if the given key is an array parent.
 *
 * @param parent This parameter specifies a possible array parent.
 * @param keys This variable stores the key set of `parent`.
 *
 * @retval true If `parent` is the parent key of an array
 * @retval false Otherwise
 */
bool isArrayParent (Key const & parent, KeySet const & keys)
{
	for (auto const & key : keys)
	{
		if (!key.isBelow (parent)) continue;
		if (!isArrayElementOf (parent, key)) return false;
	}

	return true;
}

/**
 * @brief Split `keys` into two key sets, one for array parents and one for all other keys.
 *
 * @param keys This parameter contains the key set this function splits.
 *
 * @return A pair of key sets, where the first key set contains all array parents and the second key set contains all other keys
 */
KeySetPair splitArrayParentsOther (KeySet const & keys)
{
	KeySet arrayParents;
	KeySet others;

	keys.rewind ();
	Key previous;
	for (; keys.next (); previous = keys.current ())
	{
		bool previousIsArray = previous && previous.hasMeta ("array");

		if (!previousIsArray && keys.current ().getBaseName ()[0] == '#')
		{
			if (!keys.current ().isDirectBelow (previous))
			{
				Key directParent{ keys.current ().getName (), KEY_END };
				ckdb::keySetBaseName (*directParent, NULL);
				previousIsArray = isArrayParent (directParent, keys);
				if (previousIsArray)
				{
					arrayParents.append (directParent);
				}
			}
			else
			{
				previousIsArray = isArrayParent (previous, keys);
			}
		}

		(previousIsArray ? arrayParents : others).append (previous);
	}
	(previous && previous.hasMeta ("array") ? arrayParents : others).append (previous);

	return make_pair (arrayParents, others);
}

/**
 * @brief This function splits `keys` into two key sets, one for array parents and elements, and the other one for all other keys.
 *
 * @param arrayParents This key set contains a (copy) of all array parents of `keys`.
 * @param keys This parameter contains the key set this function splits.
 *
 * @return A pair of key sets, where the first key set contains all array parents and elements,
 *         and the second key set contains all other keys
 */
KeySetPair splitArrayOther (KeySet const & arrayParents, KeySet const & keys)
{
	KeySet others = keys.dup ();
	KeySet arrays;

	for (auto const & parent : arrayParents)
	{
		arrays.append (others.cut (parent));
	}

	return make_pair (arrays, others);
}

/**
 * @brief This function returns a `NameIterator` starting at the first level that is not part of `parent`.
 *
 * @pre The parameter `key` must be a child of `parent`.
 *
 * @param key This is the key for which this function returns a relative iterator.
 * @param parent This key specifies the part of the name iterator that will not be part of the return value of this function.
 *
 * @returns A relative iterator that starts with the first part of the name of `key` not contained in `parent`.
 */
NameIterator relativeKeyIterator (Key const & key, Key const & parent)
{
	auto parentIterator = parent.begin ();
	auto keyIterator = key.begin ();
	while (parentIterator != parent.end () && keyIterator != key.end ())
	{
		parentIterator++;
		keyIterator++;
	}
	return keyIterator;
}

/**
 * @brief This function checks if a key name specifies an array key.
 *
 * If the key name contains a valid array index that is smaller than `unsigned long long`, then the function will also return this index.
 *
 * @param nameIterator This iterator specifies the name of the key.
 *
 * @retval (true, arrayIndex) if `name` specifies an array key, where `arrayIndex` specifies the index stored in the array key.
 * @retval (false, 0) otherwise
 */
std::pair<bool, unsigned long long> isArrayIndex (NameIterator const & nameIterator)
{
	string const name = *nameIterator;
	auto const offsetIndex = ckdb::elektraArrayValidateBaseNameString (name.c_str ());
	auto const isArrayElement = offsetIndex >= 1;
	return { isArrayElement, isArrayElement ? stoull (name.substr (offsetIndex)) : 0 };
}

/**
 * @brief This function creates a YAML node representing a key value.
 *
 * @param key This key specifies the data that should be saved in the YAML node returned by this function.
 *
 * @note Since YAML does not support non-empty binary data directly this function replaces data stored in binary keys with the string
 *       `Unsupported binary value!`. If you need support for binary data, please load the Base64 before you use YAML CPP.
 *
 * @returns A new YAML node containing the data specified in `key`
 */
YAML::Node createMetaDataNode (Key const & key)
{
	return key.hasMeta ("array") ?
		       YAML::Node (YAML::NodeType::Sequence) :
		       key.getBinarySize () == 0 ? YAML::Node (YAML::NodeType::Null) :
						   YAML::Node (key.isBinary () ? "Unsupported binary value!" : key.getString ());
}

/**
 * @brief This function creates a YAML Node containing a key value and optionally metadata.
 *
 * @param key This key specifies the data that should be saved in the YAML node returned by this function.
 *
 * @note Since YAML does not support non-empty binary data directly this function replaces data stored in binary keys with the string
 *       `Unsupported binary value!`. If you need support for binary data, please load the Base64 before you use YAML CPP.
 *
 * @returns A new YAML node containing the data and metadata specified in `key`
 */
YAML::Node createLeafNode (Key & key)
{

	YAML::Node metaNode{ YAML::Node (YAML::NodeType::Map) };
	YAML::Node dataNode = createMetaDataNode (key);

	key.rewindMeta ();
	while (Key meta = key.nextMeta ())
	{
		if (meta.getName () == "array" || meta.getName () == "binary") continue;
		if (meta.getName () == "type" && meta.getString () == "binary")
		{
			dataNode.SetTag ("tag:yaml.org,2002:binary");
			continue;
		}
		metaNode[meta.getName ()] = meta.getString ();
		ELEKTRA_LOG_DEBUG ("Add metakey “%s: %s”", meta.getName ().c_str (), meta.getString ().c_str ());
	}

	if (metaNode.size () <= 0)
	{
		ELEKTRA_LOG_DEBUG ("Return leaf node with value “%s”",
				   dataNode.IsNull () ? "~" : dataNode.IsSequence () ? "[]" : dataNode.as<string> ().c_str ());
		return dataNode;
	}

	YAML::Node node{ YAML::Node (YAML::NodeType::Sequence) };
	node.SetTag ("!elektra/meta");
	node.push_back (dataNode);
	node.push_back (metaNode);

#ifdef HAVE_LOGGER
	ostringstream data;
	data << node;
	ELEKTRA_LOG_DEBUG ("Return meta leaf node with value “%s”", data.str ().c_str ());
#endif

	return node;
}

/**
 * @brief This function adds `null` elements to the given YAML collection.
 *
 * @param sequence This node stores the collection to which this function adds `numberOfElements` empty elements.
 * @param numberOfElements This parameter specifies the number of empty element this function adds to `sequence`.
 */
void addEmptyArrayElements (YAML::Node & sequence, unsigned long long const numberOfElements)
{
	ELEKTRA_LOG_DEBUG ("Add %lld empty array elements", numberOfElements);
	for (auto missingFields = numberOfElements; missingFields > 0; missingFields--)
	{
		sequence.push_back ({});
	}
}

/**
 * @brief This function adds a key to a YAML node.
 *
 * @param data This node stores the data specified via `keyIterator`.
 * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`.
 * @param key This parameter specifies the key that should be added to `data`.
 */
void addKeyNoArray (YAML::Node & data, NameIterator & keyIterator, Key & key)
{
	if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined);

#ifdef HAVE_LOGGER
	ostringstream output;
	output << data;
	ELEKTRA_LOG_DEBUG ("Add key part “%s” to node “%s”", (*keyIterator).c_str (), output.str ().c_str ());
#endif

	if (keyIterator == key.end ())
	{
		ELEKTRA_LOG_DEBUG ("Create leaf node for key “%s”", key.getName ().c_str ());
		data = createLeafNode (key);
		return;
	}
	if (keyIterator == --key.end ())
	{
		data[*keyIterator] = createLeafNode (key);
		return;
	}

	YAML::Node node;

	node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node ();
	data[*keyIterator] = node;
	addKeyNoArray (node, ++keyIterator, key);
}

/**
 * @brief This function adds a key to a YAML node.
 *
 * @param data This node stores the data specified via `keyIterator`.
 * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`.
 * @param key This parameter specifies the key that should be added to `data`.
 */
void addKeyArray (YAML::Node & data, NameIterator & keyIterator, Key & key)
{
	auto const isArrayAndIndex = isArrayIndex (keyIterator);
	auto const isArrayElement = isArrayAndIndex.first;
	auto const arrayIndex = isArrayAndIndex.second;

	if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined);

#ifdef HAVE_LOGGER
	ostringstream output;
	output << data;
	ELEKTRA_LOG_DEBUG ("Add key part “%s” to node “%s”", (*keyIterator).c_str (), output.str ().c_str ());
#endif

	if (keyIterator == key.end ())
	{
		ELEKTRA_LOG_DEBUG ("Create leaf node for key “%s”", key.getName ().c_str ());
		data = createLeafNode (key);
		return;
	}
	if (keyIterator == --key.end ())
	{
		if (isArrayElement)
		{
			addEmptyArrayElements (data, arrayIndex - data.size ());
			data.push_back (createLeafNode (key));
		}
		else
		{
			data[*keyIterator] = createLeafNode (key);
		}

		return;
	}

	YAML::Node node;

	if (isArrayElement)
	{
		node = (data[arrayIndex] && !data[arrayIndex].IsScalar ()) ? data[arrayIndex] : YAML::Node ();
		data[arrayIndex] = node;
	}
	else
	{
		node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node ();
		data[*keyIterator] = node;
	}
	addKeyArray (node, ++keyIterator, key);
}

/**
 * @brief This function adds a key set to a YAML node.
 *
 * @param data This node stores the data specified via `mappings`.
 * @param mappings This keyset specifies all keys and values this function adds to `data`.
 * @param parent This key is the root of all keys stored in `mappings`.
 */
void addKeys (YAML::Node & data, KeySet const & mappings, Key const & parent, bool const isArray = false)
{
	for (auto key : mappings)
	{
		ELEKTRA_LOG_DEBUG ("Convert key “%s”: “%s”", key.getName ().c_str (),
				   key.getBinarySize () == 0 ? "NULL" : key.isString () ? key.getString ().c_str () : "binary value!");
		NameIterator keyIterator = relativeKeyIterator (key, parent);

		if (isArray)
		{
			addKeyArray (data, keyIterator, key);
		}
		else
		{
			addKeyNoArray (data, keyIterator, key);
		}

#ifdef HAVE_LOGGER
		ostringstream output;
		output << data;

		ELEKTRA_LOG_DEBUG ("Converted Data:");
		ELEKTRA_LOG_DEBUG ("__________");

		istringstream stream (output.str ());
		for (string line; std::getline (stream, line);)
		{
			ELEKTRA_LOG_DEBUG ("%s", line.c_str ());
		}

		ELEKTRA_LOG_DEBUG ("__________");
#endif
	}
}

} // end namespace

/**
 * @brief This function saves the key-value pairs stored in `mappings` as YAML data in the location specified via `parent`.
 *
 * @param mappings This key set stores the mappings that should be saved as YAML data.
 * @param parent This key specifies the path to the YAML data file that should be written.
 */
void yamlcpp::yamlWrite (KeySet const & mappings, Key const & parent)
{

	KeySet arrayParents;
	KeySet arrays;
	KeySet nonArrays;
	tie (arrayParents, std::ignore) = splitArrayParentsOther (mappings);
	tie (arrays, nonArrays) = splitArrayOther (arrayParents, mappings);

	auto data = YAML::Node ();
	addKeys (data, arrays, parent, true);
	addKeys (data, nonArrays, parent);

#ifdef HAVE_LOGGER
	ELEKTRA_LOG_DEBUG ("Write Data:");
	ELEKTRA_LOG_DEBUG ("__________");

	ostringstream outputString;
	outputString << data;
	istringstream stream (outputString.str ());
	for (string line; std::getline (stream, line);)
	{
		ELEKTRA_LOG_DEBUG ("%s", line.c_str ());
	}

	ELEKTRA_LOG_DEBUG ("__________");
#endif

	ofstream output (parent.getString ());
	output << data;
}
<commit_msg>YAML CPP: Update Doxygen documentation<commit_after>/**
 * @file
 *
 * @brief Write key sets using yaml-cpp
 *
 * @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
 */

#include "write.hpp"
#include "yaml-cpp/yaml.h"

#include <kdbassert.h>
#include <kdbease.h>
#include <kdblogger.h>
#include <kdbplugin.h>

#include <fstream>

using namespace std;
using namespace kdb;

namespace
{

using KeySetPair = pair<KeySet, KeySet>;

/**
 * @brief This function checks if `element` is an array element of `parent`.
 *
 * @pre The key `child` must be below `parent`.
 *
 * @param parent This parameter specifies a parent key.
 * @param keys This variable stores a direct or indirect child of `parent`.
 *
 * @retval true If `element` is an array element
 * @retval false Otherwise
 */
bool isArrayElementOf (Key const & parent, Key const & child)
{
	char const * relative = elektraKeyGetRelativeName (*child, *parent);
	auto offsetIndex = ckdb::elektraArrayValidateBaseNameString (relative);
	if (offsetIndex <= 0) return false;
	// Skip `#`, underscores and digits
	relative += 2 * offsetIndex;
	// The next character has to be the separation char (`/`) or end of string
	if (relative[0] != '\0' && relative[0] != '/') return false;

	return true;
}

/**
 * @brief This function determines if the given key is an array parent.
 *
 * @param parent This parameter specifies a possible array parent.
 * @param keys This variable stores the key set of `parent`.
 *
 * @retval true If `parent` is the parent key of an array
 * @retval false Otherwise
 */
bool isArrayParent (Key const & parent, KeySet const & keys)
{
	for (auto const & key : keys)
	{
		if (!key.isBelow (parent)) continue;
		if (!isArrayElementOf (parent, key)) return false;
	}

	return true;
}

/**
 * @brief Split `keys` into two key sets, one for array parents and one for all other keys.
 *
 * @note This function also adds empty parent keys for arrays, if they did not exist beforehand. For example for the key set that **only**
 *       contains the keys:
 *
 *       - `user/array/#0`, and
 *       - `user/array/#1`
 *
 *       the function will add the array parent `user/array` to the returned array parent key set.
 *
 * @param keys This parameter contains the key set this function splits.
 *
 * @return A pair of key sets, where the first key set contains all array parents and the second key set contains all other keys
 */
KeySetPair splitArrayParentsOther (KeySet const & keys)
{
	KeySet arrayParents;
	KeySet others;

	keys.rewind ();
	Key previous;
	for (; keys.next (); previous = keys.current ())
	{
		bool previousIsArray = previous && previous.hasMeta ("array");

		if (!previousIsArray && keys.current ().getBaseName ()[0] == '#')
		{
			if (!keys.current ().isDirectBelow (previous))
			{
				Key directParent{ keys.current ().getName (), KEY_END };
				ckdb::keySetBaseName (*directParent, NULL);
				previousIsArray = isArrayParent (directParent, keys);
				if (previousIsArray)
				{
					arrayParents.append (directParent);
				}
			}
			else
			{
				previousIsArray = isArrayParent (previous, keys);
			}
		}

		(previousIsArray ? arrayParents : others).append (previous);
	}
	(previous && previous.hasMeta ("array") ? arrayParents : others).append (previous);

	return make_pair (arrayParents, others);
}

/**
 * @brief This function splits `keys` into two key sets, one for array parents and elements, and the other one for all other keys.
 *
 * @param arrayParents This key set contains a (copy) of all array parents of `keys`.
 * @param keys This parameter contains the key set this function splits.
 *
 * @return A pair of key sets, where the first key set contains all array parents and elements,
 *         and the second key set contains all other keys
 */
KeySetPair splitArrayOther (KeySet const & arrayParents, KeySet const & keys)
{
	KeySet others = keys.dup ();
	KeySet arrays;

	for (auto const & parent : arrayParents)
	{
		arrays.append (others.cut (parent));
	}

	return make_pair (arrays, others);
}

/**
 * @brief This function returns a `NameIterator` starting at the first level that is not part of `parent`.
 *
 * @pre The parameter `key` must be a child of `parent`.
 *
 * @param key This is the key for which this function returns a relative iterator.
 * @param parent This key specifies the part of the name iterator that will not be part of the return value of this function.
 *
 * @returns A relative iterator that starts with the first part of the name of `key` not contained in `parent`.
 */
NameIterator relativeKeyIterator (Key const & key, Key const & parent)
{
	auto parentIterator = parent.begin ();
	auto keyIterator = key.begin ();
	while (parentIterator != parent.end () && keyIterator != key.end ())
	{
		parentIterator++;
		keyIterator++;
	}
	return keyIterator;
}

/**
 * @brief This function checks if a key name specifies an array key.
 *
 * If the key name contains a valid array index that is smaller than `unsigned long long`, then the function will also return this index.
 *
 * @param nameIterator This iterator specifies the name of the key.
 *
 * @retval (true, arrayIndex) if `name` specifies an array key, where `arrayIndex` specifies the index stored in the array key.
 * @retval (false, 0) otherwise
 */
std::pair<bool, unsigned long long> isArrayIndex (NameIterator const & nameIterator)
{
	string const name = *nameIterator;
	auto const offsetIndex = ckdb::elektraArrayValidateBaseNameString (name.c_str ());
	auto const isArrayElement = offsetIndex >= 1;
	return { isArrayElement, isArrayElement ? stoull (name.substr (offsetIndex)) : 0 };
}

/**
 * @brief This function creates a YAML node representing a key value.
 *
 * @param key This key specifies the data that should be saved in the YAML node returned by this function.
 *
 * @note Since YAML does not support non-empty binary data directly this function replaces data stored in binary keys with the string
 *       `Unsupported binary value!`. If you need support for binary data, please load the Base64 before you use YAML CPP.
 *
 * @returns A new YAML node containing the data specified in `key`
 */
YAML::Node createMetaDataNode (Key const & key)
{
	return key.hasMeta ("array") ?
		       YAML::Node (YAML::NodeType::Sequence) :
		       key.getBinarySize () == 0 ? YAML::Node (YAML::NodeType::Null) :
						   YAML::Node (key.isBinary () ? "Unsupported binary value!" : key.getString ());
}

/**
 * @brief This function creates a YAML Node containing a key value and optionally metadata.
 *
 * @param key This key specifies the data that should be saved in the YAML node returned by this function.
 *
 * @note Since YAML does not support non-empty binary data directly this function replaces data stored in binary keys with the string
 *       `Unsupported binary value!`. If you need support for binary data, please load the Base64 before you use YAML CPP.
 *
 * @returns A new YAML node containing the data and metadata specified in `key`
 */
YAML::Node createLeafNode (Key & key)
{

	YAML::Node metaNode{ YAML::Node (YAML::NodeType::Map) };
	YAML::Node dataNode = createMetaDataNode (key);

	key.rewindMeta ();
	while (Key meta = key.nextMeta ())
	{
		if (meta.getName () == "array" || meta.getName () == "binary") continue;
		if (meta.getName () == "type" && meta.getString () == "binary")
		{
			dataNode.SetTag ("tag:yaml.org,2002:binary");
			continue;
		}
		metaNode[meta.getName ()] = meta.getString ();
		ELEKTRA_LOG_DEBUG ("Add metakey “%s: %s”", meta.getName ().c_str (), meta.getString ().c_str ());
	}

	if (metaNode.size () <= 0)
	{
		ELEKTRA_LOG_DEBUG ("Return leaf node with value “%s”",
				   dataNode.IsNull () ? "~" : dataNode.IsSequence () ? "[]" : dataNode.as<string> ().c_str ());
		return dataNode;
	}

	YAML::Node node{ YAML::Node (YAML::NodeType::Sequence) };
	node.SetTag ("!elektra/meta");
	node.push_back (dataNode);
	node.push_back (metaNode);

#ifdef HAVE_LOGGER
	ostringstream data;
	data << node;
	ELEKTRA_LOG_DEBUG ("Return meta leaf node with value “%s”", data.str ().c_str ());
#endif

	return node;
}

/**
 * @brief This function adds `null` elements to the given YAML collection.
 *
 * @param sequence This node stores the collection to which this function adds `numberOfElements` empty elements.
 * @param numberOfElements This parameter specifies the number of empty element this function adds to `sequence`.
 */
void addEmptyArrayElements (YAML::Node & sequence, unsigned long long const numberOfElements)
{
	ELEKTRA_LOG_DEBUG ("Add %lld empty array elements", numberOfElements);
	for (auto missingFields = numberOfElements; missingFields > 0; missingFields--)
	{
		sequence.push_back ({});
	}
}

/**
 * @brief This function adds a key that is not part of any array to a YAML node.
 *
 * @param data This node stores the data specified via `keyIterator`.
 * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`.
 * @param key This parameter specifies the key that should be added to `data`.
 */
void addKeyNoArray (YAML::Node & data, NameIterator & keyIterator, Key & key)
{
	if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined);

#ifdef HAVE_LOGGER
	ostringstream output;
	output << data;
	ELEKTRA_LOG_DEBUG ("Add key part “%s” to node “%s”", (*keyIterator).c_str (), output.str ().c_str ());
#endif

	if (keyIterator == key.end ())
	{
		ELEKTRA_LOG_DEBUG ("Create leaf node for key “%s”", key.getName ().c_str ());
		data = createLeafNode (key);
		return;
	}
	if (keyIterator == --key.end ())
	{
		data[*keyIterator] = createLeafNode (key);
		return;
	}

	YAML::Node node;

	node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node ();
	data[*keyIterator] = node;
	addKeyNoArray (node, ++keyIterator, key);
}

/**
 * @brief This function adds a key that is either, element of an array, or an array parent to a YAML node.
 *
 * @param data This node stores the data specified via `keyIterator`.
 * @param keyIterator This iterator specifies the current part of the key name this function adds to `data`.
 * @param key This parameter specifies the key that should be added to `data`.
 */
void addKeyArray (YAML::Node & data, NameIterator & keyIterator, Key & key)
{
	auto const isArrayAndIndex = isArrayIndex (keyIterator);
	auto const isArrayElement = isArrayAndIndex.first;
	auto const arrayIndex = isArrayAndIndex.second;

	if (data.IsScalar ()) data = YAML::Node (YAML::NodeType::Undefined);

#ifdef HAVE_LOGGER
	ostringstream output;
	output << data;
	ELEKTRA_LOG_DEBUG ("Add key part “%s” to node “%s”", (*keyIterator).c_str (), output.str ().c_str ());
#endif

	if (keyIterator == key.end ())
	{
		ELEKTRA_LOG_DEBUG ("Create leaf node for key “%s”", key.getName ().c_str ());
		data = createLeafNode (key);
		return;
	}
	if (keyIterator == --key.end ())
	{
		if (isArrayElement)
		{
			addEmptyArrayElements (data, arrayIndex - data.size ());
			data.push_back (createLeafNode (key));
		}
		else
		{
			data[*keyIterator] = createLeafNode (key);
		}

		return;
	}

	YAML::Node node;

	if (isArrayElement)
	{
		node = (data[arrayIndex] && !data[arrayIndex].IsScalar ()) ? data[arrayIndex] : YAML::Node ();
		data[arrayIndex] = node;
	}
	else
	{
		node = (data[*keyIterator] && !data[*keyIterator].IsScalar ()) ? data[*keyIterator] : YAML::Node ();
		data[*keyIterator] = node;
	}
	addKeyArray (node, ++keyIterator, key);
}

/**
 * @brief This function adds a key set to a YAML node.
 *
 * @param data This node stores the data specified via `mappings`.
 * @param mappings This keyset specifies all keys and values this function adds to `data`.
 * @param parent This key is the root of all keys stored in `mappings`.
 * @param isArray This value specifies if the keys inside `keys` are all part of an array (either element or parent), or if none of them is
 *                part of an array.
 */
void addKeys (YAML::Node & data, KeySet const & mappings, Key const & parent, bool const isArray = false)
{
	for (auto key : mappings)
	{
		ELEKTRA_LOG_DEBUG ("Convert key “%s”: “%s”", key.getName ().c_str (),
				   key.getBinarySize () == 0 ? "NULL" : key.isString () ? key.getString ().c_str () : "binary value!");
		NameIterator keyIterator = relativeKeyIterator (key, parent);

		if (isArray)
		{
			addKeyArray (data, keyIterator, key);
		}
		else
		{
			addKeyNoArray (data, keyIterator, key);
		}

#ifdef HAVE_LOGGER
		ostringstream output;
		output << data;

		ELEKTRA_LOG_DEBUG ("Converted Data:");
		ELEKTRA_LOG_DEBUG ("__________");

		istringstream stream (output.str ());
		for (string line; std::getline (stream, line);)
		{
			ELEKTRA_LOG_DEBUG ("%s", line.c_str ());
		}

		ELEKTRA_LOG_DEBUG ("__________");
#endif
	}
}

} // end namespace

/**
 * @brief This function saves the key-value pairs stored in `mappings` as YAML data in the location specified via `parent`.
 *
 * @param mappings This key set stores the mappings that should be saved as YAML data.
 * @param parent This key specifies the path to the YAML data file that should be written.
 */
void yamlcpp::yamlWrite (KeySet const & mappings, Key const & parent)
{

	KeySet arrayParents;
	KeySet arrays;
	KeySet nonArrays;
	tie (arrayParents, std::ignore) = splitArrayParentsOther (mappings);
	tie (arrays, nonArrays) = splitArrayOther (arrayParents, mappings);

	auto data = YAML::Node ();
	addKeys (data, arrays, parent, true);
	addKeys (data, nonArrays, parent);

#ifdef HAVE_LOGGER
	ELEKTRA_LOG_DEBUG ("Write Data:");
	ELEKTRA_LOG_DEBUG ("__________");

	ostringstream outputString;
	outputString << data;
	istringstream stream (outputString.str ());
	for (string line; std::getline (stream, line);)
	{
		ELEKTRA_LOG_DEBUG ("%s", line.c_str ());
	}

	ELEKTRA_LOG_DEBUG ("__________");
#endif

	ofstream output (parent.getString ());
	output << data;
}
<|endoftext|>