<commit_before>#ifndef MJOLNIR_CORE_G_BAOAB_LANGEVIN_INTEGRATOR_HPP
#define MJOLNIR_CORE_G_BAOAB_LANGEVIN_INTEGRATOR_HPP
#include <mjolnir/core/SimulatorTraits.hpp>
#include <mjolnir/core/RandomNumberGenerator.hpp>
#include <mjolnir/core/System.hpp>
#include <mjolnir/core/ForceField.hpp>
#include <mjolnir/core/SystemMotionRemover.hpp>
#include <mjolnir/core/Unit.hpp>
#include <mjolnir/util/logger.hpp>

namespace mjolnir
{

// g-BAOAB Langevin integrator developed by the following papers
// Leimkuhler B., Matthews C., Proc. R. Soc. A Math. Phys. Eng. Sci. (2016)
template<typename traitsT>
class GBAOABLangevinIntegrator
{
  public:
    using traits_type      = traitsT;
    using real_type        = typename traits_type::real_type;
    using coordinate_type  = typename traits_type::coordinate_type;
    using indices_type     = std::array<std::size_t, 2>;
    using constraints_type = std::vector<std::pair<indices_type, real_type>>;
    using system_type      = System<traitsT>;
    using forcefield_type  = ForceField<traitsT>;
    using rng_type         = RandomNumberGenerator<traits_type>;
    using remover_type     = SystemMotionRemover<traits_type>;

  public:

    GBAOABLangevinIntegrator(const real_type dt, std::vector<real_type>&& gamma,
                             constraints_type&& constraint, remover_type&& remover,
                             std::size_t correction_iter_num, std::size_t max_iter_correction,
                             real_type correction_tolerance)
        : dt_(dt), halfdt_(dt / 2), gammas_(std::move(gamma)),
          exp_gamma_dt_(gammas_.size()), noise_coeff_ (gammas_.size()),
          constraints_(constraint),
          square_v0s_(constraint.size()),
          r_reduced_mass_(constraint.size()),
          remover_(std::move(remover)),
          correction_iter_num_(correction_iter_num),
          correction_max_iter_(max_iter_correction),
          correction_tolerance_dt_(correction_tolerance/ dt),
          correction_tolerance_dt_itr_(correction_tolerance/ dt * correction_iter_num),
          dt_in_correction_(dt * 0.5 / correction_iter_num_),
          r_dt_in_correction_(1 / dt_in_correction_),
          old_position_(gammas_.size()),
          old_pos_rattle_(gammas_.size())
    {}
    ~GBAOABLangevinIntegrator() = default;

    void initialize(system_type& sys, forcefield_type& ff, rng_type& rng);

    real_type step(const real_type time, system_type& sys, forcefield_type& ff,
                   rng_type& rng);

    void update(const system_type& sys)
    {
        if(!sys.has_attribute("temperature"))
        {
            throw std::out_of_range("mjolnir::g-BAOABLangevinIntegrator: "
                "Langevin Integrator requires reference temperature, but "
                "`temperature` is not found in `system.attribute`.");
        }
        this->temperature_ = sys.attribute("temperature");
        this->reset_parameter(sys);
        return;
    }

    real_type delta_t() const noexcept {return dt_;}
    std::vector<real_type> const& parameters() const noexcept {return gammas_;}

  private:

    void reset_parameter(const system_type& sys) noexcept
    {
        const auto kBT = physics::constants<real_type>::kB() * this->temperature_;
        for(std::size_t i=0; i<sys.size(); ++i)
        {
            const auto gamma     = this->gammas_.at(i);
            const auto gamma_dt  = -1 * gamma * this->dt_;
            this->exp_gamma_dt_.at(i) = std::exp(gamma_dt);
            this->noise_coeff_ .at(i) = std::sqrt(
                    kBT * (1 - std::exp(2 * gamma_dt)) * sys.rmass(i));
        }
        return;
    };

    coordinate_type gen_R(rng_type& rng) noexcept
    {
        const auto x = rng.gaussian();
        const auto y = rng.gaussian();
        const auto z = rng.gaussian();
        return math::make_coordinate<coordinate_type>(x, y, z);
    }

    void correct_coordinate(system_type& sys, const real_type tolerance)
    {
        MJOLNIR_GET_DEFAULT_LOGGER();

        std::size_t rattle_step = 0;
        while(rattle_step<correction_max_iter_)
        {
            bool corrected = false;
            for(std::size_t i=0; i<this->constraints_.size(); ++i)
            {
                const auto& constraint = constraints_[i];
                const auto& indices    = constraint.first;
                auto& p1  = sys.position(indices[0]);
                auto& p2  = sys.position(indices[1]);
                auto& v1  = sys.velocity(indices[0]);
                auto& v2  = sys.velocity(indices[1]);
                auto& op1 = this->old_pos_rattle_[indices[0]];
                auto& op2 = this->old_pos_rattle_[indices[1]];
                auto& rm1 = sys.rmass(indices[0]);
                auto& rm2 = sys.rmass(indices[1]);

                const auto      dp = p2 - p1;
                const real_type dp2 = math::length_sq(dp);
                const real_type missmatch = square_v0s_[i] - dp2;

                if(abs(missmatch) > tolerance)
                {
                    corrected = true;
                    const auto old_dp = op2 - op1;
                    const real_type dot_old_new_dp = math::dot_product(old_dp, dp);
                    const real_type lambda =
                        0.5 * missmatch * r_reduced_mass_[i] * dot_old_new_dp;
                    const coordinate_type correction_force = lambda * old_dp;
                    const coordinate_type correction_vec1  = correction_force * rm1;
                    const coordinate_type correction_vec2  = correction_force * rm2;
                    p1 -= correction_vec1;
                    p2 += correction_vec2;
                    v1 -= correction_vec1 * r_dt_in_correction_;
                    v2 += correction_vec2 * r_dt_in_correction_;
                    op1 = p1;
                    op2 = p2;
                }
            }

            if(!corrected) break;

            ++rattle_step;
        }

        if(rattle_step >= correction_max_iter_)
        {
            MJOLNIR_LOG_WARN("rattle iteration number exceeds rattle max iteration");
        }

        return;
    }

    void correct_velocity(system_type& sys, const real_type tolerance)
    {
        MJOLNIR_GET_DEFAULT_LOGGER();

        std::size_t rattle_step = 0;
        while(rattle_step<correction_max_iter_)
        {
            bool corrected = false;
            for(std::size_t i=0; i<this->constraints_.size(); ++i)
            {
                const auto& constraint       = constraints_[i];
                const auto& indices          = constraint.first;
                auto& p1  = sys.position(indices[0]);
                auto& p2  = sys.position(indices[1]);
                auto& v1  = sys.velocity(indices[0]);
                auto& v2  = sys.velocity(indices[1]);
                auto& rm1 = sys.rmass(indices[0]);
                auto& rm2 = sys.rmass(indices[1]);

                const auto pos_diff      = p2 - p1;
                const auto vel_diff      = v2 - v1;
                const real_type dot_pdvd = math::dot_product(pos_diff, vel_diff);
                const real_type lambda   = dot_pdvd * r_reduced_mass_[i] * square_v0s_[i];

                if(std::abs(lambda) > tolerance)
                {
                    const auto correction_vec = lambda * pos_diff;
                    v1 += correction_vec * rm1;
                    v2 -= correction_vec * rm2;
                    corrected = true;
                }
            }

            if(!corrected) break;

            ++rattle_step;
        }

        if(rattle_step >= correction_max_iter_)
        {
            MJOLNIR_LOG_WARN("rattle iteration number exceeds rattle max iteration.");
        }
        return;
   }

  private:
    real_type   dt_;
    real_type   halfdt_;
    std::vector<real_type>       gammas_;
    std::vector<real_type>       exp_gamma_dt_;
    std::vector<real_type>       noise_coeff_;
    constraints_type             constraints_; // pair of index pair and v0.
    std::vector<real_type>       square_v0s_;
    std::vector<real_type>       r_reduced_mass_;
    remover_type remover_;

    real_type   temperature_;
    std::size_t correction_iter_num_;
    std::size_t correction_max_iter_;
    real_type   correction_tolerance_dt_;
    real_type   correction_tolerance_dt_itr_;
    real_type   dt_in_correction_;
    real_type   r_dt_in_correction_;

    std::vector<coordinate_type> old_position_;
    std::vector<coordinate_type> old_pos_rattle_;

};

template<typename traitsT>
void GBAOABLangevinIntegrator<traitsT>::initialize(
        system_type& system, forcefield_type& ff, rng_type&)
{
    MJOLNIR_GET_DEFAULT_LOGGER();
    MJOLNIR_LOG_FUNCTION();

    // calculate parameters for each particles
    this->update(system);

    // calculate force
    for(std::size_t i=0; i<system.size(); ++i)
    {
        system.force(i) = math::make_coordinate<coordinate_type>(0, 0, 0);
    }
    ff.calc_force(system);

    // buffering old position
    for(std::size_t i=0; i<system.size(); ++i)
    {
        old_position_[i] = system.position(i);
    }

    for(std::size_t i=0; i<constraints_.size(); ++i)
    {
        // calculate square v0
        square_v0s_[i] = std::pow(constraints_[i].second, 2);

        // calculate inverse reduced mass
        const auto& constraint = constraints_[i];
        std::size_t first_idx  = constraint.first[0];
        std::size_t second_idx = constraint.first[1];
        r_reduced_mass_[i] = system.rmass(first_idx) + system.rmass(second_idx);
    }

    return;
}

template<typename traitsT>
typename GBAOABLangevinIntegrator<traitsT>::real_type
GBAOABLangevinIntegrator<traitsT>::step(
        const real_type time, system_type& sys, forcefield_type& ff, rng_type& rng)
{
    // B step
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        sys.velocity(i)  += this->halfdt_ * sys.rmass(i) * sys.force(i);
    }
    // velocity correction step
    correct_velocity(sys, correction_tolerance_dt_);
    // A step
    for(std::size_t correction_step=0; correction_step<correction_iter_num_; ++correction_step)
    {
        for(std::size_t i=0; i<sys.size(); ++i)
        {
            this->old_pos_rattle_[i] = sys.position(i);
            sys.position(i) += this->dt_in_correction_ * sys.velocity(i);
        }
        // coordinate correction step
        // TODO
        // velocity correction step
        correct_velocity(sys, correction_tolerance_dt_itr_);
    }
    // O step
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        sys.velocity(i) *= this->exp_gamma_dt_[i]; // *= exp(- gamma dt)
        sys.velocity(i) += this->noise_coeff_[i] * this->gen_R(rng);
    }
    // velocity correction step
    // TODO
    // A step
    for(std::size_t correction_step=0; correction_step<correction_iter_num_; ++correction_step)
    {
        for(std::size_t i=0; i<sys.size(); ++i)
        {
            this->old_pos_rattle_[i] = sys.position(i);
            sys.position(i) += this->dt_in_correction_ * sys.velocity(i);
        }
        // coordinate correction step
        // TODO
        // velocity correction step
        correct_velocity(sys, correction_tolerance_dt_itr_);
    }
    // update neighbor list; reduce margin, reconstruct the list if needed;
    real_type largest_disp2(0.0);
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        coordinate_type displacement = sys.position(i) - this->old_position_[i];
        largest_disp2 = std::max(largest_disp2, math::length_sq(displacement));
        sys.position(i) = sys.adjust_position(sys.position(i));

        // reset force
        sys.force(i) = math::make_coordinate<coordinate_type>(0, 0, 0);
    }

    ff.reduce_margin(2 * std::sqrt(largest_disp2), sys);

    // B step
    ff.calc_force(sys);
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        sys.velocity(i) += this->halfdt_ * sys.rmass(i) * sys.force(i);
    }
    // velocity correction step
    correct_velocity(sys, correction_tolerance_dt_);

    remover_.remove(sys);

    for(std::size_t i=0; i<sys.size(); ++i)
    {
        this->old_position_[i] = sys.position(i);
    }

    return time + dt_;
}

} // mjolnir
#endif /* MJOLNIR_CORE_G_BAOAB_LANGEVIN_INTEGRATOR_HPP */
<commit_msg>feat: implement coordinate correction function<commit_after>#ifndef MJOLNIR_CORE_G_BAOAB_LANGEVIN_INTEGRATOR_HPP
#define MJOLNIR_CORE_G_BAOAB_LANGEVIN_INTEGRATOR_HPP
#include <mjolnir/core/SimulatorTraits.hpp>
#include <mjolnir/core/RandomNumberGenerator.hpp>
#include <mjolnir/core/System.hpp>
#include <mjolnir/core/ForceField.hpp>
#include <mjolnir/core/SystemMotionRemover.hpp>
#include <mjolnir/core/Unit.hpp>
#include <mjolnir/util/logger.hpp>

namespace mjolnir
{

// g-BAOAB Langevin integrator developed by the following papers
// Leimkuhler B., Matthews C., Proc. R. Soc. A Math. Phys. Eng. Sci. (2016)
template<typename traitsT>
class GBAOABLangevinIntegrator
{
  public:
    using traits_type      = traitsT;
    using real_type        = typename traits_type::real_type;
    using coordinate_type  = typename traits_type::coordinate_type;
    using indices_type     = std::array<std::size_t, 2>;
    using constraints_type = std::vector<std::pair<indices_type, real_type>>;
    using system_type      = System<traitsT>;
    using forcefield_type  = ForceField<traitsT>;
    using rng_type         = RandomNumberGenerator<traits_type>;
    using remover_type     = SystemMotionRemover<traits_type>;

  public:

    GBAOABLangevinIntegrator(const real_type dt, std::vector<real_type>&& gamma,
                             constraints_type&& constraint, remover_type&& remover,
                             std::size_t correction_iter_num, std::size_t max_iter_correction,
                             real_type correction_tolerance)
        : dt_(dt), halfdt_(dt / 2), gammas_(std::move(gamma)),
          exp_gamma_dt_(gammas_.size()), noise_coeff_ (gammas_.size()),
          constraints_(constraint),
          square_v0s_(constraint.size()),
          r_reduced_mass_(constraint.size()),
          remover_(std::move(remover)),
          correction_iter_num_(correction_iter_num),
          correction_max_iter_(max_iter_correction),
          correction_tolerance_dt_(correction_tolerance/ dt),
          correction_tolerance_dt_itr_(correction_tolerance/ dt * correction_iter_num),
          dt_in_correction_(dt * 0.5 / correction_iter_num_),
          r_dt_in_correction_(1 / dt_in_correction_),
          old_position_(gammas_.size()),
          old_pos_rattle_(gammas_.size())
    {}
    ~GBAOABLangevinIntegrator() = default;

    void initialize(system_type& sys, forcefield_type& ff, rng_type& rng);

    real_type step(const real_type time, system_type& sys, forcefield_type& ff,
                   rng_type& rng);

    void update(const system_type& sys)
    {
        if(!sys.has_attribute("temperature"))
        {
            throw std::out_of_range("mjolnir::g-BAOABLangevinIntegrator: "
                "Langevin Integrator requires reference temperature, but "
                "`temperature` is not found in `system.attribute`.");
        }
        this->temperature_ = sys.attribute("temperature");
        this->reset_parameter(sys);
        return;
    }

    real_type delta_t() const noexcept {return dt_;}
    std::vector<real_type> const& parameters() const noexcept {return gammas_;}

  private:

    void reset_parameter(const system_type& sys) noexcept
    {
        const auto kBT = physics::constants<real_type>::kB() * this->temperature_;
        for(std::size_t i=0; i<sys.size(); ++i)
        {
            const auto gamma     = this->gammas_.at(i);
            const auto gamma_dt  = -1 * gamma * this->dt_;
            this->exp_gamma_dt_.at(i) = std::exp(gamma_dt);
            this->noise_coeff_ .at(i) = std::sqrt(
                    kBT * (1 - std::exp(2 * gamma_dt)) * sys.rmass(i));
        }
        return;
    };

    coordinate_type gen_R(rng_type& rng) noexcept
    {
        const auto x = rng.gaussian();
        const auto y = rng.gaussian();
        const auto z = rng.gaussian();
        return math::make_coordinate<coordinate_type>(x, y, z);
    }

    void correct_coordinate(system_type& sys, const real_type tolerance)
    {
        MJOLNIR_GET_DEFAULT_LOGGER();

        std::size_t rattle_step = 0;
        while(rattle_step<correction_max_iter_)
        {
            bool corrected = false;
            for(std::size_t i=0; i<this->constraints_.size(); ++i)
            {
                const auto& constraint = constraints_[i];
                const auto& indices    = constraint.first;
                auto& p1  = sys.position(indices[0]);
                auto& p2  = sys.position(indices[1]);
                auto& v1  = sys.velocity(indices[0]);
                auto& v2  = sys.velocity(indices[1]);
                auto& op1 = this->old_pos_rattle_[indices[0]];
                auto& op2 = this->old_pos_rattle_[indices[1]];
                auto& rm1 = sys.rmass(indices[0]);
                auto& rm2 = sys.rmass(indices[1]);

                const auto      dp = p2 - p1;
                const real_type dp2 = math::length_sq(dp);
                const real_type missmatch = square_v0s_[i] - dp2;

                if(abs(missmatch) > tolerance)
                {
                    corrected = true;
                    const auto old_dp = op2 - op1;
                    const real_type dot_old_new_dp = math::dot_product(old_dp, dp);
                    const real_type lambda =
                        0.5 * missmatch * r_reduced_mass_[i] * dot_old_new_dp;
                    const coordinate_type correction_force = lambda * old_dp;
                    const coordinate_type correction_vec1  = correction_force * rm1;
                    const coordinate_type correction_vec2  = correction_force * rm2;
                    p1 -= correction_vec1;
                    p2 += correction_vec2;
                    v1 -= correction_vec1 * r_dt_in_correction_;
                    v2 += correction_vec2 * r_dt_in_correction_;
                    op1 = p1;
                    op2 = p2;
                }
            }

            if(!corrected) break;

            ++rattle_step;
        }

        if(rattle_step >= correction_max_iter_)
        {
            MJOLNIR_LOG_WARN("rattle iteration number exceeds rattle max iteration");
        }

        return;
    }

    void correct_velocity(system_type& sys, const real_type tolerance)
    {
        MJOLNIR_GET_DEFAULT_LOGGER();

        std::size_t rattle_step = 0;
        while(rattle_step<correction_max_iter_)
        {
            bool corrected = false;
            for(std::size_t i=0; i<this->constraints_.size(); ++i)
            {
                const auto& constraint       = constraints_[i];
                const auto& indices          = constraint.first;
                auto& p1  = sys.position(indices[0]);
                auto& p2  = sys.position(indices[1]);
                auto& v1  = sys.velocity(indices[0]);
                auto& v2  = sys.velocity(indices[1]);
                auto& rm1 = sys.rmass(indices[0]);
                auto& rm2 = sys.rmass(indices[1]);

                const auto pos_diff      = p2 - p1;
                const auto vel_diff      = v2 - v1;
                const real_type dot_pdvd = math::dot_product(pos_diff, vel_diff);
                const real_type lambda   = dot_pdvd * r_reduced_mass_[i] * square_v0s_[i];

                if(std::abs(lambda) > tolerance)
                {
                    const auto correction_vec = lambda * pos_diff;
                    v1 += correction_vec * rm1;
                    v2 -= correction_vec * rm2;
                    corrected = true;
                }
            }

            if(!corrected) break;

            ++rattle_step;
        }

        if(rattle_step >= correction_max_iter_)
        {
            MJOLNIR_LOG_WARN("rattle iteration number exceeds rattle max iteration.");
        }
        return;
   }

  private:
    real_type   dt_;
    real_type   halfdt_;
    std::vector<real_type>       gammas_;
    std::vector<real_type>       exp_gamma_dt_;
    std::vector<real_type>       noise_coeff_;
    constraints_type             constraints_; // pair of index pair and v0.
    std::vector<real_type>       square_v0s_;
    std::vector<real_type>       r_reduced_mass_;
    remover_type remover_;

    real_type   temperature_;
    std::size_t correction_iter_num_;
    std::size_t correction_max_iter_;
    real_type   correction_tolerance_dt_;
    real_type   correction_tolerance_dt_itr_;
    real_type   dt_in_correction_;
    real_type   r_dt_in_correction_;

    std::vector<coordinate_type> old_position_;
    std::vector<coordinate_type> old_pos_rattle_;

};

template<typename traitsT>
void GBAOABLangevinIntegrator<traitsT>::initialize(
        system_type& system, forcefield_type& ff, rng_type&)
{
    MJOLNIR_GET_DEFAULT_LOGGER();
    MJOLNIR_LOG_FUNCTION();

    // calculate parameters for each particles
    this->update(system);

    // calculate force
    for(std::size_t i=0; i<system.size(); ++i)
    {
        system.force(i) = math::make_coordinate<coordinate_type>(0, 0, 0);
    }
    ff.calc_force(system);

    // buffering old position
    for(std::size_t i=0; i<system.size(); ++i)
    {
        old_position_[i] = system.position(i);
    }

    for(std::size_t i=0; i<constraints_.size(); ++i)
    {
        // calculate square v0
        square_v0s_[i] = std::pow(constraints_[i].second, 2);

        // calculate inverse reduced mass
        const auto& constraint = constraints_[i];
        std::size_t first_idx  = constraint.first[0];
        std::size_t second_idx = constraint.first[1];
        r_reduced_mass_[i] = system.rmass(first_idx) + system.rmass(second_idx);
    }

    return;
}

template<typename traitsT>
typename GBAOABLangevinIntegrator<traitsT>::real_type
GBAOABLangevinIntegrator<traitsT>::step(
        const real_type time, system_type& sys, forcefield_type& ff, rng_type& rng)
{
    // B step
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        sys.velocity(i)  += this->halfdt_ * sys.rmass(i) * sys.force(i);
    }
    correct_velocity(sys, correction_tolerance_dt_);

    // A step
    for(std::size_t correction_step=0; correction_step<correction_iter_num_; ++correction_step)
    {
        for(std::size_t i=0; i<sys.size(); ++i)
        {
            this->old_pos_rattle_[i] = sys.position(i);
            sys.position(i) += this->dt_in_correction_ * sys.velocity(i);
        }
        correct_coordinate(sys, correction_tolerance_dt_itr_);
        correct_velocity(sys, correction_tolerance_dt_itr_);
    }

    // O step
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        sys.velocity(i) *= this->exp_gamma_dt_[i]; // *= exp(- gamma dt)
        sys.velocity(i) += this->noise_coeff_[i] * this->gen_R(rng);
    }
    correct_velocity(sys, correction_tolerance_dt_);

    // A step
    for(std::size_t correction_step=0; correction_step<correction_iter_num_; ++correction_step)
    {
        for(std::size_t i=0; i<sys.size(); ++i)
        {
            this->old_pos_rattle_[i] = sys.position(i);
            sys.position(i) += this->dt_in_correction_ * sys.velocity(i);
        }
        correct_coordinate(sys, correction_tolerance_dt_itr_);
        correct_velocity(sys, correction_tolerance_dt_itr_);
    }
    // update neighbor list; reduce margin, reconstruct the list if needed;
    real_type largest_disp2(0.0);
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        coordinate_type displacement = sys.position(i) - this->old_position_[i];
        largest_disp2 = std::max(largest_disp2, math::length_sq(displacement));
        sys.position(i) = sys.adjust_position(sys.position(i));

        // reset force
        sys.force(i) = math::make_coordinate<coordinate_type>(0, 0, 0);
    }

    ff.reduce_margin(2 * std::sqrt(largest_disp2), sys);

    // B step
    ff.calc_force(sys);
    for(std::size_t i=0; i<sys.size(); ++i)
    {
        sys.velocity(i) += this->halfdt_ * sys.rmass(i) * sys.force(i);
    }
    correct_velocity(sys, correction_tolerance_dt_);

    remover_.remove(sys);

    for(std::size_t i=0; i<sys.size(); ++i)
    {
        this->old_position_[i] = sys.position(i);
    }

    return time + dt_;
}

} // mjolnir
#endif /* MJOLNIR_CORE_G_BAOAB_LANGEVIN_INTEGRATOR_HPP */
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015 - The CM Authors <legal@clickmatcher.com>
 *   All Rights Reserved.
 *
 * This file is CONFIDENTIAL -- Distribution or duplication of this material or
 * the information contained herein is strictly forbidden unless prior written
 * permission is obtained.
 */
#define BOOST_NO_CXX11_NUMERIC_LIMITS 1
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include "fnord-base/io/filerepository.h"
#include "fnord-base/io/fileutil.h"
#include "fnord-base/application.h"
#include "fnord-base/logging.h"
#include "fnord-base/random.h"
#include "fnord-base/thread/eventloop.h"
#include "fnord-base/thread/threadpool.h"
#include "fnord-base/wallclock.h"
#include "fnord-rpc/ServerGroup.h"
#include "fnord-rpc/RPC.h"
#include "fnord-rpc/RPCClient.h"
#include "fnord-base/cli/flagparser.h"
#include "fnord-json/json.h"
#include "fnord-json/jsonrpc.h"
#include "fnord-http/httprouter.h"
#include "fnord-http/httpserver.h"
#include "fnord-feeds/FeedService.h"
#include "fnord-feeds/RemoteFeedFactory.h"
#include "fnord-feeds/RemoteFeedReader.h"
#include "fnord-base/stats/statsdagent.h"
#include "fnord-fts/fts.h"
#include "fnord-fts/fts_common.h"
#include "fnord-mdb/MDB.h"
#include "CustomerNamespace.h"
#include "FeatureSchema.h"
#include "FeatureIndexWriter.h"
#include "IndexRequest.h"
#include "IndexBuild.h"

using namespace cm;
using namespace fnord;

std::atomic<bool> cm_indexbuild_shutdown;

void quit(int n) {
  cm_indexbuild_shutdown = true;
}

int main(int argc, const char** argv) {
  Application::init();
  Application::logToStderr();

  /* shutdown hook */
  cm_indexbuild_shutdown = false;
  struct sigaction sa;
  memset(&sa, 0, sizeof(struct sigaction));
  sa.sa_handler = quit;
  sigaction(SIGTERM, &sa, NULL);
  sigaction(SIGQUIT, &sa, NULL);
  sigaction(SIGINT, &sa, NULL);

  cli::FlagParser flags;

  flags.defineFlag(
      "index",
      cli::FlagParser::T_STRING,
      true,
      NULL,
      NULL,
      "index dir",
      "<path>");

  flags.defineFlag(
      "statsd_addr",
      fnord::cli::FlagParser::T_STRING,
      false,
      NULL,
      "127.0.0.1:8192",
      "Statsd addr",
      "<addr>");

  flags.defineFlag(
      "batch_size",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "2048",
      "batch_size",
      "<num>");

  flags.defineFlag(
      "buffer_size",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "8192",
      "buffer_size",
      "<num>");

  flags.defineFlag(
      "db_commit_size",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "1024",
      "db_commit_size",
      "<num>");

  flags.defineFlag(
      "db_commit_interval",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "5",
      "db_commit_interval",
      "<num>");

  flags.defineFlag(
      "dbsize",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "128",
      "max sessiondb size",
      "<MB>");

  flags.defineFlag(
      "loglevel",
      fnord::cli::FlagParser::T_STRING,
      false,
      NULL,
      "INFO",
      "loglevel",
      "<level>");

  flags.parseArgv(argc, argv);

  Logger::get()->setMinimumLogLevel(
      strToLogLevel(flags.getString("loglevel")));

  /* set up feature schema */
  FeatureSchema feature_schema;
  feature_schema.registerFeature("shop_id", 1, 1);
  feature_schema.registerFeature("category1", 2, 1);
  feature_schema.registerFeature("category2", 3, 1);
  feature_schema.registerFeature("category3", 4, 1);
  feature_schema.registerFeature("title~de", 5, 2);

  /* start event loop */
  fnord::thread::EventLoop ev;

  auto evloop_thread = std::thread([&ev] {
    ev.run();
  });

  /* set up rpc client */
  HTTPRPCClient rpc_client(&ev);

  /* start stats reporting */
  fnord::stats::StatsdAgent statsd_agent(
      fnord::net::InetAddr::resolve(flags.getString("statsd_addr")),
      10 * fnord::kMicrosPerSecond);

  statsd_agent.start();

  /* set up indexbuild */
  size_t batch_size = flags.getInt("batch_size");
  size_t buffer_size = flags.getInt("buffer_size");
  size_t db_commit_size = flags.getInt("db_commit_size");
  size_t db_commit_interval = flags.getInt("db_commit_interval");

  fnord::logInfo(
      "cm.indexbuild",
      "Starting cm-indexbuild with:\n    index=$0\n    batch_size=$1\n" \
      "    buffer_size=$2\n    db_commit_size=$3\n    db_commit_interval=$4\n"
      "    max_dbsize=$5MB",
      flags.getString("index"),
      batch_size,
      buffer_size,
      db_commit_size,
      db_commit_interval,
      flags.getInt("dbsize"));

  /* stats */
  fnord::stats::Counter<uint64_t> stat_documents_indexed_total_;
  fnord::stats::Counter<uint64_t> stat_documents_indexed_success_;
  fnord::stats::Counter<uint64_t> stat_documents_indexed_error_;

  exportStat(
      "/cm-indexbuild/global/documents_indexed_total",
      &stat_documents_indexed_total_,
      fnord::stats::ExportMode::EXPORT_DELTA);

  exportStat(
      "/cm-indexbuild/global/documents_indexed_success",
      &stat_documents_indexed_success_,
      fnord::stats::ExportMode::EXPORT_DELTA);

  exportStat(
      "/cm-indexbuild/global/documents_indexed_error",
      &stat_documents_indexed_error_,
      fnord::stats::ExportMode::EXPORT_DELTA);

  /* open featuredb db */
  auto featuredb_path = FileUtil::joinPaths(flags.getString("index"), "db");
  FileUtil::mkdir_p(featuredb_path);
  auto featuredb = mdb::MDB::open(featuredb_path);
  featuredb->setMaxSize(1000000 * flags.getInt("dbsize"));
  cm::FeatureIndexWriter feature_index_writer(&feature_schema);

  /* open full index */
  auto fullindex_path = FileUtil::joinPaths(flags.getString("index"), "docs");
  FileUtil::mkdir_p(fullindex_path);
  cm::FullIndex full_index(fullindex_path);

  /* open lucene index */
  /*
  auto index_path = FileUtil::joinPaths(
      cmdata_path,
      StringUtil::format("index/$0/fts", cmcustomer));
  FileUtil::mkdir_p(index_path);

  auto index_writer =
      fts::newLucene<fts::IndexWriter>(
          fts::FSDirectory::open(StringUtil::convertUTF8To16(index_path)),
          fts::newLucene<fts::StandardAnalyzer>(
              fts::LuceneVersion::LUCENE_CURRENT),
          true,
          fts::IndexWriter::MaxFieldLengthLIMITED);
  */

  cm::IndexBuild index_build(&feature_index_writer, &full_index);

  /* set up input feed reader */
  feeds::RemoteFeedReader feed_reader(&rpc_client);
  feed_reader.setMaxSpread(3600 * 24 * 30 * kMicrosPerSecond);

  HashMap<String, URI> input_feeds;
  auto cmcustomer = "dawanda";
  input_feeds.emplace(
      StringUtil::format(
          "$0.index_requests.feedserver01.nue01.production.fnrd.net",
          cmcustomer),
      URI("http://s01.nue01.production.fnrd.net:7001/rpc"));
  input_feeds.emplace(
      StringUtil::format(
          "$0.index_requests.feedserver02.nue01.production.fnrd.net",
          cmcustomer),
      URI("http://s02.nue01.production.fnrd.net:7001/rpc"));

  /* resume from last offset */
  auto txn = featuredb->startTransaction(true);
  try {
    for (const auto& input_feed : input_feeds) {
      uint64_t offset = 0;

      auto last_offset = txn->get(
          StringUtil::format("__indexfeed_offset~$0", input_feed.first));

      if (!last_offset.isEmpty()) {
        offset = std::stoul(last_offset.get().toString());
      }

      fnord::logInfo(
          "cm.indexbuild",
          "Adding source feed:\n    feed=$0\n    url=$1\n    offset: $2",
          input_feed.first,
          input_feed.second.toString(),
          offset);

      feed_reader.addSourceFeed(
          input_feed.second,
          input_feed.first,
          offset,
          batch_size,
          buffer_size);
    }

    txn->abort();
  } catch (...) {
    txn->abort();
    throw;
  }

  fnord::logInfo("cm.indexbuild", "Resuming IndexBuild...");
  // index document
/*
  auto doc = fts::newLucene<fts::Document>();
  doc->add(
      fts::newLucene<fts::Field>(
          L"keywords",
          L"my fnordy document",
          fts::Field::STORE_NO,
          fts::Field::INDEX_ANALYZED));

  index_writer->addDocument(doc);
*/

  //index_writer->commit();
  //index_writer->close();

  // simple search
  /*
  auto index_reader = fts::IndexReader::open(
      fts::FSDirectory::open(StringUtil::convertUTF8To16(index_path)),
      true);

  auto searcher = fts::newLucene<fts::IndexSearcher>(index_reader);

  auto analyzer = fts::newLucene<fts::StandardAnalyzer>(
      fts::LuceneVersion::LUCENE_CURRENT);

  auto query_parser = fts::newLucene<fts::QueryParser>(
      fts::LuceneVersion::LUCENE_CURRENT,
      L"keywords",
      analyzer);

  auto collector = fts::TopScoreDocCollector::create(
      500,
      false);

  auto query = query_parser->parse(L"fnordy");

  searcher->search(query, collector);
  fnord::iputs("found $0 documents", collector->getTotalHits());
  */

  DateTime last_iter;
  uint64_t rate_limit_micros = db_commit_interval * kMicrosPerSecond;

  for (;;) {
    last_iter = WallClock::now();
    feed_reader.fillBuffers();
    int i = 0;
    for (; i < db_commit_size; ++i) {
      auto entry = feed_reader.fetchNextEntry();

      if (entry.isEmpty()) {
        break;
      }

      try {
        stat_documents_indexed_total_.incr(1);
        fnord::logTrace("cm.indexbuild", "Indexing: $0", entry.get().data);
        auto index_req = json::fromJSON<cm::IndexRequest>(entry.get().data);
        index_build.updateDocument(index_req);
        stat_documents_indexed_success_.incr(1);
      } catch (const std::exception& e) {
        stat_documents_indexed_error_.incr(1);
        fnord::logError(
            "cm.indexbuild",
            e,
            "error while indexing document: $0",
            entry.get().data);
      }
    }

    auto stream_offsets = feed_reader.streamOffsets();
    String stream_offsets_str;

    index_build.commit();

    auto txn = featuredb->startTransaction();

    for (const auto& soff : stream_offsets) {
      txn->update(
          StringUtil::format("__indexfeed_offset~$0", soff.first),
          StringUtil::toString(soff.second));

      stream_offsets_str +=
          StringUtil::format("\n    offset[$0]=$1", soff.first, soff.second);
    }

    fnord::logInfo(
        "cm.indexbuild",
        "IndexBuild comitting...$0",
        stream_offsets_str);

    txn->commit();

    if (cm_indexbuild_shutdown.load() == true) {
      break;
    }

    auto etime = WallClock::now().unixMicros() - last_iter.unixMicros();
    if (i < 1 && etime < rate_limit_micros) {
      usleep(rate_limit_micros - etime);
    }
  }

  ev.shutdown();
  //evloop_thread.join();

  fnord::logInfo("cm.indexbuild", "IndexBuild exiting...");
  exit(0); // FIXPAUL

  return 0;
}

<commit_msg>move all the feed subscription code into a separate method in cm-indexbuild<commit_after>/**
 * Copyright (c) 2015 - The CM Authors <legal@clickmatcher.com>
 *   All Rights Reserved.
 *
 * This file is CONFIDENTIAL -- Distribution or duplication of this material or
 * the information contained herein is strictly forbidden unless prior written
 * permission is obtained.
 */
#define BOOST_NO_CXX11_NUMERIC_LIMITS 1
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include "fnord-base/io/filerepository.h"
#include "fnord-base/io/fileutil.h"
#include "fnord-base/application.h"
#include "fnord-base/logging.h"
#include "fnord-base/random.h"
#include "fnord-base/thread/eventloop.h"
#include "fnord-base/thread/threadpool.h"
#include "fnord-base/wallclock.h"
#include "fnord-rpc/ServerGroup.h"
#include "fnord-rpc/RPC.h"
#include "fnord-rpc/RPCClient.h"
#include "fnord-base/cli/flagparser.h"
#include "fnord-json/json.h"
#include "fnord-json/jsonrpc.h"
#include "fnord-http/httprouter.h"
#include "fnord-http/httpserver.h"
#include "fnord-feeds/FeedService.h"
#include "fnord-feeds/RemoteFeedFactory.h"
#include "fnord-feeds/RemoteFeedReader.h"
#include "fnord-base/stats/statsdagent.h"
#include "fnord-fts/fts.h"
#include "fnord-fts/fts_common.h"
#include "fnord-mdb/MDB.h"
#include "CustomerNamespace.h"
#include "FeatureSchema.h"
#include "FeatureIndexWriter.h"
#include "IndexRequest.h"
#include "IndexBuild.h"

using namespace cm;
using namespace fnord;

std::atomic<bool> cm_indexbuild_shutdown;

/* stats */
fnord::stats::Counter<uint64_t> stat_documents_indexed_total_;
fnord::stats::Counter<uint64_t> stat_documents_indexed_success_;
fnord::stats::Counter<uint64_t> stat_documents_indexed_error_;

void quit(int n) {
  cm_indexbuild_shutdown = true;
}

void buildIndexFromFeed(
    IndexBuild* index_build,
    RefPtr<mdb::MDB> featuredb,
    const cli::FlagParser& flags) {
  size_t batch_size = flags.getInt("batch_size");
  size_t buffer_size = flags.getInt("buffer_size");
  size_t db_commit_size = flags.getInt("db_commit_size");
  size_t db_commit_interval = flags.getInt("db_commit_interval");

  /* start event loop */
  fnord::thread::EventLoop ev;

  auto evloop_thread = std::thread([&ev] {
    ev.run();
  });

  /* set up rpc client */
  HTTPRPCClient rpc_client(&ev);

  feeds::RemoteFeedReader feed_reader(&rpc_client);
  feed_reader.setMaxSpread(3600 * 24 * 30 * kMicrosPerSecond);

  HashMap<String, URI> input_feeds;
  auto cmcustomer = "dawanda";
  input_feeds.emplace(
      StringUtil::format(
          "$0.index_requests.feedserver01.nue01.production.fnrd.net",
          cmcustomer),
      URI("http://s01.nue01.production.fnrd.net:7001/rpc"));
  input_feeds.emplace(
      StringUtil::format(
          "$0.index_requests.feedserver02.nue01.production.fnrd.net",
          cmcustomer),
      URI("http://s02.nue01.production.fnrd.net:7001/rpc"));

  /* resume from last offset */
  auto txn = featuredb->startTransaction(true);
  try {
    for (const auto& input_feed : input_feeds) {
      uint64_t offset = 0;

      auto last_offset = txn->get(
          StringUtil::format("__indexfeed_offset~$0", input_feed.first));

      if (!last_offset.isEmpty()) {
        offset = std::stoul(last_offset.get().toString());
      }

      fnord::logInfo(
          "cm.indexbuild",
          "Adding source feed:\n    feed=$0\n    url=$1\n    offset: $2",
          input_feed.first,
          input_feed.second.toString(),
          offset);

      feed_reader.addSourceFeed(
          input_feed.second,
          input_feed.first,
          offset,
          batch_size,
          buffer_size);
    }

    txn->abort();
  } catch (...) {
    txn->abort();
    throw;
  }

  fnord::logInfo("cm.indexbuild", "Resuming IndexBuild...");

  DateTime last_iter;
  uint64_t rate_limit_micros = db_commit_interval * kMicrosPerSecond;

  for (;;) {
    last_iter = WallClock::now();
    feed_reader.fillBuffers();
    int i = 0;
    for (; i < db_commit_size; ++i) {
      auto entry = feed_reader.fetchNextEntry();

      if (entry.isEmpty()) {
        break;
      }

      try {
        stat_documents_indexed_total_.incr(1);
        fnord::logTrace("cm.indexbuild", "Indexing: $0", entry.get().data);
        auto index_req = json::fromJSON<cm::IndexRequest>(entry.get().data);
        index_build->updateDocument(index_req);
        stat_documents_indexed_success_.incr(1);
      } catch (const std::exception& e) {
        stat_documents_indexed_error_.incr(1);
        fnord::logError(
            "cm.indexbuild",
            e,
            "error while indexing document: $0",
            entry.get().data);
      }
    }

    auto stream_offsets = feed_reader.streamOffsets();
    String stream_offsets_str;

    index_build->commit();

    auto txn = featuredb->startTransaction();

    for (const auto& soff : stream_offsets) {
      txn->update(
          StringUtil::format("__indexfeed_offset~$0", soff.first),
          StringUtil::toString(soff.second));

      stream_offsets_str +=
          StringUtil::format("\n    offset[$0]=$1", soff.first, soff.second);
    }

    fnord::logInfo(
        "cm.indexbuild",
        "IndexBuild comitting...$0",
        stream_offsets_str);

    txn->commit();

    if (cm_indexbuild_shutdown.load() == true) {
      break;
    }

    auto etime = WallClock::now().unixMicros() - last_iter.unixMicros();
    if (i < 1 && etime < rate_limit_micros) {
      usleep(rate_limit_micros - etime);
    }
  }

  ev.shutdown();
  evloop_thread.join();
}

int main(int argc, const char** argv) {
  Application::init();
  Application::logToStderr();

  /* shutdown hook */
  cm_indexbuild_shutdown = false;
  struct sigaction sa;
  memset(&sa, 0, sizeof(struct sigaction));
  sa.sa_handler = quit;
  sigaction(SIGTERM, &sa, NULL);
  sigaction(SIGQUIT, &sa, NULL);
  sigaction(SIGINT, &sa, NULL);

  cli::FlagParser flags;

  flags.defineFlag(
      "index",
      cli::FlagParser::T_STRING,
      true,
      NULL,
      NULL,
      "index dir",
      "<path>");

  flags.defineFlag(
      "statsd_addr",
      fnord::cli::FlagParser::T_STRING,
      false,
      NULL,
      "127.0.0.1:8192",
      "Statsd addr",
      "<addr>");

  flags.defineFlag(
      "batch_size",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "2048",
      "batch_size",
      "<num>");

  flags.defineFlag(
      "buffer_size",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "8192",
      "buffer_size",
      "<num>");

  flags.defineFlag(
      "db_commit_size",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "1024",
      "db_commit_size",
      "<num>");

  flags.defineFlag(
      "db_commit_interval",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "5",
      "db_commit_interval",
      "<num>");

  flags.defineFlag(
      "dbsize",
      fnord::cli::FlagParser::T_INTEGER,
      false,
      NULL,
      "128",
      "max sessiondb size",
      "<MB>");

  flags.defineFlag(
      "loglevel",
      fnord::cli::FlagParser::T_STRING,
      false,
      NULL,
      "INFO",
      "loglevel",
      "<level>");

  flags.parseArgv(argc, argv);

  Logger::get()->setMinimumLogLevel(
      strToLogLevel(flags.getString("loglevel")));

  /* set up feature schema */
  FeatureSchema feature_schema;
  feature_schema.registerFeature("shop_id", 1, 1);
  feature_schema.registerFeature("category1", 2, 1);
  feature_schema.registerFeature("category2", 3, 1);
  feature_schema.registerFeature("category3", 4, 1);
  feature_schema.registerFeature("title~de", 5, 2);

  /* start stats reporting */
  fnord::stats::StatsdAgent statsd_agent(
      fnord::net::InetAddr::resolve(flags.getString("statsd_addr")),
      10 * fnord::kMicrosPerSecond);

  statsd_agent.start();

  /* set up indexbuild */
  size_t batch_size = flags.getInt("batch_size");
  size_t buffer_size = flags.getInt("buffer_size");
  size_t db_commit_size = flags.getInt("db_commit_size");
  size_t db_commit_interval = flags.getInt("db_commit_interval");

  fnord::logInfo(
      "cm.indexbuild",
      "Starting cm-indexbuild with:\n    index=$0\n    batch_size=$1\n" \
      "    buffer_size=$2\n    db_commit_size=$3\n    db_commit_interval=$4\n"
      "    max_dbsize=$5MB",
      flags.getString("index"),
      batch_size,
      buffer_size,
      db_commit_size,
      db_commit_interval,
      flags.getInt("dbsize"));

  exportStat(
      "/cm-indexbuild/global/documents_indexed_total",
      &stat_documents_indexed_total_,
      fnord::stats::ExportMode::EXPORT_DELTA);

  exportStat(
      "/cm-indexbuild/global/documents_indexed_success",
      &stat_documents_indexed_success_,
      fnord::stats::ExportMode::EXPORT_DELTA);

  exportStat(
      "/cm-indexbuild/global/documents_indexed_error",
      &stat_documents_indexed_error_,
      fnord::stats::ExportMode::EXPORT_DELTA);

  /* open featuredb db */
  auto featuredb_path = FileUtil::joinPaths(flags.getString("index"), "db");
  FileUtil::mkdir_p(featuredb_path);
  auto featuredb = mdb::MDB::open(featuredb_path);
  featuredb->setMaxSize(1000000 * flags.getInt("dbsize"));
  cm::FeatureIndexWriter feature_index_writer(&feature_schema);

  /* open full index */
  auto fullindex_path = FileUtil::joinPaths(flags.getString("index"), "docs");
  FileUtil::mkdir_p(fullindex_path);
  cm::FullIndex full_index(fullindex_path);

  cm::IndexBuild index_build(&feature_index_writer, &full_index);

  buildIndexFromFeed(&index_build, featuredb, flags);

  fnord::logInfo("cm.indexbuild", "IndexBuild exiting...");
  return 0;
}

/* open lucene index */
  /*
  auto index_path = FileUtil::joinPaths(
      cmdata_path,
      StringUtil::format("index/$0/fts", cmcustomer));
  FileUtil::mkdir_p(index_path);

  auto index_writer =
      fts::newLucene<fts::IndexWriter>(
          fts::FSDirectory::open(StringUtil::convertUTF8To16(index_path)),
          fts::newLucene<fts::StandardAnalyzer>(
              fts::LuceneVersion::LUCENE_CURRENT),
          true,
          fts::IndexWriter::MaxFieldLengthLIMITED);
  */
  // index document
/*
  auto doc = fts::newLucene<fts::Document>();
  doc->add(
      fts::newLucene<fts::Field>(
          L"keywords",
          L"my fnordy document",
          fts::Field::STORE_NO,
          fts::Field::INDEX_ANALYZED));

  index_writer->addDocument(doc);
*/

  //index_writer->commit();
  //index_writer->close();

  // simple search
  /*
  auto index_reader = fts::IndexReader::open(
      fts::FSDirectory::open(StringUtil::convertUTF8To16(index_path)),
      true);

  auto searcher = fts::newLucene<fts::IndexSearcher>(index_reader);

  auto analyzer = fts::newLucene<fts::StandardAnalyzer>(
      fts::LuceneVersion::LUCENE_CURRENT);

  auto query_parser = fts::newLucene<fts::QueryParser>(
      fts::LuceneVersion::LUCENE_CURRENT,
      L"keywords",
      analyzer);

  auto collector = fts::TopScoreDocCollector::create(
      500,
      false);

  auto query = query_parser->parse(L"fnordy");

  searcher->search(query, collector);
  fnord::iputs("found $0 documents", collector->getTotalHits());
  */

<|endoftext|>


<commit_before>#include "xchainer/array_repr.h"

#include <gtest/gtest.h>

#include <algorithm>
#include <cmath>
#include <limits>
#include <vector>

#include "xchainer/device.h"

namespace xchainer {
namespace {

template <typename T>
void CheckArrayReprWithCurrentDevice(const std::vector<T>& data_vec, Shape shape, const std::string& expected,
                                     std::vector<GraphId> graph_ids) {
    // Copy to a contiguous memory block because std::vector<bool> is not packed as a sequence of bool's.
    std::shared_ptr<T> data_ptr = std::make_unique<T[]>(data_vec.size());
    std::copy(data_vec.begin(), data_vec.end(), data_ptr.get());
    Array array = Array::FromBuffer(shape, TypeToDtype<T>, static_cast<std::shared_ptr<void>>(data_ptr));
    std::for_each(graph_ids.begin(), graph_ids.end(), [&array](const GraphId& graph_id) { array.RequireGrad(graph_id); });

    // std::string version
    EXPECT_EQ(ArrayRepr(array), expected);

    // std::ostream version
    std::ostringstream os;
    os << array;
    EXPECT_EQ(os.str(), expected);
}

template <typename T>
void CheckArrayRepr(const std::vector<T>& data_vec, Shape shape, const std::string& expected,
                    const std::vector<GraphId>& graph_ids = std::vector<GraphId>()) {
    {
        DeviceScope ctx{"cpu"};
        CheckArrayReprWithCurrentDevice(data_vec, shape, expected, graph_ids);
    }

    {
#ifdef XCHAINER_ENABLE_CUDA
        DeviceScope ctx{"cuda"};
        CheckArrayReprWithCurrentDevice(data_vec, shape, expected, graph_ids);
#endif  // XCHAINER_ENABLE_CUDA
    }
}

TEST(ArrayReprTest, ArrayRepr) {
    // bool
    CheckArrayRepr<bool>({false}, Shape({1}), "array([False], dtype=bool)");
    CheckArrayRepr<bool>({true}, Shape({1}), "array([ True], dtype=bool)");
    CheckArrayRepr<bool>({false, true, true, true, false, true}, Shape({2, 3}),
                         "array([[False,  True,  True],\n"
                         "       [ True, False,  True]], dtype=bool)");
    CheckArrayRepr<bool>({true}, Shape({1, 1, 1, 1}), "array([[[[ True]]]], dtype=bool)");

    // int8
    CheckArrayRepr<int8_t>({0}, Shape({1}), "array([0], dtype=int8)");
    CheckArrayRepr<int8_t>({-2}, Shape({1}), "array([-2], dtype=int8)");
    CheckArrayRepr<int8_t>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                           "array([[0, 1, 2],\n"
                           "       [3, 4, 5]], dtype=int8)");
    CheckArrayRepr<int8_t>({0, 1, 2, -3, 4, 5}, Shape({2, 3}),
                           "array([[ 0,  1,  2],\n"
                           "       [-3,  4,  5]], dtype=int8)");
    CheckArrayRepr<int8_t>({3}, Shape({1, 1, 1, 1}), "array([[[[3]]]], dtype=int8)");

    // int16
    CheckArrayRepr<int16_t>({0}, Shape({1}), "array([0], dtype=int16)");
    CheckArrayRepr<int16_t>({-2}, Shape({1}), "array([-2], dtype=int16)");
    CheckArrayRepr<int16_t>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                            "array([[0, 1, 2],\n"
                            "       [3, 4, 5]], dtype=int16)");
    CheckArrayRepr<int16_t>({0, 1, 2, -3, 4, 5}, Shape({2, 3}),
                            "array([[ 0,  1,  2],\n"
                            "       [-3,  4,  5]], dtype=int16)");
    CheckArrayRepr<int16_t>({3}, Shape({1, 1, 1, 1}), "array([[[[3]]]], dtype=int16)");

    // int32
    CheckArrayRepr<int32_t>({0}, Shape({1}), "array([0], dtype=int32)");
    CheckArrayRepr<int32_t>({-2}, Shape({1}), "array([-2], dtype=int32)");
    CheckArrayRepr<int32_t>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                            "array([[0, 1, 2],\n"
                            "       [3, 4, 5]], dtype=int32)");
    CheckArrayRepr<int32_t>({0, 1, 2, -3, 4, 5}, Shape({2, 3}),
                            "array([[ 0,  1,  2],\n"
                            "       [-3,  4,  5]], dtype=int32)");
    CheckArrayRepr<int32_t>({3}, Shape({1, 1, 1, 1}), "array([[[[3]]]], dtype=int32)");

    // int64
    CheckArrayRepr<int64_t>({0}, Shape({1}), "array([0], dtype=int64)");
    CheckArrayRepr<int64_t>({-2}, Shape({1}), "array([-2], dtype=int64)");
    CheckArrayRepr<int64_t>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                            "array([[0, 1, 2],\n"
                            "       [3, 4, 5]], dtype=int64)");
    CheckArrayRepr<int64_t>({0, 1, 2, -3, 4, 5}, Shape({2, 3}),
                            "array([[ 0,  1,  2],\n"
                            "       [-3,  4,  5]], dtype=int64)");
    CheckArrayRepr<int64_t>({3}, Shape({1, 1, 1, 1}), "array([[[[3]]]], dtype=int64)");

    // uint8
    CheckArrayRepr<uint8_t>({0}, Shape({1}), "array([0], dtype=uint8)");
    CheckArrayRepr<uint8_t>({2}, Shape({1}), "array([2], dtype=uint8)");
    CheckArrayRepr<uint8_t>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                            "array([[0, 1, 2],\n"
                            "       [3, 4, 5]], dtype=uint8)");
    CheckArrayRepr<uint8_t>({3}, Shape({1, 1, 1, 1}), "array([[[[3]]]], dtype=uint8)");

    // float32
    CheckArrayRepr<float>({0}, Shape({1}), "array([0.], dtype=float32)");
    CheckArrayRepr<float>({3.25}, Shape({1}), "array([3.25], dtype=float32)");
    CheckArrayRepr<float>({-3.25}, Shape({1}), "array([-3.25], dtype=float32)");
    CheckArrayRepr<float>({std::numeric_limits<float>::infinity()}, Shape({1}), "array([ inf], dtype=float32)");
    CheckArrayRepr<float>({-std::numeric_limits<float>::infinity()}, Shape({1}), "array([ -inf], dtype=float32)");
    CheckArrayRepr<float>({std::nanf("")}, Shape({1}), "array([ nan], dtype=float32)");
    CheckArrayRepr<float>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                          "array([[0., 1., 2.],\n"
                          "       [3., 4., 5.]], dtype=float32)");
    CheckArrayRepr<float>({0, 1, 2, 3.25, 4, 5}, Shape({2, 3}),
                          "array([[0.  , 1.  , 2.  ],\n"
                          "       [3.25, 4.  , 5.  ]], dtype=float32)");
    CheckArrayRepr<float>({0, 1, 2, -3.25, 4, 5}, Shape({2, 3}),
                          "array([[ 0.  ,  1.  ,  2.  ],\n"
                          "       [-3.25,  4.  ,  5.  ]], dtype=float32)");
    CheckArrayRepr<float>({3.25}, Shape({1, 1, 1, 1}), "array([[[[3.25]]]], dtype=float32)");

    // float64
    CheckArrayRepr<double>({0}, Shape({1}), "array([0.], dtype=float64)");
    CheckArrayRepr<double>({3.25}, Shape({1}), "array([3.25], dtype=float64)");
    CheckArrayRepr<double>({-3.25}, Shape({1}), "array([-3.25], dtype=float64)");
    CheckArrayRepr<double>({std::numeric_limits<double>::infinity()}, Shape({1}), "array([ inf], dtype=float64)");
    CheckArrayRepr<double>({-std::numeric_limits<double>::infinity()}, Shape({1}), "array([ -inf], dtype=float64)");
    CheckArrayRepr<double>({std::nan("")}, Shape({1}), "array([ nan], dtype=float64)");
    CheckArrayRepr<double>({0, 1, 2, 3, 4, 5}, Shape({2, 3}),
                           "array([[0., 1., 2.],\n"
                           "       [3., 4., 5.]], dtype=float64)");
    CheckArrayRepr<double>({0, 1, 2, 3.25, 4, 5}, Shape({2, 3}),
                           "array([[0.  , 1.  , 2.  ],\n"
                           "       [3.25, 4.  , 5.  ]], dtype=float64)");
    CheckArrayRepr<double>({0, 1, 2, -3.25, 4, 5}, Shape({2, 3}),
                           "array([[ 0.  ,  1.  ,  2.  ],\n"
                           "       [-3.25,  4.  ,  5.  ]], dtype=float64)");
    CheckArrayRepr<double>({3.25}, Shape({1, 1, 1, 1}), "array([[[[3.25]]]], dtype=float64)");
}

TEST(ArrayReprTest, ArrayReprWithGraphIds) {
    // No graph
    CheckArrayRepr<int32_t>({3}, Shape({1}), "array([3], dtype=int32)");

    // Single graph
    CheckArrayRepr<int32_t>({-2}, Shape({1}), "array([-2], dtype=int32, graph_ids=[graph_1])", {"graph_1"});

    // Two graphs
    CheckArrayRepr<int32_t>({1}, Shape({1}), "array([1], dtype=int32, graph_ids=[graph_1, graph_2])", {"graph_1, graph_2"});

    // Multiple graphs
    CheckArrayRepr<int32_t>({-9}, Shape({1}), "array([-9], dtype=int32, graph_ids=[graph_1, graph_2, graph_3, graph_4, graph_5])",
                            {"graph_1, graph_2", "graph_3", "graph_4", "graph_5"});
}

}  // namespace
}  // namespace xchainer
<commit_msg>Put expectation first<commit_after>#include "xchainer/array_repr.h"

#include <gtest/gtest.h>

#include <algorithm>
#include <cmath>
#include <limits>
#include <vector>

#include "xchainer/device.h"

namespace xchainer {
namespace {

template <typename T>
void CheckArrayReprWithCurrentDevice(const std::string& expected, const std::vector<T>& data_vec, Shape shape,
                                     std::vector<GraphId> graph_ids) {
    // Copy to a contiguous memory block because std::vector<bool> is not packed as a sequence of bool's.
    std::shared_ptr<T> data_ptr = std::make_unique<T[]>(data_vec.size());
    std::copy(data_vec.begin(), data_vec.end(), data_ptr.get());
    Array array = Array::FromBuffer(shape, TypeToDtype<T>, static_cast<std::shared_ptr<void>>(data_ptr));
    std::for_each(graph_ids.begin(), graph_ids.end(), [&array](const GraphId& graph_id) { array.RequireGrad(graph_id); });

    // std::string version
    EXPECT_EQ(ArrayRepr(array), expected);

    // std::ostream version
    std::ostringstream os;
    os << array;
    EXPECT_EQ(os.str(), expected);
}

template <typename T>
void CheckArrayRepr(const std::string& expected, const std::vector<T>& data_vec, Shape shape,
                    const std::vector<GraphId>& graph_ids = std::vector<GraphId>()) {
    {
        DeviceScope ctx{"cpu"};
        CheckArrayReprWithCurrentDevice(expected, data_vec, shape, graph_ids);
    }

    {
#ifdef XCHAINER_ENABLE_CUDA
        DeviceScope ctx{"cuda"};
        CheckArrayReprWithCurrentDevice(expected, data_vec, shape, graph_ids);
#endif  // XCHAINER_ENABLE_CUDA
    }
}

TEST(ArrayReprTest, ArrayRepr) {
    // bool
    CheckArrayRepr<bool>("array([False], dtype=bool)", {false}, Shape({1}));
    CheckArrayRepr<bool>("array([ True], dtype=bool)", {true}, Shape({1}));
    CheckArrayRepr<bool>(
        "array([[False,  True,  True],\n"
        "       [ True, False,  True]], dtype=bool)",
        {false, true, true, true, false, true}, Shape({2, 3}));
    CheckArrayRepr<bool>("array([[[[ True]]]], dtype=bool)", {true}, Shape({1, 1, 1, 1}));

    // int8
    CheckArrayRepr<int8_t>("array([0], dtype=int8)", {0}, Shape({1}));
    CheckArrayRepr<int8_t>("array([-2], dtype=int8)", {-2}, Shape({1}));
    CheckArrayRepr<int8_t>(
        "array([[0, 1, 2],\n"
        "       [3, 4, 5]], dtype=int8)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));

    CheckArrayRepr<int8_t>(
        "array([[ 0,  1,  2],\n"
        "       [-3,  4,  5]], dtype=int8)",
        {0, 1, 2, -3, 4, 5}, Shape({2, 3}));

    CheckArrayRepr<int8_t>("array([[[[3]]]], dtype=int8)", {3}, Shape({1, 1, 1, 1}));

    // int16
    CheckArrayRepr<int16_t>("array([0], dtype=int16)", {0}, Shape({1}));
    CheckArrayRepr<int16_t>("array([-2], dtype=int16)", {-2}, Shape({1}));
    CheckArrayRepr<int16_t>(
        "array([[0, 1, 2],\n"
        "       [3, 4, 5]], dtype=int16)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));

    CheckArrayRepr<int16_t>(
        "array([[ 0,  1,  2],\n"
        "       [-3,  4,  5]], dtype=int16)",
        {0, 1, 2, -3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<int16_t>("array([[[[3]]]], dtype=int16)", {3}, Shape({1, 1, 1, 1}));

    // int32
    CheckArrayRepr<int32_t>("array([0], dtype=int32)", {0}, Shape({1}));
    CheckArrayRepr<int32_t>("array([-2], dtype=int32)", {-2}, Shape({1}));
    CheckArrayRepr<int32_t>(
        "array([[0, 1, 2],\n"
        "       [3, 4, 5]], dtype=int32)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));

    CheckArrayRepr<int32_t>(
        "array([[ 0,  1,  2],\n"
        "       [-3,  4,  5]], dtype=int32)",
        {0, 1, 2, -3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<int32_t>("array([[[[3]]]], dtype=int32)", {3}, Shape({1, 1, 1, 1}));

    // int64
    CheckArrayRepr<int64_t>("array([0], dtype=int64)", {0}, Shape({1}));
    CheckArrayRepr<int64_t>("array([-2], dtype=int64)", {-2}, Shape({1}));
    CheckArrayRepr<int64_t>(
        "array([[0, 1, 2],\n"
        "       [3, 4, 5]], dtype=int64)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<int64_t>(
        "array([[ 0,  1,  2],\n"
        "       [-3,  4,  5]], dtype=int64)",
        {0, 1, 2, -3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<int64_t>("array([[[[3]]]], dtype=int64)", {3}, Shape({1, 1, 1, 1}));

    // uint8
    CheckArrayRepr<uint8_t>("array([0], dtype=uint8)", {0}, Shape({1}));
    CheckArrayRepr<uint8_t>("array([2], dtype=uint8)", {2}, Shape({1}));
    CheckArrayRepr<uint8_t>(
        "array([[0, 1, 2],\n"
        "       [3, 4, 5]], dtype=uint8)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<uint8_t>("array([[[[3]]]], dtype=uint8)", {3}, Shape({1, 1, 1, 1}));

    // float32
    CheckArrayRepr<float>("array([0.], dtype=float32)", {0}, Shape({1}));
    CheckArrayRepr<float>("array([3.25], dtype=float32)", {3.25}, Shape({1}));
    CheckArrayRepr<float>("array([-3.25], dtype=float32)", {-3.25}, Shape({1}));
    CheckArrayRepr<float>("array([ inf], dtype=float32)", {std::numeric_limits<float>::infinity()}, Shape({1}));
    CheckArrayRepr<float>("array([ -inf], dtype=float32)", {-std::numeric_limits<float>::infinity()}, Shape({1}));
    CheckArrayRepr<float>("array([ nan], dtype=float32)", {std::nanf("")}, Shape({1}));
    CheckArrayRepr<float>(
        "array([[0., 1., 2.],\n"
        "       [3., 4., 5.]], dtype=float32)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<float>(
        "array([[0.  , 1.  , 2.  ],\n"
        "       [3.25, 4.  , 5.  ]], dtype=float32)",
        {0, 1, 2, 3.25, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<float>(
        "array([[ 0.  ,  1.  ,  2.  ],\n"
        "       [-3.25,  4.  ,  5.  ]], dtype=float32)",
        {0, 1, 2, -3.25, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<float>("array([[[[3.25]]]], dtype=float32)", {3.25}, Shape({1, 1, 1, 1}));

    // float64
    CheckArrayRepr<double>("array([0.], dtype=float64)", {0}, Shape({1}));
    CheckArrayRepr<double>("array([3.25], dtype=float64)", {3.25}, Shape({1}));
    CheckArrayRepr<double>("array([-3.25], dtype=float64)", {-3.25}, Shape({1}));
    CheckArrayRepr<double>("array([ inf], dtype=float64)", {std::numeric_limits<double>::infinity()}, Shape({1}));
    CheckArrayRepr<double>("array([ -inf], dtype=float64)", {-std::numeric_limits<double>::infinity()}, Shape({1}));
    CheckArrayRepr<double>("array([ nan], dtype=float64)", {std::nan("")}, Shape({1}));
    CheckArrayRepr<double>(
        "array([[0., 1., 2.],\n"
        "       [3., 4., 5.]], dtype=float64)",
        {0, 1, 2, 3, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<double>(
        "array([[0.  , 1.  , 2.  ],\n"
        "       [3.25, 4.  , 5.  ]], dtype=float64)",
        {0, 1, 2, 3.25, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<double>(
        "array([[ 0.  ,  1.  ,  2.  ],\n"
        "       [-3.25,  4.  ,  5.  ]], dtype=float64)",
        {0, 1, 2, -3.25, 4, 5}, Shape({2, 3}));
    CheckArrayRepr<double>("array([[[[3.25]]]], dtype=float64)", {3.25}, Shape({1, 1, 1, 1}));
}

TEST(ArrayReprTest, ArrayReprWithGraphIds) {
    // No graph
    CheckArrayRepr<int32_t>("array([3], dtype=int32)", {3}, Shape({1}));

    // Single graph
    CheckArrayRepr<int32_t>("array([-2], dtype=int32, graph_ids=[graph_1])", {-2}, Shape({1}), {"graph_1"});

    // Two graphs
    CheckArrayRepr<int32_t>("array([1], dtype=int32, graph_ids=[graph_1, graph_2])", {1}, Shape({1}), {"graph_1, graph_2"});

    // Multiple graphs
    CheckArrayRepr<int32_t>("array([-9], dtype=int32, graph_ids=[graph_1, graph_2, graph_3, graph_4, graph_5])", {-9}, Shape({1}),
                            {"graph_1, graph_2", "graph_3", "graph_4", "graph_5"});
}

}  // namespace
}  // namespace xchainer
<|endoftext|>


<commit_before>/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/svg/include/SkSVGPattern.h"

#include "include/core/SkPictureRecorder.h"
#include "include/core/SkShader.h"
#include "modules/svg/include/SkSVGRenderContext.h"
#include "modules/svg/include/SkSVGValue.h"

SkSVGPattern::SkSVGPattern() : INHERITED(SkSVGTag::kPattern) {}

bool SkSVGPattern::parseAndSetAttribute(const char* name, const char* value) {
    return INHERITED::parseAndSetAttribute(name, value) ||
           this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", name, value)) ||
           this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value)) ||
           this->setWidth(SkSVGAttributeParser::parse<SkSVGLength>("width", name, value)) ||
           this->setHeight(SkSVGAttributeParser::parse<SkSVGLength>("height", name, value)) ||
           this->setPatternTransform(SkSVGAttributeParser::parse<SkSVGTransformType>(
                   "patternTransform", name, value)) ||
           this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value));
}

const SkSVGPattern* SkSVGPattern::hrefTarget(const SkSVGRenderContext& ctx) const {
    if (fHref.iri().isEmpty()) {
        return nullptr;
    }

    const auto href = ctx.findNodeById(fHref);
    if (!href || href->tag() != SkSVGTag::kPattern) {
        return nullptr;
    }

    return static_cast<const SkSVGPattern*>(href.get());
}

template <typename T>
bool inherit_if_needed(const SkTLazy<T>& src, SkTLazy<T>& dst) {
    if (!dst.isValid()) {
        dst = src;
        return true;
    }

    return false;
}

/* https://www.w3.org/TR/SVG11/pservers.html#PatternElementHrefAttribute
 *
 * Any attributes which are defined on the referenced element which are not defined on this element
 * are inherited by this element. If this element has no children, and the referenced element does
 * (possibly due to its own ‘xlink:href’ attribute), then this element inherits the children from
 * the referenced element. Inheritance can be indirect to an arbitrary level; thus, if the
 * referenced element inherits attributes or children due to its own ‘xlink:href’ attribute, then
 * the current element can inherit those attributes or children.
 */
const SkSVGPattern* SkSVGPattern::resolveHref(const SkSVGRenderContext& ctx,
                                              PatternAttributes* attrs) const {
    const SkSVGPattern *currentNode = this,
                       *contentNode = this;
    do {
        // Bitwise OR to avoid short-circuiting.
        const bool didInherit =
            inherit_if_needed(currentNode->fX               , attrs->fX)      |
            inherit_if_needed(currentNode->fY               , attrs->fY)      |
            inherit_if_needed(currentNode->fWidth           , attrs->fWidth)  |
            inherit_if_needed(currentNode->fHeight          , attrs->fHeight) |
            inherit_if_needed(currentNode->fPatternTransform, attrs->fPatternTransform);

        if (!contentNode->hasChildren()) {
            contentNode = currentNode;
        }

        if (contentNode->hasChildren() && !didInherit) {
            // All attributes have been resolved, and a valid content node has been found.
            // We can terminate the href chain early.
            break;
        }

        // TODO: reference loop mitigation.
        currentNode = currentNode->hrefTarget(ctx);
    } while (currentNode);

    return contentNode;
}

bool SkSVGPattern::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
    PatternAttributes attrs;
    const auto* contentNode = this->resolveHref(ctx, &attrs);

    const auto tile = ctx.lengthContext().resolveRect(
            attrs.fX.isValid()      ? *attrs.fX      : SkSVGLength(0),
            attrs.fY.isValid()      ? *attrs.fY      : SkSVGLength(0),
            attrs.fWidth.isValid()  ? *attrs.fWidth  : SkSVGLength(0),
            attrs.fHeight.isValid() ? *attrs.fHeight : SkSVGLength(0));

    if (tile.isEmpty()) {
        return false;
    }

    const SkMatrix* patternTransform = attrs.fPatternTransform.isValid()
            ? attrs.fPatternTransform.get()
            : nullptr;

    SkPictureRecorder recorder;
    SkSVGRenderContext recordingContext(ctx, recorder.beginRecording(tile));

    // Cannot call into INHERITED:: because SkSVGHiddenContainer skips rendering.
    contentNode->SkSVGContainer::onRender(recordingContext);

    paint->setShader(recorder.finishRecordingAsPicture()->makeShader(
                                                 SkTileMode::kRepeat,
                                                 SkTileMode::kRepeat,
                                                 SkFilterMode::kLinear,
                                                 patternTransform,
                                                 &tile));
    return true;
}
<commit_msg>Fix Clang warning -Wbitwise-instead-of-logical.<commit_after>/*
 * Copyright 2017 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/svg/include/SkSVGPattern.h"

#include "include/core/SkPictureRecorder.h"
#include "include/core/SkShader.h"
#include "modules/svg/include/SkSVGRenderContext.h"
#include "modules/svg/include/SkSVGValue.h"

SkSVGPattern::SkSVGPattern() : INHERITED(SkSVGTag::kPattern) {}

bool SkSVGPattern::parseAndSetAttribute(const char* name, const char* value) {
    return INHERITED::parseAndSetAttribute(name, value) ||
           this->setX(SkSVGAttributeParser::parse<SkSVGLength>("x", name, value)) ||
           this->setY(SkSVGAttributeParser::parse<SkSVGLength>("y", name, value)) ||
           this->setWidth(SkSVGAttributeParser::parse<SkSVGLength>("width", name, value)) ||
           this->setHeight(SkSVGAttributeParser::parse<SkSVGLength>("height", name, value)) ||
           this->setPatternTransform(SkSVGAttributeParser::parse<SkSVGTransformType>(
                   "patternTransform", name, value)) ||
           this->setHref(SkSVGAttributeParser::parse<SkSVGIRI>("xlink:href", name, value));
}

const SkSVGPattern* SkSVGPattern::hrefTarget(const SkSVGRenderContext& ctx) const {
    if (fHref.iri().isEmpty()) {
        return nullptr;
    }

    const auto href = ctx.findNodeById(fHref);
    if (!href || href->tag() != SkSVGTag::kPattern) {
        return nullptr;
    }

    return static_cast<const SkSVGPattern*>(href.get());
}

template <typename T>
int inherit_if_needed(const SkTLazy<T>& src, SkTLazy<T>& dst) {
    if (!dst.isValid()) {
        dst = src;
        return 1;
    }

    return 0;
}

/* https://www.w3.org/TR/SVG11/pservers.html#PatternElementHrefAttribute
 *
 * Any attributes which are defined on the referenced element which are not defined on this element
 * are inherited by this element. If this element has no children, and the referenced element does
 * (possibly due to its own ‘xlink:href’ attribute), then this element inherits the children from
 * the referenced element. Inheritance can be indirect to an arbitrary level; thus, if the
 * referenced element inherits attributes or children due to its own ‘xlink:href’ attribute, then
 * the current element can inherit those attributes or children.
 */
const SkSVGPattern* SkSVGPattern::resolveHref(const SkSVGRenderContext& ctx,
                                              PatternAttributes* attrs) const {
    const SkSVGPattern *currentNode = this,
                       *contentNode = this;
    do {
        // Bitwise OR to avoid short-circuiting.
        const bool didInherit =
            inherit_if_needed(currentNode->fX               , attrs->fX)      |
            inherit_if_needed(currentNode->fY               , attrs->fY)      |
            inherit_if_needed(currentNode->fWidth           , attrs->fWidth)  |
            inherit_if_needed(currentNode->fHeight          , attrs->fHeight) |
            inherit_if_needed(currentNode->fPatternTransform, attrs->fPatternTransform);

        if (!contentNode->hasChildren()) {
            contentNode = currentNode;
        }

        if (contentNode->hasChildren() && !didInherit) {
            // All attributes have been resolved, and a valid content node has been found.
            // We can terminate the href chain early.
            break;
        }

        // TODO: reference loop mitigation.
        currentNode = currentNode->hrefTarget(ctx);
    } while (currentNode);

    return contentNode;
}

bool SkSVGPattern::onAsPaint(const SkSVGRenderContext& ctx, SkPaint* paint) const {
    PatternAttributes attrs;
    const auto* contentNode = this->resolveHref(ctx, &attrs);

    const auto tile = ctx.lengthContext().resolveRect(
            attrs.fX.isValid()      ? *attrs.fX      : SkSVGLength(0),
            attrs.fY.isValid()      ? *attrs.fY      : SkSVGLength(0),
            attrs.fWidth.isValid()  ? *attrs.fWidth  : SkSVGLength(0),
            attrs.fHeight.isValid() ? *attrs.fHeight : SkSVGLength(0));

    if (tile.isEmpty()) {
        return false;
    }

    const SkMatrix* patternTransform = attrs.fPatternTransform.isValid()
            ? attrs.fPatternTransform.get()
            : nullptr;

    SkPictureRecorder recorder;
    SkSVGRenderContext recordingContext(ctx, recorder.beginRecording(tile));

    // Cannot call into INHERITED:: because SkSVGHiddenContainer skips rendering.
    contentNode->SkSVGContainer::onRender(recordingContext);

    paint->setShader(recorder.finishRecordingAsPicture()->makeShader(
                                                 SkTileMode::kRepeat,
                                                 SkTileMode::kRepeat,
                                                 SkFilterMode::kLinear,
                                                 patternTransform,
                                                 &tile));
    return true;
}
<|endoftext|>


<commit_before>#pragma once

#include "pile.hpp"

namespace morda{

/**
 * @brief Overlay container for displaying widgets on top of anything.
 * Overlay container is used for displaying topmost widgets like context menus, hint popups etc.
 * Essentially, the overlay is a simple pile container which keeps track of open overlay widgets.
 * From GUI scripts it can be instantiated as "overlay".
 */
class overlay :
		virtual public widget,
		public pile
{
public:
	overlay(std::shared_ptr<morda::context> c, const puu::forest& desc);

	overlay(const overlay&) = delete;
	overlay& operator=(const overlay&) = delete;

	/**
	 * @brief Showing context menu.
	 * This function adds the context menu widget to the overlay as the topmost widget.
	 * The given context menu widget is wrapped into special overhead container to make it properly positioned
	 * on the screen and to handle mouse clicks outside of the context menu widget
	 * (i.e. close the context menu in case of mouse click outside).
	 * @param menu - context menu widget.
	 * @param pos - position of top left corner of the context menu within the overlay container.
	 * @return the final widget added to the overlay pile. This widget can be used to later close the particular context menu
	 *         by just removing the widget from its parent.
	 */
	std::shared_ptr<widget> show_context_menu(std::shared_ptr<widget> menu, vector2 pos);

	/**
	 * @brief Close all context menus.
	 */
	void close_all_context_menus();
private:

};

}
<commit_msg>stuff<commit_after>#pragma once

#include "pile.hpp"

namespace morda{

/**
 * @brief Overlay container for displaying widgets on top of anything.
 * Overlay container is used for displaying topmost widgets like context menus, hint popups etc.
 * Essentially, the overlay is a simple pile container which keeps track of open overlay widgets.
 * From GUI scripts it can be instantiated as "overlay".
 */
class overlay : public pile
{
public:
	overlay(std::shared_ptr<morda::context> c, const puu::forest& desc);

	overlay(const overlay&) = delete;
	overlay& operator=(const overlay&) = delete;

	/**
	 * @brief Showing context menu.
	 * This function adds the context menu widget to the overlay as the topmost widget.
	 * The given context menu widget is wrapped into special overhead container to make it properly positioned
	 * on the screen and to handle mouse clicks outside of the context menu widget
	 * (i.e. close the context menu in case of mouse click outside).
	 * @param menu - context menu widget.
	 * @param pos - position of top left corner of the context menu within the overlay container.
	 * @return the final widget added to the overlay pile. This widget can be used to later close the particular context menu
	 *         by just removing the widget from its parent.
	 */
	std::shared_ptr<widget> show_context_menu(std::shared_ptr<widget> menu, vector2 pos);

	/**
	 * @brief Close all context menus.
	 */
	void close_all_context_menus();
private:

};

}
<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2016 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice,
//      this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the author nor the names of other contributors may
//      be used to endorse or promote products derived from this software
//      without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "TrackListView.h"

#include <cursespp/Colors.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/IMessage.h>
#include <cursespp/Text.h>

#include <core/library/LocalLibraryConstants.h>

#include <app/util/Hotkeys.h>
#include <app/util/Duration.h>
#include <app/window/EntryWithHeader.h>

#include <boost/format.hpp>

#define WINDOW_MESSAGE_QUERY_COMPLETED 1002

using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::library;
using namespace musik::box;
using namespace cursespp;

using namespace boost::chrono;

/* if the user hasn't changed the selected index in 30 seconds
we assume he's not paying attention, and will automatically scroll
the view to the next track if it's invisible. */
static const milliseconds AUTO_SCROLL_COOLDOWN = milliseconds(30000LL);

static inline milliseconds now() {
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch());
}

static IScrollAdapter::EntryPtr MISSING_ENTRY = IScrollAdapter::EntryPtr();

TrackListView::TrackListView(
    PlaybackService& playback,
    LibraryPtr library,
    RowFormatter formatter)
: ListWindow(nullptr)
, playback(playback) {
    this->library = library;
    this->library->QueryCompleted.connect(this, &TrackListView::OnQueryCompleted);
    this->playback.TrackChanged.connect(this, &TrackListView::OnTrackChanged);
    this->adapter = new Adapter(*this);
    this->lastQueryHash = 0;
    this->lastChanged = now();
    this->formatter = formatter;

    if (!MISSING_ENTRY) {
        auto e = std::shared_ptr<SingleLineEntry>(new SingleLineEntry("track missing"));
        e->SetAttrs(COLOR_PAIR(CURSESPP_TEXT_ERROR));
        MISSING_ENTRY = e;
    }
}

TrackListView::~TrackListView() {
    delete this->adapter;
    this->adapter = nullptr;
}

void TrackListView::Requery(std::shared_ptr<TrackListQueryBase> query) {
    this->query = query;
    this->library->Enqueue(this->query);
}

void TrackListView::OnQueryCompleted(IQueryPtr query) {
    if (query == this->query) {
        this->PostMessage(WINDOW_MESSAGE_QUERY_COMPLETED);
    }
}

void TrackListView::OnSelectionChanged(size_t newIndex, size_t oldIndex) {
    ListWindow::OnSelectionChanged(newIndex, oldIndex);
    this->lastChanged = now();
}

std::shared_ptr<TrackList> TrackListView::GetTrackList() {
    return this->metadata;
}

void TrackListView::Clear() {
    this->query.reset();
    this->metadata.reset(new TrackList(this->library));
    this->headers.reset(new std::set<size_t>());
    this->OnAdapterChanged();
}

void TrackListView::ScrollToPlaying() {
    if (this->playing) {
        DBID id = this->playing->Id();
        for (size_t i = 0; i < this->metadata->Count(); i++) {
            if (this->metadata->GetId(i) == id) {
                this->SetSelectedIndex(i);

                auto pos = this->GetScrollPosition();
                size_t first = pos.firstVisibleEntryIndex;
                size_t last = first + pos.visibleEntryCount;
                if (i < first || i > last) {
                    /* only scroll if the playing track is not visible. */
                    this->ScrollTo(i);
                }
                break;
            }
        }
    }
}

void TrackListView::ProcessMessage(IMessage &message) {
    if (message.Type() == WINDOW_MESSAGE_QUERY_COMPLETED) {
        if (this->query && this->query->GetStatus() == IQuery::Finished) {
            this->metadata = this->query->GetResult();
            this->headers = this->query->GetHeaders();

            /* if the query was functionally the same as the last query, don't
            mess with the selected index */
            if (this->lastQueryHash != query->GetQueryHash()) {
                this->SetSelectedIndex(0);
                this->ScrollToTop();
            }

            this->lastQueryHash = this->query->GetQueryHash();
            this->query.reset();

            this->OnAdapterChanged(); /* internal handling */
            this->Requeried(); /* for external handlers */
        }
    }
}

bool TrackListView::KeyPress(const std::string& key) {
    if (Hotkeys::Is(Hotkeys::NavigateJumpToPlaying, key)) {
        this->ScrollToPlaying();
        return true;
    }

    return ListWindow::KeyPress(key);
}

void TrackListView::OnTrackChanged(size_t index, musik::core::TrackPtr track) {
    this->playing = track;
    this->OnAdapterChanged();

    if (now() - lastChanged >= AUTO_SCROLL_COOLDOWN) {
        this->ScrollToPlaying();
    }
}

IScrollAdapter& TrackListView::GetScrollAdapter() {
    return *this->adapter;
}

TrackListView::Adapter::Adapter(TrackListView &parent)
: parent(parent) {
}

size_t TrackListView::Adapter::GetEntryCount() {
    return parent.metadata ? parent.metadata->Count() : 0;
}

#define TRACK_COL_WIDTH 3
#define ARTIST_COL_WIDTH 17
#define DURATION_COL_WIDTH 5 /* 00:00 */

static std::string formatWithoutAlbum(TrackPtr track, size_t width) {
    std::string trackNum = text::Align(
        track->GetValue(constants::Track::TRACK_NUM),
        text::AlignRight,
        TRACK_COL_WIDTH);

    std::string duration = text::Align(
        musik::box::duration::Duration(track->GetValue(constants::Track::DURATION)),
        text::AlignRight,
        DURATION_COL_WIDTH);

    std::string artist = text::Align(
        track->GetValue(constants::Track::ARTIST),
        text::AlignLeft,
        ARTIST_COL_WIDTH);

    int titleWidth =
        width -
        TRACK_COL_WIDTH -
        DURATION_COL_WIDTH -
        ARTIST_COL_WIDTH -
        (3 * 3); /* 3 = spacing */

    titleWidth = std::max(0, titleWidth);

    std::string title = text::Align(
        track->GetValue(constants::Track::TITLE),
        text::AlignLeft,
        titleWidth);

    return boost::str(
        boost::format("%s   %s   %s   %s")
        % trackNum % title % duration % artist);
}

IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(size_t index) {
    bool selected = index == parent.GetSelectedIndex();
    int64 attrs = selected ? COLOR_PAIR(CURSESPP_HIGHLIGHTED_LIST_ITEM) : -1LL;

    TrackPtr track = parent.metadata->Get(index);

    if (!track) {
        return MISSING_ENTRY;
    }

    TrackPtr playing = parent.playing;
    if (playing &&
        playing->Id() == track->Id() &&
        playing->LibraryId() == track->LibraryId())
    {
        if (selected) {
            attrs = COLOR_PAIR(CURSESPP_HIGHLIGHTED_SELECTED_LIST_ITEM);
        }
        else {
            attrs = COLOR_PAIR(CURSESPP_SELECTED_LIST_ITEM);
        }
    }

    std::string text = parent.formatter
        ? parent.formatter(track, this->GetWidth())
        : formatWithoutAlbum(track, this->GetWidth());

    if (this->parent.headers->find(index) != this->parent.headers->end()) {
        std::string album = track->GetValue(constants::Track::ALBUM);
        std::shared_ptr<EntryWithHeader> entry(new EntryWithHeader(album, text));
        entry->SetAttrs(COLOR_PAIR(CURSESPP_LIST_ITEM_HEADER), attrs);
        return entry;
    }
    else {
        std::shared_ptr<SingleLineEntry> entry(new SingleLineEntry(text));
        entry->SetAttrs(attrs);
        return entry;
    }
}
<commit_msg>Fixed a silly null pointer dereference in TrackListView related to auto-scrolling behavior.<commit_after>//////////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2007-2016 musikcube team
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice,
//      this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the author nor the names of other contributors may
//      be used to endorse or promote products derived from this software
//      without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "TrackListView.h"

#include <cursespp/Colors.h>
#include <cursespp/SingleLineEntry.h>
#include <cursespp/IMessage.h>
#include <cursespp/Text.h>

#include <core/library/LocalLibraryConstants.h>

#include <app/util/Hotkeys.h>
#include <app/util/Duration.h>
#include <app/window/EntryWithHeader.h>

#include <boost/format.hpp>

#define WINDOW_MESSAGE_QUERY_COMPLETED 1002

using namespace musik::core;
using namespace musik::core::audio;
using namespace musik::core::library;
using namespace musik::box;
using namespace cursespp;

using namespace boost::chrono;

/* if the user hasn't changed the selected index in 30 seconds
we assume he's not paying attention, and will automatically scroll
the view to the next track if it's invisible. */
static const milliseconds AUTO_SCROLL_COOLDOWN = milliseconds(30000LL);

static inline milliseconds now() {
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch());
}

static IScrollAdapter::EntryPtr MISSING_ENTRY = IScrollAdapter::EntryPtr();

TrackListView::TrackListView(
    PlaybackService& playback,
    LibraryPtr library,
    RowFormatter formatter)
: ListWindow(nullptr)
, playback(playback) {
    this->library = library;
    this->library->QueryCompleted.connect(this, &TrackListView::OnQueryCompleted);
    this->playback.TrackChanged.connect(this, &TrackListView::OnTrackChanged);
    this->adapter = new Adapter(*this);
    this->lastQueryHash = 0;
    this->lastChanged = now();
    this->formatter = formatter;

    if (!MISSING_ENTRY) {
        auto e = std::shared_ptr<SingleLineEntry>(new SingleLineEntry("track missing"));
        e->SetAttrs(COLOR_PAIR(CURSESPP_TEXT_ERROR));
        MISSING_ENTRY = e;
    }
}

TrackListView::~TrackListView() {
    delete this->adapter;
    this->adapter = nullptr;
}

void TrackListView::Requery(std::shared_ptr<TrackListQueryBase> query) {
    this->query = query;
    this->library->Enqueue(this->query);
}

void TrackListView::OnQueryCompleted(IQueryPtr query) {
    if (query == this->query) {
        this->PostMessage(WINDOW_MESSAGE_QUERY_COMPLETED);
    }
}

void TrackListView::OnSelectionChanged(size_t newIndex, size_t oldIndex) {
    ListWindow::OnSelectionChanged(newIndex, oldIndex);
    this->lastChanged = now();
}

std::shared_ptr<TrackList> TrackListView::GetTrackList() {
    return this->metadata;
}

void TrackListView::Clear() {
    this->query.reset();
    this->metadata.reset(new TrackList(this->library));
    this->headers.reset(new std::set<size_t>());
    this->OnAdapterChanged();
}

void TrackListView::ScrollToPlaying() {
    if (this->playing && this->metadata) {
        DBID id = this->playing->Id();
        for (size_t i = 0; i < this->metadata->Count(); i++) {
            if (this->metadata->GetId(i) == id) {
                this->SetSelectedIndex(i);

                auto pos = this->GetScrollPosition();
                size_t first = pos.firstVisibleEntryIndex;
                size_t last = first + pos.visibleEntryCount;
                if (i < first || i > last) {
                    /* only scroll if the playing track is not visible. */
                    this->ScrollTo(i);
                }
                break;
            }
        }
    }
}

void TrackListView::ProcessMessage(IMessage &message) {
    if (message.Type() == WINDOW_MESSAGE_QUERY_COMPLETED) {
        if (this->query && this->query->GetStatus() == IQuery::Finished) {
            this->metadata = this->query->GetResult();
            this->headers = this->query->GetHeaders();

            /* if the query was functionally the same as the last query, don't
            mess with the selected index */
            if (this->lastQueryHash != query->GetQueryHash()) {
                this->SetSelectedIndex(0);
                this->ScrollToTop();
            }

            this->lastQueryHash = this->query->GetQueryHash();
            this->query.reset();

            this->OnAdapterChanged(); /* internal handling */
            this->Requeried(); /* for external handlers */
        }
    }
}

bool TrackListView::KeyPress(const std::string& key) {
    if (Hotkeys::Is(Hotkeys::NavigateJumpToPlaying, key)) {
        this->ScrollToPlaying();
        return true;
    }

    return ListWindow::KeyPress(key);
}

void TrackListView::OnTrackChanged(size_t index, musik::core::TrackPtr track) {
    this->playing = track;
    this->OnAdapterChanged();

    if (now() - lastChanged >= AUTO_SCROLL_COOLDOWN) {
        this->ScrollToPlaying();
    }
}

IScrollAdapter& TrackListView::GetScrollAdapter() {
    return *this->adapter;
}

TrackListView::Adapter::Adapter(TrackListView &parent)
: parent(parent) {
}

size_t TrackListView::Adapter::GetEntryCount() {
    return parent.metadata ? parent.metadata->Count() : 0;
}

#define TRACK_COL_WIDTH 3
#define ARTIST_COL_WIDTH 17
#define DURATION_COL_WIDTH 5 /* 00:00 */

static std::string formatWithoutAlbum(TrackPtr track, size_t width) {
    std::string trackNum = text::Align(
        track->GetValue(constants::Track::TRACK_NUM),
        text::AlignRight,
        TRACK_COL_WIDTH);

    std::string duration = text::Align(
        musik::box::duration::Duration(track->GetValue(constants::Track::DURATION)),
        text::AlignRight,
        DURATION_COL_WIDTH);

    std::string artist = text::Align(
        track->GetValue(constants::Track::ARTIST),
        text::AlignLeft,
        ARTIST_COL_WIDTH);

    int titleWidth =
        width -
        TRACK_COL_WIDTH -
        DURATION_COL_WIDTH -
        ARTIST_COL_WIDTH -
        (3 * 3); /* 3 = spacing */

    titleWidth = std::max(0, titleWidth);

    std::string title = text::Align(
        track->GetValue(constants::Track::TITLE),
        text::AlignLeft,
        titleWidth);

    return boost::str(
        boost::format("%s   %s   %s   %s")
        % trackNum % title % duration % artist);
}

IScrollAdapter::EntryPtr TrackListView::Adapter::GetEntry(size_t index) {
    bool selected = index == parent.GetSelectedIndex();
    int64 attrs = selected ? COLOR_PAIR(CURSESPP_HIGHLIGHTED_LIST_ITEM) : -1LL;

    TrackPtr track = parent.metadata->Get(index);

    if (!track) {
        return MISSING_ENTRY;
    }

    TrackPtr playing = parent.playing;
    if (playing &&
        playing->Id() == track->Id() &&
        playing->LibraryId() == track->LibraryId())
    {
        if (selected) {
            attrs = COLOR_PAIR(CURSESPP_HIGHLIGHTED_SELECTED_LIST_ITEM);
        }
        else {
            attrs = COLOR_PAIR(CURSESPP_SELECTED_LIST_ITEM);
        }
    }

    std::string text = parent.formatter
        ? parent.formatter(track, this->GetWidth())
        : formatWithoutAlbum(track, this->GetWidth());

    if (this->parent.headers->find(index) != this->parent.headers->end()) {
        std::string album = track->GetValue(constants::Track::ALBUM);
        std::shared_ptr<EntryWithHeader> entry(new EntryWithHeader(album, text));
        entry->SetAttrs(COLOR_PAIR(CURSESPP_LIST_ITEM_HEADER), attrs);
        return entry;
    }
    else {
        std::shared_ptr<SingleLineEntry> entry(new SingleLineEntry(text));
        entry->SetAttrs(attrs);
        return entry;
    }
}
<|endoftext|>


<commit_before>/**
 * 2.4.4 constexpr 和 常量表达式
 * @Author Bob
 * @Eamil 0haizhu0@gmail.com
 * @Date 2017/7/12
 */
int main() {
  /**
   * 常量表达式：是指值不会改变并且在编译过程中就能得到计算结果的表达式。
   * 显然，字面值属于常量表达式。
   * 一个对象是不是常量表达式由它的数据类型和初始值共同决定。
   */
  const int max_files = 20; // 是常量表达式
  const int limit = max_files; // 是常量表达式
  int staff_size = 27; // 不是常量表达式：虽然初始值是个字面值常量，但他的数据类型只是普通int而不是const int，所以不属于常量表达式。
  // const int sz = get_size(); // sz不是常量表达式：尽管sz本身是一个常量，但是它的具体指要到运行时才能获取到，所以不属于常量表达式。

  /**
   * C++ 11 标准中，允许将常量声明为constexpr类型以便编辑器来验证变量的值是否是一个常量表达式。
   * 声明为constexpr的变量一定是一个常量，而且必须用常量表达式初始化。
   */
  constexpr int mf = 20; // 20 是常量表达式
  constexpr int limit2 = mf + 1; // mf+1 是常量表达式
  // constexpr int sz2 = size(); // 只有当size是一个constexpr函数时才是一条正确的声明语句

  /**
   * 字面值类型：常量表达式的值需要在编译时就得到计算，因此对声明constexpr时用到的类型必须有所限制。
   * 因为这些类型一般比较简单，值也显而易见、容易得到，就把它们成为"字面值类型"（literal type）。
   * 目前接触过的数据类型中，算数类型、引用、指针都属于字面值类型。
   * 自定义Sales_item、IO库、string 类型则不属于字面值类型，也就不能被定义为constexpr。
   */

  /**
   * 指针和constexpr：在constexpr声明中如果定义了一个指针，限定符constexpr仅对指针有效，与指针所指的对象无关。
   * 如下所示：constexpr把它所定义的对象置为了顶层const。
   */
  const int *p = nullptr;       // p是一个指向整数常量的指针
  constexpr int *q = nullptr;   // q是一个指向整数的常量指针

  /**
   * 与其他常量指针类似，constexpr既可以指向常量也可以指向非常量。
   */

  return 0;
}
<commit_msg>2.4.4 constexpr 和 常量表达式<commit_after>/**
 * 2.4.4 constexpr 和 常量表达式
 * @Author Bob
 * @Eamil 0haizhu0@gmail.com
 * @Date 2017/7/12
 */
int main() {
  /**
   * 常量表达式：是指值不会改变并且在编译过程中就能得到计算结果的表达式。
   * 显然，字面值属于常量表达式。
   * 一个对象是不是常量表达式由它的数据类型和初始值共同决定。
   */
  const int max_files = 20; // 是常量表达式
  const int limit = max_files; // 是常量表达式
  int staff_size = 27; // 不是常量表达式：虽然初始值是个字面值常量，但他的数据类型只是普通int而不是const int，所以不属于常量表达式。
  // const int sz = get_size(); // sz不是常量表达式：尽管sz本身是一个常量，但是它的具体指要到运行时才能获取到，所以不属于常量表达式。

  /**
   * C++ 11 标准中，允许将常量声明为constexpr类型以便编辑器来验证变量的值是否是一个常量表达式。
   * 声明为constexpr的变量一定是一个常量，而且必须用常量表达式初始化。
   */
  constexpr int mf = 20; // 20 是常量表达式
  constexpr int limit2 = mf + 1; // mf+1 是常量表达式
  // constexpr int sz2 = size(); // 只有当size是一个constexpr函数时才是一条正确的声明语句

  /**
   * 字面值类型：常量表达式的值需要在编译时就得到计算，因此对声明constexpr时用到的类型必须有所限制。
   * 因为这些类型一般比较简单，值也显而易见、容易得到，就把它们成为"字面值类型"（literal type）。
   * 目前接触过的数据类型中，算数类型、引用、指针都属于字面值类型。
   * 自定义Sales_item、IO库、string 类型则不属于字面值类型，也就不能被定义为constexpr。
   */

  /**
   * 指针和constexpr：在constexpr声明中如果定义了一个指针，限定符constexpr仅对指针有效，与指针所指的对象无关。
   * 如下所示：constexpr把它所定义的对象置为了顶层const。
   */
  const int *p = nullptr;       // p是一个指向整数常量的指针，可以修改p指向的对象，但不能修改对象的值
  constexpr int *q = nullptr;   // q是一个指向整数的常量指针，不能修改p指向的对象，但能修改对象的值

  /**
   * 与其他常量指针类似，constexpr既可以指向常量也可以指向非常量。
   *
   * 总的来说，使用const的地方就可以用constexpr，但要注意最后一个范例的区别
   */

  return 0;
}
<|endoftext|>