<commit_before>/*
 * Copyright (C) 2015-2018 Dubalu LLC. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "raft.h"

#ifdef XAPIAND_CLUSTERING

#include "endpoint.h"
#include "ignore_unused.h"
#include "log.h"
#include "manager.h"

Raft::Raft(const std::shared_ptr<XapiandManager>& manager_, ev::loop_ref* ev_loop_, unsigned int ev_flags_, int port_, const std::string& group_)
	: BaseUDP(manager_, ev_loop_, ev_flags_, port_, "Raft", XAPIAND_RAFT_PROTOCOL_VERSION, group_),
	  term(0),
	  votes(0),
	  state(State::FOLLOWER),
	  number_servers(1),
	  leader_election_timeout(*ev_loop),
	  leader_heartbeat(*ev_loop),
	  reset_leader_election_timeout_async(*ev_loop),
	  reset_async(*ev_loop)
{
	leader_election_timeout.set<Raft, &Raft::leader_election_timeout_cb>(this);

	leader_heartbeat.set<Raft, &Raft::leader_heartbeat_cb>(this);

	start_leader_heartbeat_async.set<Raft, &Raft::start_leader_heartbeat_async_cb>(this);
	start_leader_heartbeat_async.start();
	L_EV("Start raft's async start leader heartbeat event");

	reset_leader_election_timeout_async.set<Raft, &Raft::reset_leader_election_timeout_async_cb>(this);
	reset_leader_election_timeout_async.start();
	L_EV("Start raft's async reset leader election timeout event");

	reset_async.set<Raft, &Raft::reset_async_cb>(this);
	reset_async.start();
	L_EV("Start raft's async reset event");

	L_OBJ("CREATED RAFT CONSENSUS");
}

Raft::~Raft()
{
	leader_election_timeout.stop();
	L_EV("Stop raft's leader election event");

	leader_heartbeat.stop();
	L_EV("Stop raft's leader heartbeat event");

	L_OBJ("DELETED RAFT CONSENSUS");
}

void
Raft::start()
{
	_reset_leader_election_timeout();

	L_RAFT("Raft was started!");
}

void
Raft::stop()
{
	leader_election_timeout.stop();
	L_EV("Stop raft's leader election event");

	leader_heartbeat.stop();
	L_EV("Stop raft's leader heartbeat event");

	state = State::FOLLOWER;
	number_servers = 1;

	L_RAFT("Raft was stopped!");
}

void
Raft::start_leader_heartbeat_async_cb(ev::async&, int revents)
{
	L_CALL("Raft::start_leader_heartbeat_async_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	_start_leader_heartbeat();
}

void
Raft::reset_leader_election_timeout_async_cb(ev::async&, int revents)
{
	L_CALL("Raft::reset_leader_election_timeout_async_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	_reset_leader_election_timeout();
}

void
Raft::reset_async_cb(ev::async&, int revents)
{
	L_CALL("Raft::reset_async_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	_reset();
}

void
Raft::_reset()
{
	leader_heartbeat.stop();
	L_EV("Stop raft's leader heartbeat event");

	state = State::FOLLOWER;

	_reset_leader_election_timeout();

	L_RAFT("Raft was restarted!");
}

void
Raft::leader_election_timeout_cb(ev::timer&, int revents)
{
	L_CALL("Raft::leader_election_timeout_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	L_EV_BEGIN("Raft::leader_election_timeout_cb:BEGIN");

	if (XapiandManager::manager->state != XapiandManager::State::READY) {
		L_EV_END("Raft::leader_election_timeout_cb:END");
		return;
	}

	auto local_node_ = local_node.load();
	L_RAFT_PROTO("Raft { Reg: %d; State: %d; Elec_t: %f; Term: %llu; #ser: %zu; Lead: %s }",
		local_node_->region, state, leader_election_timeout.repeat, term, number_servers, leader);

	if (state != State::LEADER) {
		state = State::CANDIDATE;
		++term;
		votes = 0;
		votedFor.clear();
		send_message(Message::REQUEST_VOTE, local_node_->serialise() + serialise_length(term));
	}

	_reset_leader_election_timeout();

	L_EV_END("Raft::leader_election_timeout_cb:END");
}

void
Raft::_reset_leader_election_timeout()
{
	auto local_node_ = local_node.load();
	number_servers = XapiandManager::manager->get_nodes_by_region(local_node_->region) + 1;

	leader_election_timeout.repeat = random_real(LEADER_ELECTION_MIN, LEADER_ELECTION_MAX);
	leader_election_timeout.again();
	L_EV("Restart raft's leader election event (%g)", leader_election_timeout.repeat);
}

void
Raft::leader_heartbeat_cb(ev::timer&, int revents)
{
	L_CALL("Raft::leader_heartbeat_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	L_EV_BEGIN("Raft::leader_heartbeat_cb:BEGIN");

	if (XapiandManager::manager->state != XapiandManager::State::READY) {
		L_EV_END("Raft::leader_heartbeat_cb:END");
		return;
	}

	auto local_node_ = local_node.load();
	send_message(Message::HEARTBEAT_LEADER, local_node_->serialise());

	L_EV_END("Raft::leader_heartbeat_cb:END");
}

void
Raft::_start_leader_heartbeat()
{
	auto local_node_ = local_node.load();
	assert(leader == *local_node_);

	leader_heartbeat.repeat = random_real(HEARTBEAT_LEADER_MIN, HEARTBEAT_LEADER_MAX);
	leader_heartbeat.again();
	L_EV("Restart raft's leader heartbeat event (%g)", leader_heartbeat.repeat);

	send_message(Message::LEADER, local_node_->serialise() +
		serialise_length(number_servers) +
		serialise_length(term));
}

void
Raft::send_message(Message type, const std::string& message)
{
	if (type != Raft::Message::HEARTBEAT_LEADER) {
		L_RAFT("<< send_message(%s)", MessageNames(type));
	}
	L_RAFT_PROTO("message: %s", repr(message));
	BaseUDP::send_message(toUType(type), message);
}

std::string
Raft::getDescription() const noexcept
{
	return "UDP:" + std::to_string(port) + " (" + description + " v" + std::to_string(XAPIAND_RAFT_PROTOCOL_MAJOR_VERSION) + "." + std::to_string(XAPIAND_RAFT_PROTOCOL_MINOR_VERSION) + ")";
}

#endif
<commit_msg>Raft: Added L_CALLs<commit_after>/*
 * Copyright (C) 2015-2018 Dubalu LLC. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "raft.h"

#ifdef XAPIAND_CLUSTERING

#include "endpoint.h"
#include "ignore_unused.h"
#include "log.h"
#include "manager.h"

Raft::Raft(const std::shared_ptr<XapiandManager>& manager_, ev::loop_ref* ev_loop_, unsigned int ev_flags_, int port_, const std::string& group_)
	: BaseUDP(manager_, ev_loop_, ev_flags_, port_, "Raft", XAPIAND_RAFT_PROTOCOL_VERSION, group_),
	  term(0),
	  votes(0),
	  state(State::FOLLOWER),
	  number_servers(1),
	  leader_election_timeout(*ev_loop),
	  leader_heartbeat(*ev_loop),
	  reset_leader_election_timeout_async(*ev_loop),
	  reset_async(*ev_loop)
{
	leader_election_timeout.set<Raft, &Raft::leader_election_timeout_cb>(this);

	leader_heartbeat.set<Raft, &Raft::leader_heartbeat_cb>(this);

	start_leader_heartbeat_async.set<Raft, &Raft::start_leader_heartbeat_async_cb>(this);
	start_leader_heartbeat_async.start();
	L_EV("Start raft's async start leader heartbeat event");

	reset_leader_election_timeout_async.set<Raft, &Raft::reset_leader_election_timeout_async_cb>(this);
	reset_leader_election_timeout_async.start();
	L_EV("Start raft's async reset leader election timeout event");

	reset_async.set<Raft, &Raft::reset_async_cb>(this);
	reset_async.start();
	L_EV("Start raft's async reset event");

	L_OBJ("CREATED RAFT CONSENSUS");
}

Raft::~Raft()
{
	leader_election_timeout.stop();
	L_EV("Stop raft's leader election event");

	leader_heartbeat.stop();
	L_EV("Stop raft's leader heartbeat event");

	L_OBJ("DELETED RAFT CONSENSUS");
}

void
Raft::start()
{
	L_CALL("Raft::start()");

	_reset_leader_election_timeout();

	L_RAFT("Raft was started!");
}

void
Raft::stop()
{
	L_CALL("Raft::stop()");

	leader_election_timeout.stop();
	L_EV("Stop raft's leader election event");

	leader_heartbeat.stop();
	L_EV("Stop raft's leader heartbeat event");

	state = State::FOLLOWER;
	number_servers = 1;

	L_RAFT("Raft was stopped!");
}

void
Raft::start_leader_heartbeat_async_cb(ev::async&, int revents)
{
	L_CALL("Raft::start_leader_heartbeat_async_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	_start_leader_heartbeat();
}

void
Raft::reset_leader_election_timeout_async_cb(ev::async&, int revents)
{
	L_CALL("Raft::reset_leader_election_timeout_async_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	_reset_leader_election_timeout();
}

void
Raft::reset_async_cb(ev::async&, int revents)
{
	L_CALL("Raft::reset_async_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	_reset();
}

void
Raft::_reset()
{
	L_CALL("Raft::_reset()");

	leader_heartbeat.stop();
	L_EV("Stop raft's leader heartbeat event");

	state = State::FOLLOWER;

	_reset_leader_election_timeout();

	L_RAFT("Raft was restarted!");
}

void
Raft::leader_election_timeout_cb(ev::timer&, int revents)
{
	L_CALL("Raft::leader_election_timeout_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	L_EV_BEGIN("Raft::leader_election_timeout_cb:BEGIN");

	if (XapiandManager::manager->state != XapiandManager::State::READY) {
		L_EV_END("Raft::leader_election_timeout_cb:END");
		return;
	}

	auto local_node_ = local_node.load();
	L_RAFT_PROTO("Raft { Reg: %d; State: %d; Elec_t: %f; Term: %llu; #ser: %zu; Lead: %s }",
		local_node_->region, state, leader_election_timeout.repeat, term, number_servers, leader);

	if (state != State::LEADER) {
		state = State::CANDIDATE;
		++term;
		votes = 0;
		votedFor.clear();
		send_message(Message::REQUEST_VOTE, local_node_->serialise() + serialise_length(term));
	}

	_reset_leader_election_timeout();

	L_EV_END("Raft::leader_election_timeout_cb:END");
}

void
Raft::_reset_leader_election_timeout()
{
	L_CALL("Raft::_reset_leader_election_timeout()");

	auto local_node_ = local_node.load();
	number_servers = XapiandManager::manager->get_nodes_by_region(local_node_->region) + 1;

	leader_election_timeout.repeat = random_real(LEADER_ELECTION_MIN, LEADER_ELECTION_MAX);
	leader_election_timeout.again();
	L_EV("Restart raft's leader election event (%g)", leader_election_timeout.repeat);
}

void
Raft::leader_heartbeat_cb(ev::timer&, int revents)
{
	L_CALL("Raft::leader_heartbeat_cb(<watcher>, 0x%x (%s))", revents, readable_revents(revents));

	ignore_unused(revents);

	L_EV_BEGIN("Raft::leader_heartbeat_cb:BEGIN");

	if (XapiandManager::manager->state != XapiandManager::State::READY) {
		L_EV_END("Raft::leader_heartbeat_cb:END");
		return;
	}

	auto local_node_ = local_node.load();
	send_message(Message::HEARTBEAT_LEADER, local_node_->serialise());

	L_EV_END("Raft::leader_heartbeat_cb:END");
}

void
Raft::_start_leader_heartbeat()
{
	L_CALL("Raft::_start_leader_heartbeat()");

	auto local_node_ = local_node.load();
	assert(leader == *local_node_);

	leader_heartbeat.repeat = random_real(HEARTBEAT_LEADER_MIN, HEARTBEAT_LEADER_MAX);
	leader_heartbeat.again();
	L_EV("Restart raft's leader heartbeat event (%g)", leader_heartbeat.repeat);

	send_message(Message::LEADER, local_node_->serialise() +
		serialise_length(number_servers) +
		serialise_length(term));
}

void
Raft::send_message(Message type, const std::string& message)
{
	L_CALL("Raft::send_message(%s, <message>)", MessageNames(type));

	if (type != Raft::Message::HEARTBEAT_LEADER) {
		L_RAFT("<< send_message(%s)", MessageNames(type));
	}
	L_RAFT_PROTO("message: %s", repr(message));
	BaseUDP::send_message(toUType(type), message);
}

std::string
Raft::getDescription() const noexcept
{
	L_CALL("Raft::getDescription()");

	return "UDP:" + std::to_string(port) + " (" + description + " v" + std::to_string(XAPIAND_RAFT_PROTOCOL_MAJOR_VERSION) + "." + std::to_string(XAPIAND_RAFT_PROTOCOL_MINOR_VERSION) + ")";
}

#endif
<|endoftext|>


<commit_before>#include <shadow_pass.hpp>

#ifdef BUILD_DEFERRED_RENDERER

#include <chrono>
#include <list>
#include <random>

namespace harmont {

shadow_pass::shadow_pass(uint32_t resolution, uint32_t sample_count) : res_(resolution), sample_count_(sample_count) {
    tex_ = texture::texture_2d<float>(res_, res_, 1);
    dummy_tex_ = texture::depth_texture<float>(res_, res_, GL_DEPTH_COMPONENT32F);
    vertex_shader::ptr   vert = vertex_shader::from_file(std::string(GLSL_PREFIX)+"shadow.vert");
    fragment_shader::ptr frag = fragment_shader::from_file(std::string(GLSL_PREFIX)+"shadow.frag");
    pass_ = std::make_shared<render_pass>(vert, frag, render_pass::textures({tex_}), dummy_tex_);
    //tex_ = texture::depth_texture<float>(res_, res_);
    //pass_ = std::make_shared<render_pass>(vertex_shader::from_file(vertex_shader), fragment_shader::from_file(fragment_shader), render_pass::textures(), tex_);
    disk_ = poisson_disk_(sample_count_, 1.f);
}

shadow_pass::~shadow_pass() {
}

texture::ptr shadow_pass::shadow_texture() {
    return tex_;
}

texture::const_ptr shadow_pass::shadow_texture() const {
    return tex_;
}

Eigen::Matrix4f& shadow_pass::transform() {
    return mat_;
}

const Eigen::Matrix4f& shadow_pass::transform() const {
    return mat_;
}

std::vector<float>& shadow_pass::poisson_disk() {
    return disk_;
}

const std::vector<float>& shadow_pass::poisson_disk() const {
    return disk_;
}

void shadow_pass::render(const render_callback_t& render_callback, int width, int height, bool clipping, float clipping_z) {
    pass_->set_uniform("shadow_matrix", mat_);
    glViewport(0, 0, res_, res_);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    //glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    if (clipping) {
        pass_->set_uniform("clip_normal", std::vector<float>({0.f, 0.f, 1.f}));
        pass_->set_uniform("clip_distance", clipping_z);
        glEnable(GL_CLIP_DISTANCE0);
    }
    //glPolygonOffset(-1.1, 4.0);
    //glEnable(GL_POLYGON_OFFSET_FILL);
    pass_->render(render_callback);
    glDisable(GL_POLYGON_OFFSET_FILL);
    if (clipping) {
        glDisable(GL_CLIP_DISTANCE0);
    }
    //glPolygonOffset(0, 0);
    //glDisable(GL_POLYGON_OFFSET_FILL);
    //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glViewport(0, 0, width, height);
    glClearColor(0.0, 0.0, 0.0, 1.0);
}

void shadow_pass::update(const bounding_box_t& bbox, const Eigen::Vector3f& light_dir) {
    const float margin = 0.01f;
    Eigen::Vector3f center = bbox.center();
    float radius = (bbox.max() - center).norm();
    Eigen::Vector3f forward = light_dir;
    Eigen::Vector3f up = Eigen::Vector3f::UnitZ();
    if (fabs(up.dot(forward)) + Eigen::NumTraits<float>::dummy_precision() > 1.f) {
        up = Eigen::Vector3f::UnitY();
    }
    up -= up.dot(forward) * forward;
    up.normalize();
    Eigen::Vector3f right = up.cross(forward);

    mat_.block<1,3>(0, 0) = right.transpose();
    mat_.block<1,3>(1, 0) = up.transpose();
    mat_.block<1,3>(2, 0) = forward.transpose();
    Eigen::Vector3f light_pos = center + (radius + margin) * light_dir;
    mat_.block<3,1>(0, 3) = mat_.block<3,3>(0, 0) * (-light_pos);
    mat_.row(3) = Eigen::RowVector4f(0.f, 0.f, 0.f, 1.f);
    mat_ = ortho(-radius, radius, -radius, radius, margin, 2.f * radius + 2.f * margin) * mat_;
}

void shadow_pass::render_(shader_program::ptr program, uint32_t num_indices) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, nullptr);
}

std::vector<float> shadow_pass::poisson_disk_(uint32_t n, float radius, uint32_t k) {
    if (n == 0) return std::vector<float>();

    auto seed = std::chrono::system_clock::now().time_since_epoch().count();
    std::default_random_engine generator(seed);
    std::uniform_real_distribution<float> float_dist;
    auto float_rng = std::bind(float_dist, generator);

    float r2 = radius * radius;
    Eigen::Vector2f p = gen_point_(float_rng, radius, 0.f);

    std::vector<Eigen::Vector2f> points(1, p);
    std::list<Eigen::Vector2f>   active(1, p);

    Eigen::Vector2f avg_point = p;
    uint32_t point_count = 0;
    while (points.size() < n && active.size()) {
        std::uniform_int_distribution<int> int_dist(0, active.size() - 1);
        std::list<Eigen::Vector2f>::iterator iter = active.begin();
        int random_int = int_dist(generator);
        std::advance(iter, random_int);
        const Eigen::Vector2f& s = *iter;

        bool valid = true;
        for (uint32_t j = 0; j < k; ++j) {
            Eigen::Vector2f np = gen_point_(float_rng, radius, radius, s);
            valid = true;
            for (uint32_t pt = 0; pt < points.size(); ++pt) {
                const Eigen::Vector2f& point = points[pt];
                float square_dist = (point - np).squaredNorm();
                if (square_dist < r2) {
                    valid = false;
                    break;
                }
            }
            if (valid) {
                if (point_count > 1) avg_point *= static_cast<float>(point_count);
                avg_point += np;
                avg_point /= static_cast<float>(++point_count);
                points.push_back(np);
                active.push_back(np);
                break;
            }
        }

        if (!valid) {
            active.erase(iter);
        }
    }

    std::vector<float> result(points.size() * 2);
    for (uint32_t i = 0; i < points.size(); ++i) {
        Eigen::Vector2f local = points[i] - avg_point;
        result[2*i + 0] = local[0];
        result[2*i + 1] = local[1];
    }

    return result;
}

#endif // BUILD_DEFERRED_RENDERER

} // harmont
<commit_msg>define fix for shadow pass<commit_after>#include <shadow_pass.hpp>

#ifdef BUILD_DEFERRED_RENDERER

#include <chrono>
#include <list>
#include <random>

namespace harmont {

shadow_pass::shadow_pass(uint32_t resolution, uint32_t sample_count) : res_(resolution), sample_count_(sample_count) {
    tex_ = texture::texture_2d<float>(res_, res_, 1);
    dummy_tex_ = texture::depth_texture<float>(res_, res_, GL_DEPTH_COMPONENT32F);
    vertex_shader::ptr   vert = vertex_shader::from_file(std::string(GLSL_PREFIX)+"shadow.vert");
    fragment_shader::ptr frag = fragment_shader::from_file(std::string(GLSL_PREFIX)+"shadow.frag");
    pass_ = std::make_shared<render_pass>(vert, frag, render_pass::textures({tex_}), dummy_tex_);
    //tex_ = texture::depth_texture<float>(res_, res_);
    //pass_ = std::make_shared<render_pass>(vertex_shader::from_file(vertex_shader), fragment_shader::from_file(fragment_shader), render_pass::textures(), tex_);
    disk_ = poisson_disk_(sample_count_, 1.f);
}

shadow_pass::~shadow_pass() {
}

texture::ptr shadow_pass::shadow_texture() {
    return tex_;
}

texture::const_ptr shadow_pass::shadow_texture() const {
    return tex_;
}

Eigen::Matrix4f& shadow_pass::transform() {
    return mat_;
}

const Eigen::Matrix4f& shadow_pass::transform() const {
    return mat_;
}

std::vector<float>& shadow_pass::poisson_disk() {
    return disk_;
}

const std::vector<float>& shadow_pass::poisson_disk() const {
    return disk_;
}

void shadow_pass::render(const render_callback_t& render_callback, int width, int height, bool clipping, float clipping_z) {
    pass_->set_uniform("shadow_matrix", mat_);
    glViewport(0, 0, res_, res_);
    glClearColor(1.0, 1.0, 1.0, 1.0);
    //glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    if (clipping) {
        pass_->set_uniform("clip_normal", std::vector<float>({0.f, 0.f, 1.f}));
        pass_->set_uniform("clip_distance", clipping_z);
        glEnable(GL_CLIP_DISTANCE0);
    }
    //glPolygonOffset(-1.1, 4.0);
    //glEnable(GL_POLYGON_OFFSET_FILL);
    pass_->render(render_callback);
    glDisable(GL_POLYGON_OFFSET_FILL);
    if (clipping) {
        glDisable(GL_CLIP_DISTANCE0);
    }
    //glPolygonOffset(0, 0);
    //glDisable(GL_POLYGON_OFFSET_FILL);
    //glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glViewport(0, 0, width, height);
    glClearColor(0.0, 0.0, 0.0, 1.0);
}

void shadow_pass::update(const bounding_box_t& bbox, const Eigen::Vector3f& light_dir) {
    const float margin = 0.01f;
    Eigen::Vector3f center = bbox.center();
    float radius = (bbox.max() - center).norm();
    Eigen::Vector3f forward = light_dir;
    Eigen::Vector3f up = Eigen::Vector3f::UnitZ();
    if (fabs(up.dot(forward)) + Eigen::NumTraits<float>::dummy_precision() > 1.f) {
        up = Eigen::Vector3f::UnitY();
    }
    up -= up.dot(forward) * forward;
    up.normalize();
    Eigen::Vector3f right = up.cross(forward);

    mat_.block<1,3>(0, 0) = right.transpose();
    mat_.block<1,3>(1, 0) = up.transpose();
    mat_.block<1,3>(2, 0) = forward.transpose();
    Eigen::Vector3f light_pos = center + (radius + margin) * light_dir;
    mat_.block<3,1>(0, 3) = mat_.block<3,3>(0, 0) * (-light_pos);
    mat_.row(3) = Eigen::RowVector4f(0.f, 0.f, 0.f, 1.f);
    mat_ = ortho(-radius, radius, -radius, radius, margin, 2.f * radius + 2.f * margin) * mat_;
}

void shadow_pass::render_(shader_program::ptr program, uint32_t num_indices) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, nullptr);
}

std::vector<float> shadow_pass::poisson_disk_(uint32_t n, float radius, uint32_t k) {
    if (n == 0) return std::vector<float>();

    auto seed = std::chrono::system_clock::now().time_since_epoch().count();
    std::default_random_engine generator(seed);
    std::uniform_real_distribution<float> float_dist;
    auto float_rng = std::bind(float_dist, generator);

    float r2 = radius * radius;
    Eigen::Vector2f p = gen_point_(float_rng, radius, 0.f);

    std::vector<Eigen::Vector2f> points(1, p);
    std::list<Eigen::Vector2f>   active(1, p);

    Eigen::Vector2f avg_point = p;
    uint32_t point_count = 0;
    while (points.size() < n && active.size()) {
        std::uniform_int_distribution<int> int_dist(0, active.size() - 1);
        std::list<Eigen::Vector2f>::iterator iter = active.begin();
        int random_int = int_dist(generator);
        std::advance(iter, random_int);
        const Eigen::Vector2f& s = *iter;

        bool valid = true;
        for (uint32_t j = 0; j < k; ++j) {
            Eigen::Vector2f np = gen_point_(float_rng, radius, radius, s);
            valid = true;
            for (uint32_t pt = 0; pt < points.size(); ++pt) {
                const Eigen::Vector2f& point = points[pt];
                float square_dist = (point - np).squaredNorm();
                if (square_dist < r2) {
                    valid = false;
                    break;
                }
            }
            if (valid) {
                if (point_count > 1) avg_point *= static_cast<float>(point_count);
                avg_point += np;
                avg_point /= static_cast<float>(++point_count);
                points.push_back(np);
                active.push_back(np);
                break;
            }
        }

        if (!valid) {
            active.erase(iter);
        }
    }

    std::vector<float> result(points.size() * 2);
    for (uint32_t i = 0; i < points.size(); ++i) {
        Eigen::Vector2f local = points[i] - avg_point;
        result[2*i + 0] = local[0];
        result[2*i + 1] = local[1];
    }

    return result;
}

} // harmont

#endif // BUILD_DEFERRED_RENDERER
<|endoftext|>


<commit_before>#include <gtest/gtest.h>

TEST(SimpleTest, FirstTest)
{
  EXPECT_EQ(1, 1);
  EXPECT_NE(false, true);
}
<commit_msg>add more assertion samples<commit_after>#include <gtest/gtest.h>

TEST(SimpleTest, FirstTest)
{
  // ASSERT_* can be replaced by EXPECT_*.
  // ASSERT_* yields fatal failure and EXPECT_* yields nonfatal failure.

  ASSERT_TRUE(true);
  ASSERT_FALSE(false);

  ASSERT_EQ(42, 42);
  ASSERT_NE(false, true);
  ASSERT_LT(1, 2);
  ASSERT_LE(1, 1);
  EXPECT_LE(1, 2);
  ASSERT_GT(1, 0);
  ASSERT_GE(1, 0);
  ASSERT_GE(0, 0);

  ASSERT_STREQ("foo", "foo");
  ASSERT_STRNE("foo", "Foo");
  ASSERT_STRCASEEQ("foo", "FOO");
  ASSERT_STRCASENE("foo", "bar");
  ASSERT_STRCASENE("", nullptr);
}
<|endoftext|>


<commit_before>#include "Queue.hpp"
#include "gtest/gtest.h"
#include <iostream>
#include <queue>
#include <thread>
#include <random>
namespace {
	const int MAX = 1000000;
	const int REPEATE = 10000;
	class thread_guard {
	public:
		explicit thread_guard(std::thread &t):mythread{t}{}
		~thread_guard() {if (mythread.joinable()) mythread.join();}
		thread_guard(const thread_guard &)=delete;
		thread_guard& operator=(const std::thread &)=delete;
	private:
		std::thread &mythread;
	};
}

TEST(BasicTest, InsertAndDel) {
	que::PriorityQueue<int, std::vector<int>, std::greater<int>> pq;
	std::priority_queue<int, std::vector<int>, std::greater<int>> spq;
	std::default_random_engine gene;
	std::uniform_int_distribution<int> dist{1, MAX};
	auto rand = std::bind(dist, gene);
	for (int i = 0; i < REPEATE; i++) {
		int tmp{rand()};
		pq.push(tmp); spq.push(tmp);
	}
	while (!spq.empty()) {
		ASSERT_EQ(spq.top(), pq.top());
		spq.pop(); pq.pop();
	}
}

<commit_msg>add copy and assignment test<commit_after>#include "Queue.hpp"
#include "gtest/gtest.h"
#include <iostream>
#include <queue>
#include <thread>
#include <random>
namespace {
	const int MAX = 1000000;
	const int REPEATE = 10000;
	class Scope_thread {
	public:
		// Only accept rvalue reference
		explicit Scope_thread(std::thread t):mythread{std::move(t)}{}
		~Scope_thread() {mythread.join();}
		Scope_thread(const Scope_thread&)=delete;
		Scope_thread& operator=(const Scope_thread)=delete;
	private:
		std::thread mythread;
	};
}

TEST(BasicTest, InsertAndDel) {
	que::PriorityQueue<int, std::vector<int>, std::greater<int>> pq;
	std::priority_queue<int, std::vector<int>, std::greater<int>> spq;
	std::default_random_engine gene;
	std::uniform_int_distribution<int> dist{1, MAX};
	auto rand = std::bind(dist, gene);
	for (int i = 0; i < REPEATE; i++) {
		int tmp{rand()};
		pq.push(tmp); spq.push(tmp);
	}
	while (spq.size() > REPEATE / 2) {
		ASSERT_EQ(spq.top(), pq.top());
		spq.pop(); pq.pop();
	}
	for (int i = 0; i < REPEATE; i++) {
		int tmp{rand()};
		pq.push(tmp); spq.push(tmp);
		spq.pop(); pq.pop();
	}
	while (!spq.empty()) {
		ASSERT_EQ(spq.top(), pq.top());
		spq.pop(); pq.pop();
	}
}

TEST(BasicTest, CopyAndAssign) {
	que::PriorityQueue<int> pq;
	std::vector<int> v;
	for (auto i = 0; i < REPEATE; i++) {
		pq.push(i); v.push_back(i);
	}
	que::PriorityQueue<int> copyed{pq};
	que::PriorityQueue<int> assigned; assigned = pq;
	que::PriorityQueue<int> rvalue_copyed
		{que::PriorityQueue<int>{v.begin(), v.end()}};
	que::PriorityQueue<int> rvalue_assigned
		= que::PriorityQueue<int>{v.begin(), v.end()};
	while (!copyed.empty()) {
		ASSERT_EQ(copyed.top(), assigned.top());
		ASSERT_EQ(copyed.top(), rvalue_copyed.top());
		ASSERT_EQ(copyed.top(), rvalue_assigned.top());
		copyed.pop(); assigned.pop();
		rvalue_copyed.pop(); rvalue_assigned.pop();
	}
}

TEST(ThreadTest, Insert) {
	que::PriorityQueue<int> pq;
	Scope_thread p{std::thread{[&](){pq.push(10);}}};
}
<|endoftext|>


<commit_before>#include "parser.hpp"

#include <sstream>

#include <ting/debug.hpp>

#include "Exc.hpp"

using namespace stob;

void Parser::AppendCharToString(ting::u8 c){
	*this->p = c;
	++this->p;
	if(this->p == this->buf->End()){
		ting::Array<ting::u8> a(this->buf->Size() * 2);
		memcpy(a.Begin(), this->buf->Begin(), this->buf->SizeInBytes());

		this->p = a.Begin() + this->buf->Size();

		this->arrayBuf = a;
		this->buf = &this->arrayBuf; //set reference
	}
}

void Parser::HandleLeftCurlyBracket(ParseListener& listener){
	if(this->nestingLevel == unsigned(-1)){
		throw stob::Exc("Malformed STOB document. Nesting level is too high.");
	}
	if(!this->stringParsed){
		throw stob::Exc("Malformed STOB document. Curly braces without preceding string declaration encountered.");
	}
	++this->nestingLevel;
	this->stringParsed = false;
	listener.OnChildrenParseStarted();
}

void Parser::HandleRightCurlyBracket(ParseListener& listener){
	if(this->nestingLevel == 0){
		std::stringstream ss;
		ss << "Malformed STOB document. Unexpected '}' at line: ";
		ss << this->curLine;
		throw stob::Exc(ss.str());
	}
	--this->nestingLevel;
	listener.OnChildrenParseFinished();
}

void Parser::HandleStringEnd(ParseListener& listener){
	listener.OnStringParsed(reinterpret_cast<char*>(this->buf->Begin()), this->p - this->buf->Begin());
	this->arrayBuf.Reset();
	this->buf = &this->staticBuf;
	this->p = this->buf->Begin();
	this->stringParsed = true;
	this->state = IDLE;
}

void Parser::ParseChar(ting::u8 c, ParseListener& listener){
	switch(this->state){
		case IDLE:
			switch(c){
				case '"':
					this->state = QUOTED_STRING;
					break;
				case '{':
					this->HandleLeftCurlyBracket(listener);
					break;
				case '}':
					this->HandleRightCurlyBracket(listener);
					break;
				case '\n':
					++this->curLine;
					break;
				case '\r':
				case ' ':
				case '\t':
					//ignore
					break;
				default:
					this->state = UNQUOTED_STRING;
					this->AppendCharToString(c);
					break;
			}
			break;
		case UNQUOTED_STRING:
			switch(c){
				case '\n':
					++this->curLine;
				case ' ':
				case '\r':
				case '\t':
				case '{':
				case '}':
					this->HandleStringEnd(listener);

					if(c == '{'){
						this->HandleLeftCurlyBracket(listener);
					}else if(c == '}'){
						this->HandleRightCurlyBracket(listener);
					}

					return;
//					break;
				default:
					this->AppendCharToString(c);
					break;
			}
			break;
		case QUOTED_STRING:
			this->AppendCharToString(c);
			break;
		default:
			ASSERT(false)
			break;
	}
}

void Parser::PreParseChar(ting::u8 c, ParseListener& listener){
	if(this->prevChar != 0){
		if(this->prevChar == '/'){//possible comment sequence
			if(c == '/'){
				this->commentState = LINE_COMMENT;
			}else if(c == '*'){
				this->commentState = MULTILINE_COMMENT;
			}else{
				this->ParseChar('/', listener);

				ASSERT(this->state != QUOTED_STRING)
				this->ParseChar(c, listener);
			}
		}else{
			switch(this->state){
				case IDLE:
				case UNQUOTED_STRING:
					ASSERT(false)
					break;
				case QUOTED_STRING:
					ASSERT(this->prevChar == '\\')//escape sequence
					switch(c){
						case '\\'://backslash
							this->ParseChar('\\', listener);
							break;
						case '/'://slash
							this->ParseChar('/', listener);
							break;
						case '"':
							this->ParseChar('"', listener);
							break;
						case 'n':
							this->ParseChar('\n', listener);
							break;
						case 'r':
							this->ParseChar('\r', listener);
							break;
						case 't':
							this->ParseChar('\t', listener);
							break;
						default:
							{
								std::stringstream ss;
								ss << "Malformed document. Unknown escape sequence (\\" << c << ") on line: ";
								ss << this->curLine;
								throw stob::Exc(ss.str());
							}
							break;
					}
					break;
				default:
					ASSERT(false)
					break;
			}
		}
		this->prevChar = 0;
	}else{//~if(this->prevChar == 0)
		if(c == '/' && this->state != QUOTED_STRING){//possible comment sequence
			this->prevChar = '/';
		}else{
			switch(this->state){
				case QUOTED_STRING:
					switch(c){
						case '\\': //escape sequence
							this->prevChar = '\\';
							break;
						case '"':
//								TRACE(<< "qsp = " << std::string(reinterpret_cast<char*>(this->buf->Begin()), 11) << std::endl)
							this->HandleStringEnd(listener);
							break;
						case '\n':
							++this->curLine;
						case '\r':
						case '\t':
							//ignore
							break;
						default:
							this->ParseChar(c, listener);
							break;
					}
					break;
				case UNQUOTED_STRING:
				case IDLE:
					this->ParseChar(c, listener);
					break;
				default:
					ASSERT(false)
					break;
			}
		}
	}
}

void Parser::ParseDataChunk(const ting::Buffer<ting::u8>& chunk, ParseListener& listener){
	for(const ting::u8* s = chunk.Begin(); s != chunk.End(); ++s){
//		TRACE(<< "Parser::ParseDataChunk(): *s = " << (*s) << std::endl)

		//skip comments if needed
		if(this->commentState != NO_COMMENT){
			switch(this->commentState){
				case LINE_COMMENT:
					for(; s != chunk.End(); ++s){
						if(*s == '\n'){
							++this->curLine;
							this->commentState = NO_COMMENT;
							break;
						}
					}
					break;//~switch
				case MULTILINE_COMMENT:
					if(this->prevChar == '*'){
						this->prevChar = 0;
						if(*s == '/'){
							this->commentState = NO_COMMENT;
							break;//~switch()
						}
					}
					for(; s != chunk.End(); ++s){
						if(*s == '\n'){
							++this->curLine;
						}else if(*s == '*'){
							this->prevChar = '*';
							break;//~for()
						}
					}
					break;//~switch
				default:
					ASSERT(false)
					break;
			}
			continue;
		}

		this->PreParseChar(*s, listener);

	}//~for(s)
}

void Parser::EndOfData(ParseListener& listener){
	if(this->state != IDLE){
		//add new line at the end of data
		this->PreParseChar('\n', listener);
	}

	if(this->nestingLevel != 0 || this->state != IDLE){
		throw stob::Exc("Malformed stob document fed. After parsing all the data, the parser remained in the middle of some parsing task.");
	}

	this->Reset();
}

void stob::Parse(ting::fs::File& fi, ParseListener& listener){
	ting::fs::File::Guard fileGuard(fi, ting::fs::File::READ);

	stob::Parser parser;

	ting::StaticBuffer<ting::u8, 2048> buf; //2kb read buffer.

	size_t bytesRead;

	do{
		bytesRead = fi.Read(buf);

		parser.ParseDataChunk(
				ting::Buffer<ting::u8>(buf.Begin(), bytesRead),
				listener
			);
	}while(bytesRead == buf.Size());

	parser.EndOfData(listener);
}
<commit_msg>refactoring<commit_after>#include "parser.hpp"

#include <sstream>

#include <ting/debug.hpp>

#include "Exc.hpp"

using namespace stob;

void Parser::AppendCharToString(ting::u8 c){
	*this->p = c;
	++this->p;
	if(this->p == this->buf->End()){
		ting::Array<ting::u8> a(this->buf->Size() * 2);
		memcpy(a.Begin(), this->buf->Begin(), this->buf->SizeInBytes());

		this->p = a.Begin() + this->buf->Size();

		this->arrayBuf = a;
		this->buf = &this->arrayBuf; //set reference
	}
}

void Parser::HandleLeftCurlyBracket(ParseListener& listener){
	if(this->nestingLevel == unsigned(-1)){
		throw stob::Exc("Malformed STOB document. Nesting level is too high.");
	}
	if(!this->stringParsed){
		throw stob::Exc("Malformed STOB document. Curly braces without preceding string declaration encountered.");
	}
	++this->nestingLevel;
	this->stringParsed = false;
	listener.OnChildrenParseStarted();
}

void Parser::HandleRightCurlyBracket(ParseListener& listener){
	if(this->nestingLevel == 0){
		std::stringstream ss;
		ss << "Malformed STOB document. Unexpected '}' at line: ";
		ss << this->curLine;
		throw stob::Exc(ss.str());
	}
	--this->nestingLevel;
	listener.OnChildrenParseFinished();
}

void Parser::HandleStringEnd(ParseListener& listener){
	listener.OnStringParsed(reinterpret_cast<char*>(this->buf->Begin()), this->p - this->buf->Begin());
	this->arrayBuf.Reset();
	this->buf = &this->staticBuf;
	this->p = this->buf->Begin();
	this->stringParsed = true;
	this->state = IDLE;
}

void Parser::ParseChar(ting::u8 c, ParseListener& listener){
	switch(this->state){
		case IDLE:
			switch(c){
				case '"':
					this->state = QUOTED_STRING;
					break;
				case '{':
					this->HandleLeftCurlyBracket(listener);
					break;
				case '}':
					this->HandleRightCurlyBracket(listener);
					break;
				case '\n':
					++this->curLine;
					break;
				case '\r':
				case ' ':
				case '\t':
					//ignore
					break;
				default:
					this->state = UNQUOTED_STRING;
					this->AppendCharToString(c);
					break;
			}
			break;
		case UNQUOTED_STRING:
			switch(c){
				case '\n':
					++this->curLine;
				case ' ':
				case '\r':
				case '\t':
				case '{':
				case '}':
					this->HandleStringEnd(listener);

					if(c == '{'){
						this->HandleLeftCurlyBracket(listener);
					}else if(c == '}'){
						this->HandleRightCurlyBracket(listener);
					}

					return;
//					break;
				default:
					this->AppendCharToString(c);
					break;
			}
			break;
		case QUOTED_STRING:
			this->AppendCharToString(c);
			break;
		default:
			ASSERT(false)
			break;
	}
}

void Parser::PreParseChar(ting::u8 c, ParseListener& listener){
	if(this->prevChar != 0){
		if(this->prevChar == '/'){//possible comment sequence
			if(c == '/'){
				this->commentState = LINE_COMMENT;
			}else if(c == '*'){
				this->commentState = MULTILINE_COMMENT;
			}else{
				this->ParseChar('/', listener);

				ASSERT(this->state != QUOTED_STRING)
				this->ParseChar(c, listener);
			}
		}else{
			switch(this->state){
				case IDLE:
				case UNQUOTED_STRING:
					ASSERT(false)
					break;
				case QUOTED_STRING:
					ASSERT(this->prevChar == '\\')//escape sequence
					switch(c){
						case '\\'://backslash
							this->ParseChar('\\', listener);
							break;
						case '/'://slash
							this->ParseChar('/', listener);
							break;
						case '"':
							this->ParseChar('"', listener);
							break;
						case 'n':
							this->ParseChar('\n', listener);
							break;
						case 'r':
							this->ParseChar('\r', listener);
							break;
						case 't':
							this->ParseChar('\t', listener);
							break;
						default:
							{
								std::stringstream ss;
								ss << "Malformed document. Unknown escape sequence (\\" << c << ") on line: ";
								ss << this->curLine;
								throw stob::Exc(ss.str());
							}
							break;
					}
					break;
				default:
					ASSERT(false)
					break;
			}
		}
		this->prevChar = 0;
	}else{//~if(this->prevChar != 0)
		switch(this->state){
			case QUOTED_STRING:
				switch(c){
					case '\\': //escape sequence
						this->prevChar = '\\';
						break;
					case '"':
//								TRACE(<< "qsp = " << std::string(reinterpret_cast<char*>(this->buf->Begin()), 11) << std::endl)
						this->HandleStringEnd(listener);
						break;
					case '\n':
						++this->curLine;
					case '\r':
					case '\t':
						//ignore
						break;
					default:
						this->ParseChar(c, listener);
						break;
				}
				break;
			case UNQUOTED_STRING:
			case IDLE:
				if(c == '/'){//possible comment sequence
					this->prevChar = '/';
				}else{
					this->ParseChar(c, listener);
				}
				break;
			default:
				ASSERT(false)
				break;
		}//~switch
	}
}

void Parser::ParseDataChunk(const ting::Buffer<ting::u8>& chunk, ParseListener& listener){
	for(const ting::u8* s = chunk.Begin(); s != chunk.End(); ++s){
//		TRACE(<< "Parser::ParseDataChunk(): *s = " << (*s) << std::endl)

		//skip comments if needed
		if(this->commentState != NO_COMMENT){
			switch(this->commentState){
				case LINE_COMMENT:
					for(; s != chunk.End(); ++s){
						if(*s == '\n'){
							++this->curLine;
							this->commentState = NO_COMMENT;
							break;
						}
					}
					break;//~switch
				case MULTILINE_COMMENT:
					if(this->prevChar == '*'){
						this->prevChar = 0;
						if(*s == '/'){
							this->commentState = NO_COMMENT;
							break;//~switch()
						}
					}
					for(; s != chunk.End(); ++s){
						if(*s == '\n'){
							++this->curLine;
						}else if(*s == '*'){
							this->prevChar = '*';
							break;//~for()
						}
					}
					break;//~switch
				default:
					ASSERT(false)
					break;
			}
			continue;
		}

		this->PreParseChar(*s, listener);

	}//~for(s)
}

void Parser::EndOfData(ParseListener& listener){
	if(this->state != IDLE){
		//add new line at the end of data
		this->PreParseChar('\n', listener);
	}

	if(this->nestingLevel != 0 || this->state != IDLE){
		throw stob::Exc("Malformed stob document fed. After parsing all the data, the parser remained in the middle of some parsing task.");
	}

	this->Reset();
}

void stob::Parse(ting::fs::File& fi, ParseListener& listener){
	ting::fs::File::Guard fileGuard(fi, ting::fs::File::READ);

	stob::Parser parser;

	ting::StaticBuffer<ting::u8, 2048> buf; //2kb read buffer.

	size_t bytesRead;

	do{
		bytesRead = fi.Read(buf);

		parser.ParseDataChunk(
				ting::Buffer<ting::u8>(buf.Begin(), bytesRead),
				listener
			);
	}while(bytesRead == buf.Size());

	parser.EndOfData(listener);
}
<|endoftext|>


<commit_before>/*
 * Objects in stock.  May be used for connection pooling.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "Stock.hxx"
#include "Class.hxx"
#include "Stats.hxx"
#include "Item.hxx"
#include "GetHandler.hxx"
#include "async.hxx"
#include "pool.hxx"
#include "event/TimerEvent.hxx"
#include "event/DeferEvent.hxx"
#include "event/Callback.hxx"
#include "util/Cast.hxx"

#include <daemon/log.h>

#include <glib.h>

#include <assert.h>

struct Stock {
    struct pool &pool;
    const StockClass &cls;
    void *const class_ctx;
    const char *const uri;

    /**
     * The maximum number of items in this stock.  If any more items
     * are requested, they are put into the #waiting list, which gets
     * checked as soon as stock_put() is called.
     */
    const unsigned limit;

    /**
     * The maximum number of permanent idle items.  If there are more
     * than that, a timer will incrementally kill excess items.
     */
    const unsigned max_idle;

    StockHandler *const handler;

    /**
     * This event is used to move the "retry waiting" code out of the
     * current stack, to invoke the handler method in a safe
     * environment.
     */
    DeferEvent retry_event;

    /**
     * This event is used to move the "empty" check out of the current
     * stack, to invoke the handler method in a safe environment.
     */
    DeferEvent empty_event;

    TimerEvent cleanup_event;
    TimerEvent clear_event;

    typedef boost::intrusive::list<StockItem,
                                   boost::intrusive::constant_time_size<true>> ItemList;

    ItemList idle;

    ItemList busy;

    unsigned num_create = 0;

    struct Waiting
        : boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {

        Stock &stock;

        struct async_operation operation;

        struct pool &pool;
        void *const info;

        StockGetHandler &handler;

        struct async_operation_ref &async_ref;

        Waiting(Stock &_stock, struct pool &_pool, void *_info,
                StockGetHandler &_handler,
                struct async_operation_ref &_async_ref)
            :stock(_stock), pool(_pool), info(_info),
             handler(_handler),
             async_ref(_async_ref) {
            operation.Init2<Waiting>();
            pool_ref(&pool);
            async_ref.Set(operation);
        }

        void Destroy() {
            DeleteUnrefPool(pool, this);
        }

        void Abort();
    };

    typedef boost::intrusive::list<Waiting,
                                   boost::intrusive::constant_time_size<false>> WaitingList;

    WaitingList waiting;

    bool may_clear = false;

    Stock(struct pool &_pool, const StockClass &cls, void *class_ctx,
          const char *uri, unsigned limit, unsigned max_idle,
          StockHandler *handler);

    ~Stock();

    void Destroy() {
        DeleteUnrefPool(pool, this);
    }

    gcc_pure
    bool IsEmpty() const {
        return idle.empty() && busy.empty() && num_create == 0;
    }

    /**
     * Check if the stock has become empty, and invoke the handler.
     */
    void CheckEmpty();
    void ScheduleCheckEmpty();

    void DestroyItem(StockItem &item);

    void ScheduleClear() {
        static constexpr struct timeval tv = { .tv_sec = 60, .tv_usec = 0 };
        clear_event.Add(tv);
    }

    void ClearIdle();

    bool GetIdle(StockGetHandler &handler);
    void GetCreate(struct pool &caller_pool, void *info,
                   StockGetHandler &get_handler,
                   struct async_operation_ref &async_ref);

    /**
     * Retry the waiting requests.  This is called after the number of
     * busy items was reduced.
     */
    void RetryWaiting();
    void ScheduleRetryWaiting();

    void ScheduleCleanup() {
        static constexpr struct timeval tv = { .tv_sec = 20, .tv_usec = 0 };
        cleanup_event.Add(tv);
    }

    void UnscheduleCleanup() {
        cleanup_event.Cancel();
    }
    void CleanupEventCallback();
    void ClearEventCallback();
};

/*
 * The "empty()" handler method.
 *
 */

void
Stock::CheckEmpty()
{
    if (IsEmpty() && handler != nullptr)
        handler->OnStockEmpty(*this, uri);
}

void
Stock::ScheduleCheckEmpty()
{
    if (IsEmpty() && handler != nullptr)
        empty_event.Add();
}

/*
 * cleanup
 *
 */

void
Stock::CleanupEventCallback()
{
    assert(idle.size() > max_idle);

    /* destroy one third of the idle items */

    for (unsigned i = (idle.size() - max_idle + 2) / 3; i > 0; --i)
        idle.pop_front_and_dispose([this](StockItem *item){
                DestroyItem(*item);
            });

    /* schedule next cleanup */

    if (idle.size() > max_idle)
        ScheduleCleanup();
    else
        CheckEmpty();
}

/*
 * wait operation
 *
 */

inline void
Stock::Waiting::Abort()
{
    auto &list = stock.waiting;
    const auto i = list.iterator_to(*this);
    list.erase_and_dispose(i, [](Stock::Waiting *w){ w->Destroy(); });
}

void
Stock::RetryWaiting()
{
    if (limit == 0)
        /* no limit configured, no waiters possible */
        return;

    /* first try to serve existing idle items */

    while (!idle.empty()) {
        const auto i = waiting.begin();
        if (i == waiting.end())
            return;

        auto &w = *i;

        w.operation.Finished();
        waiting.erase(i);

        if (GetIdle(w.handler))
            w.Destroy();
        else
            /* didn't work (probably because borrowing the item has
               failed) - re-add to "waiting" list */
            waiting.push_front(w);
    }

    /* if we're below the limit, create a bunch of new items */

    for (unsigned i = limit - busy.size() - num_create;
         busy.size() + num_create < limit && i > 0 && !waiting.empty();
         --i) {
        auto &w = waiting.front();
        waiting.pop_front();

        w.operation.Finished();
        GetCreate(w.pool, w.info,
                  w.handler,
                  w.async_ref);
        w.Destroy();
    }
}

void
Stock::ScheduleRetryWaiting()
{
    if (limit > 0 && !waiting.empty() &&
        busy.size() - num_create < limit)
        retry_event.Add();
}

/*
 * clear after 60 seconds idle
 *
 */

void
Stock::ClearIdle()
{
    daemon_log(5, "Stock::ClearIdle(%p, '%s') num_idle=%zu num_busy=%zu\n",
               (const void *)this, uri,
               idle.size(), busy.size());

    if (idle.size() > max_idle)
        UnscheduleCleanup();

    idle.clear_and_dispose([this](StockItem *item){
            DestroyItem(*item);
        });
}

void
Stock::ClearEventCallback()
{
    daemon_log(6, "stock_clear_event_callback(%p, '%s') may_clear=%d\n",
               (const void *)this, uri, may_clear);

    if (may_clear)
        ClearIdle();

    may_clear = true;
    ScheduleClear();
    CheckEmpty();
}

/*
 * constructor
 *
 */

inline Stock::Stock(struct pool &_pool,
                    const StockClass &_cls, void *_class_ctx,
                    const char *_uri, unsigned _limit, unsigned _max_idle,
                    StockHandler *_handler)
    :pool(_pool), cls(_cls), class_ctx(_class_ctx),
     uri(p_strdup_checked(&pool, _uri)),
     limit(_limit), max_idle(_max_idle),
     handler(_handler),
     retry_event(MakeSimpleEventCallback(Stock, RetryWaiting), this),
     empty_event(MakeSimpleEventCallback(Stock, CheckEmpty), this),
     cleanup_event(MakeSimpleEventCallback(Stock, CleanupEventCallback), this),
     clear_event(MakeSimpleEventCallback(Stock, ClearEventCallback), this)
{
    ScheduleClear();
}

inline Stock::~Stock()
{
    assert(num_create == 0);

    /* must not call stock_free() when there are busy items left */
    assert(busy.empty());

    retry_event.Deinit();
    empty_event.Deinit();
    cleanup_event.Deinit();
    clear_event.Deinit();

    ClearIdle();

    pool_unref(&pool);
}

Stock *
stock_new(struct pool &_pool, const StockClass &cls, void *class_ctx,
          const char *uri, unsigned limit, unsigned max_idle,
          StockHandler *handler)
{
    assert(cls.pool != nullptr);
    assert(cls.create != nullptr);
    assert(max_idle > 0);

    struct pool *pool = pool_new_libc(&_pool, "stock");

    return new Stock(*pool, cls, class_ctx,
                     uri, limit, max_idle,
                     handler);
}

void
Stock::DestroyItem(StockItem &item)
{
    item.Destroy(class_ctx);
}

void
stock_free(Stock *stock)
{
    assert(stock != nullptr);

    delete stock;
}

const char *
stock_get_uri(Stock &stock)
{
    return stock.uri;
}

bool
stock_is_empty(const Stock &stock)
{
    return stock.IsEmpty();
}

void
stock_add_stats(const Stock &stock, StockStats &data)
{
    data.busy += stock.busy.size();
    data.idle += stock.idle.size();
}

void
stock_fade_all(Stock &stock)
{
    for (auto &i : stock.busy)
        i.fade = true;

    stock.ClearIdle();
    stock.ScheduleCheckEmpty();

    // TODO: restart the "num_create" list?
}

bool
Stock::GetIdle(StockGetHandler &get_handler)
{
    auto i = idle.begin();
    const auto end = idle.end();
    while (i != end) {
        StockItem &item = *i;
        assert(item.is_idle);

        i = idle.erase(i);

        if (idle.size() == max_idle)
            UnscheduleCleanup();

        if (item.Borrow(class_ctx)) {
#ifndef NDEBUG
            item.is_idle = false;
#endif

            busy.push_front(item);

            get_handler.OnStockItemReady(item);
            return true;
        }

        DestroyItem(item);
    }

    ScheduleCheckEmpty();
    return false;
}

void
Stock::GetCreate(struct pool &caller_pool, void *info,
                 StockGetHandler &get_handler,
                 struct async_operation_ref &async_ref)
{
    struct pool *item_pool = cls.pool(class_ctx, pool, uri);

    ++num_create;

    cls.create(class_ctx, {*this, *item_pool, get_handler},
               uri, info, caller_pool, async_ref);
}

void
stock_get(Stock &stock, struct pool &caller_pool, void *info,
          StockGetHandler &handler,
          struct async_operation_ref &async_ref)
{
    stock.may_clear = false;

    if (stock.GetIdle(handler))
        return;

    if (stock.limit > 0 &&
        stock.busy.size() + stock.num_create >= stock.limit) {
        /* item limit reached: wait for an item to return */
        auto waiting = NewFromPool<Stock::Waiting>(caller_pool, stock,
                                                   caller_pool, info,
                                                   handler, async_ref);
        stock.waiting.push_front(*waiting);
        return;
    }

    stock.GetCreate(caller_pool, info, handler, async_ref);
}

StockItem *
stock_get_now(Stock &stock, struct pool &pool, void *info,
              GError **error_r)
{
    struct NowRequest final : public StockGetHandler {
#ifndef NDEBUG
        bool created = false;
#endif
        StockItem *item;
        GError *error;

        /* virtual methods from class StockGetHandler */
        void OnStockItemReady(StockItem &_item) override {
#ifndef NDEBUG
            created = true;
#endif

            item = &_item;
        }

        void OnStockItemError(GError *_error) override {
#ifndef NDEBUG
            created = true;
#endif

            item = nullptr;
            error = _error;
        }
    };

    NowRequest data;
    struct async_operation_ref async_ref;

    /* cannot call this on a limited stock */
    assert(stock.limit == 0);

    stock_get(stock, pool, info, data, async_ref);
    assert(data.created);

    if (data.item == nullptr)
        g_propagate_error(error_r, data.error);

    return data.item;
}

void
stock_item_available(StockItem &item)
{
    Stock &stock = item.stock;

    assert(stock.num_create > 0);
    --stock.num_create;

    stock.busy.push_front(item);

    item.handler.OnStockItemReady(item);
}

void
stock_item_failed(StockItem &item, GError *error)
{
    Stock &stock = item.stock;

    assert(error != nullptr);
    assert(stock.num_create > 0);
    --stock.num_create;

    item.handler.OnStockItemError(error);
    item.Destroy(stock.class_ctx);

    stock.ScheduleCheckEmpty();
    stock.ScheduleRetryWaiting();
}

void
stock_item_aborted(StockItem &item)
{
    Stock &stock = item.stock;

    assert(stock.num_create > 0);
    --stock.num_create;

    item.Destroy(stock.class_ctx);

    stock.ScheduleCheckEmpty();
    stock.ScheduleRetryWaiting();
}

void
stock_put(StockItem &item, bool destroy)
{
    assert(!item.is_idle);

    Stock &stock = item.stock;
    stock.may_clear = false;

    assert(!stock.busy.empty());

    stock.busy.erase(stock.busy.iterator_to(item));

    if (destroy || item.fade || !item.Release(stock.class_ctx)) {
        stock.DestroyItem(item);
        stock.ScheduleCheckEmpty();
    } else {
#ifndef NDEBUG
        item.is_idle = true;
#endif

        if (stock.idle.size() == stock.max_idle)
            stock.ScheduleCleanup();

        stock.idle.push_front(item);
    }

    stock.ScheduleRetryWaiting();
}

void
stock_del(StockItem &item)
{
    assert(item.is_idle);

    Stock &stock = item.stock;

    assert(!stock.idle.empty());

    stock.idle.erase(stock.idle.iterator_to(item));

    if (stock.idle.size() == stock.max_idle)
        stock.UnscheduleCleanup();

    stock.DestroyItem(item);
    stock.ScheduleCheckEmpty();
}
<commit_msg>Stock: remove obsolete method Destroy()<commit_after>/*
 * Objects in stock.  May be used for connection pooling.
 *
 * author: Max Kellermann <mk@cm4all.com>
 */

#include "Stock.hxx"
#include "Class.hxx"
#include "Stats.hxx"
#include "Item.hxx"
#include "GetHandler.hxx"
#include "async.hxx"
#include "pool.hxx"
#include "event/TimerEvent.hxx"
#include "event/DeferEvent.hxx"
#include "event/Callback.hxx"
#include "util/Cast.hxx"

#include <daemon/log.h>

#include <glib.h>

#include <assert.h>

struct Stock {
    struct pool &pool;
    const StockClass &cls;
    void *const class_ctx;
    const char *const uri;

    /**
     * The maximum number of items in this stock.  If any more items
     * are requested, they are put into the #waiting list, which gets
     * checked as soon as stock_put() is called.
     */
    const unsigned limit;

    /**
     * The maximum number of permanent idle items.  If there are more
     * than that, a timer will incrementally kill excess items.
     */
    const unsigned max_idle;

    StockHandler *const handler;

    /**
     * This event is used to move the "retry waiting" code out of the
     * current stack, to invoke the handler method in a safe
     * environment.
     */
    DeferEvent retry_event;

    /**
     * This event is used to move the "empty" check out of the current
     * stack, to invoke the handler method in a safe environment.
     */
    DeferEvent empty_event;

    TimerEvent cleanup_event;
    TimerEvent clear_event;

    typedef boost::intrusive::list<StockItem,
                                   boost::intrusive::constant_time_size<true>> ItemList;

    ItemList idle;

    ItemList busy;

    unsigned num_create = 0;

    struct Waiting
        : boost::intrusive::list_base_hook<boost::intrusive::link_mode<boost::intrusive::normal_link>> {

        Stock &stock;

        struct async_operation operation;

        struct pool &pool;
        void *const info;

        StockGetHandler &handler;

        struct async_operation_ref &async_ref;

        Waiting(Stock &_stock, struct pool &_pool, void *_info,
                StockGetHandler &_handler,
                struct async_operation_ref &_async_ref)
            :stock(_stock), pool(_pool), info(_info),
             handler(_handler),
             async_ref(_async_ref) {
            operation.Init2<Waiting>();
            pool_ref(&pool);
            async_ref.Set(operation);
        }

        void Destroy() {
            DeleteUnrefPool(pool, this);
        }

        void Abort();
    };

    typedef boost::intrusive::list<Waiting,
                                   boost::intrusive::constant_time_size<false>> WaitingList;

    WaitingList waiting;

    bool may_clear = false;

    Stock(struct pool &_pool, const StockClass &cls, void *class_ctx,
          const char *uri, unsigned limit, unsigned max_idle,
          StockHandler *handler);

    ~Stock();

    gcc_pure
    bool IsEmpty() const {
        return idle.empty() && busy.empty() && num_create == 0;
    }

    /**
     * Check if the stock has become empty, and invoke the handler.
     */
    void CheckEmpty();
    void ScheduleCheckEmpty();

    void DestroyItem(StockItem &item);

    void ScheduleClear() {
        static constexpr struct timeval tv = { .tv_sec = 60, .tv_usec = 0 };
        clear_event.Add(tv);
    }

    void ClearIdle();

    bool GetIdle(StockGetHandler &handler);
    void GetCreate(struct pool &caller_pool, void *info,
                   StockGetHandler &get_handler,
                   struct async_operation_ref &async_ref);

    /**
     * Retry the waiting requests.  This is called after the number of
     * busy items was reduced.
     */
    void RetryWaiting();
    void ScheduleRetryWaiting();

    void ScheduleCleanup() {
        static constexpr struct timeval tv = { .tv_sec = 20, .tv_usec = 0 };
        cleanup_event.Add(tv);
    }

    void UnscheduleCleanup() {
        cleanup_event.Cancel();
    }
    void CleanupEventCallback();
    void ClearEventCallback();
};

/*
 * The "empty()" handler method.
 *
 */

void
Stock::CheckEmpty()
{
    if (IsEmpty() && handler != nullptr)
        handler->OnStockEmpty(*this, uri);
}

void
Stock::ScheduleCheckEmpty()
{
    if (IsEmpty() && handler != nullptr)
        empty_event.Add();
}

/*
 * cleanup
 *
 */

void
Stock::CleanupEventCallback()
{
    assert(idle.size() > max_idle);

    /* destroy one third of the idle items */

    for (unsigned i = (idle.size() - max_idle + 2) / 3; i > 0; --i)
        idle.pop_front_and_dispose([this](StockItem *item){
                DestroyItem(*item);
            });

    /* schedule next cleanup */

    if (idle.size() > max_idle)
        ScheduleCleanup();
    else
        CheckEmpty();
}

/*
 * wait operation
 *
 */

inline void
Stock::Waiting::Abort()
{
    auto &list = stock.waiting;
    const auto i = list.iterator_to(*this);
    list.erase_and_dispose(i, [](Stock::Waiting *w){ w->Destroy(); });
}

void
Stock::RetryWaiting()
{
    if (limit == 0)
        /* no limit configured, no waiters possible */
        return;

    /* first try to serve existing idle items */

    while (!idle.empty()) {
        const auto i = waiting.begin();
        if (i == waiting.end())
            return;

        auto &w = *i;

        w.operation.Finished();
        waiting.erase(i);

        if (GetIdle(w.handler))
            w.Destroy();
        else
            /* didn't work (probably because borrowing the item has
               failed) - re-add to "waiting" list */
            waiting.push_front(w);
    }

    /* if we're below the limit, create a bunch of new items */

    for (unsigned i = limit - busy.size() - num_create;
         busy.size() + num_create < limit && i > 0 && !waiting.empty();
         --i) {
        auto &w = waiting.front();
        waiting.pop_front();

        w.operation.Finished();
        GetCreate(w.pool, w.info,
                  w.handler,
                  w.async_ref);
        w.Destroy();
    }
}

void
Stock::ScheduleRetryWaiting()
{
    if (limit > 0 && !waiting.empty() &&
        busy.size() - num_create < limit)
        retry_event.Add();
}

/*
 * clear after 60 seconds idle
 *
 */

void
Stock::ClearIdle()
{
    daemon_log(5, "Stock::ClearIdle(%p, '%s') num_idle=%zu num_busy=%zu\n",
               (const void *)this, uri,
               idle.size(), busy.size());

    if (idle.size() > max_idle)
        UnscheduleCleanup();

    idle.clear_and_dispose([this](StockItem *item){
            DestroyItem(*item);
        });
}

void
Stock::ClearEventCallback()
{
    daemon_log(6, "stock_clear_event_callback(%p, '%s') may_clear=%d\n",
               (const void *)this, uri, may_clear);

    if (may_clear)
        ClearIdle();

    may_clear = true;
    ScheduleClear();
    CheckEmpty();
}

/*
 * constructor
 *
 */

inline Stock::Stock(struct pool &_pool,
                    const StockClass &_cls, void *_class_ctx,
                    const char *_uri, unsigned _limit, unsigned _max_idle,
                    StockHandler *_handler)
    :pool(_pool), cls(_cls), class_ctx(_class_ctx),
     uri(p_strdup_checked(&pool, _uri)),
     limit(_limit), max_idle(_max_idle),
     handler(_handler),
     retry_event(MakeSimpleEventCallback(Stock, RetryWaiting), this),
     empty_event(MakeSimpleEventCallback(Stock, CheckEmpty), this),
     cleanup_event(MakeSimpleEventCallback(Stock, CleanupEventCallback), this),
     clear_event(MakeSimpleEventCallback(Stock, ClearEventCallback), this)
{
    ScheduleClear();
}

inline Stock::~Stock()
{
    assert(num_create == 0);

    /* must not call stock_free() when there are busy items left */
    assert(busy.empty());

    retry_event.Deinit();
    empty_event.Deinit();
    cleanup_event.Deinit();
    clear_event.Deinit();

    ClearIdle();

    pool_unref(&pool);
}

Stock *
stock_new(struct pool &_pool, const StockClass &cls, void *class_ctx,
          const char *uri, unsigned limit, unsigned max_idle,
          StockHandler *handler)
{
    assert(cls.pool != nullptr);
    assert(cls.create != nullptr);
    assert(max_idle > 0);

    struct pool *pool = pool_new_libc(&_pool, "stock");

    return new Stock(*pool, cls, class_ctx,
                     uri, limit, max_idle,
                     handler);
}

void
Stock::DestroyItem(StockItem &item)
{
    item.Destroy(class_ctx);
}

void
stock_free(Stock *stock)
{
    assert(stock != nullptr);

    delete stock;
}

const char *
stock_get_uri(Stock &stock)
{
    return stock.uri;
}

bool
stock_is_empty(const Stock &stock)
{
    return stock.IsEmpty();
}

void
stock_add_stats(const Stock &stock, StockStats &data)
{
    data.busy += stock.busy.size();
    data.idle += stock.idle.size();
}

void
stock_fade_all(Stock &stock)
{
    for (auto &i : stock.busy)
        i.fade = true;

    stock.ClearIdle();
    stock.ScheduleCheckEmpty();

    // TODO: restart the "num_create" list?
}

bool
Stock::GetIdle(StockGetHandler &get_handler)
{
    auto i = idle.begin();
    const auto end = idle.end();
    while (i != end) {
        StockItem &item = *i;
        assert(item.is_idle);

        i = idle.erase(i);

        if (idle.size() == max_idle)
            UnscheduleCleanup();

        if (item.Borrow(class_ctx)) {
#ifndef NDEBUG
            item.is_idle = false;
#endif

            busy.push_front(item);

            get_handler.OnStockItemReady(item);
            return true;
        }

        DestroyItem(item);
    }

    ScheduleCheckEmpty();
    return false;
}

void
Stock::GetCreate(struct pool &caller_pool, void *info,
                 StockGetHandler &get_handler,
                 struct async_operation_ref &async_ref)
{
    struct pool *item_pool = cls.pool(class_ctx, pool, uri);

    ++num_create;

    cls.create(class_ctx, {*this, *item_pool, get_handler},
               uri, info, caller_pool, async_ref);
}

void
stock_get(Stock &stock, struct pool &caller_pool, void *info,
          StockGetHandler &handler,
          struct async_operation_ref &async_ref)
{
    stock.may_clear = false;

    if (stock.GetIdle(handler))
        return;

    if (stock.limit > 0 &&
        stock.busy.size() + stock.num_create >= stock.limit) {
        /* item limit reached: wait for an item to return */
        auto waiting = NewFromPool<Stock::Waiting>(caller_pool, stock,
                                                   caller_pool, info,
                                                   handler, async_ref);
        stock.waiting.push_front(*waiting);
        return;
    }

    stock.GetCreate(caller_pool, info, handler, async_ref);
}

StockItem *
stock_get_now(Stock &stock, struct pool &pool, void *info,
              GError **error_r)
{
    struct NowRequest final : public StockGetHandler {
#ifndef NDEBUG
        bool created = false;
#endif
        StockItem *item;
        GError *error;

        /* virtual methods from class StockGetHandler */
        void OnStockItemReady(StockItem &_item) override {
#ifndef NDEBUG
            created = true;
#endif

            item = &_item;
        }

        void OnStockItemError(GError *_error) override {
#ifndef NDEBUG
            created = true;
#endif

            item = nullptr;
            error = _error;
        }
    };

    NowRequest data;
    struct async_operation_ref async_ref;

    /* cannot call this on a limited stock */
    assert(stock.limit == 0);

    stock_get(stock, pool, info, data, async_ref);
    assert(data.created);

    if (data.item == nullptr)
        g_propagate_error(error_r, data.error);

    return data.item;
}

void
stock_item_available(StockItem &item)
{
    Stock &stock = item.stock;

    assert(stock.num_create > 0);
    --stock.num_create;

    stock.busy.push_front(item);

    item.handler.OnStockItemReady(item);
}

void
stock_item_failed(StockItem &item, GError *error)
{
    Stock &stock = item.stock;

    assert(error != nullptr);
    assert(stock.num_create > 0);
    --stock.num_create;

    item.handler.OnStockItemError(error);
    item.Destroy(stock.class_ctx);

    stock.ScheduleCheckEmpty();
    stock.ScheduleRetryWaiting();
}

void
stock_item_aborted(StockItem &item)
{
    Stock &stock = item.stock;

    assert(stock.num_create > 0);
    --stock.num_create;

    item.Destroy(stock.class_ctx);

    stock.ScheduleCheckEmpty();
    stock.ScheduleRetryWaiting();
}

void
stock_put(StockItem &item, bool destroy)
{
    assert(!item.is_idle);

    Stock &stock = item.stock;
    stock.may_clear = false;

    assert(!stock.busy.empty());

    stock.busy.erase(stock.busy.iterator_to(item));

    if (destroy || item.fade || !item.Release(stock.class_ctx)) {
        stock.DestroyItem(item);
        stock.ScheduleCheckEmpty();
    } else {
#ifndef NDEBUG
        item.is_idle = true;
#endif

        if (stock.idle.size() == stock.max_idle)
            stock.ScheduleCleanup();

        stock.idle.push_front(item);
    }

    stock.ScheduleRetryWaiting();
}

void
stock_del(StockItem &item)
{
    assert(item.is_idle);

    Stock &stock = item.stock;

    assert(!stock.idle.empty());

    stock.idle.erase(stock.idle.iterator_to(item));

    if (stock.idle.size() == stock.max_idle)
        stock.UnscheduleCleanup();

    stock.DestroyItem(item);
    stock.ScheduleCheckEmpty();
}
<|endoftext|>


<commit_before>#include "string_util.hpp"

#include <algorithm>
#include <stdexcept>
#include <fstream>
#include <iomanip>
#include <sstream>

std::string uint32_t_to_ipv4(const uint32_t address)
{
	std::ostringstream ostr;
	ostr<<(uint32_t)((uint8_t*)&address)[0]<<"."<<
		(uint32_t)((uint8_t*)&address)[1]<<"."<<
		(uint32_t)((uint8_t*)&address)[2]<<"."<<
		(uint32_t)((uint8_t*)&address)[3];
	return ostr.str();
}

std::string hex_to_ipv4(const std::string& hex)
{
	if(!hex.size()==4||!is_hex(hex))
		throw std::runtime_error("hex_to_ipv4 - Invalid hex value \""+hex+"\".");

	std::ostringstream ostr;
	ostr<<hex_to_decimal(hex.substr(6,2))<<".";
	ostr<<hex_to_decimal(hex.substr(4,2))<<".";
	ostr<<hex_to_decimal(hex.substr(2,2))<<".";
	ostr<<hex_to_decimal(hex.substr(0,2));

	return ostr.str();
}

std::string hex_to_ipv6(const std::string& hex)
{
	if(!hex.size()==4||!is_hex(hex))
		throw std::runtime_error("hex_to_ipv4 - Invalid hex value \""+hex+"\".");

	std::string lower_hex=to_lower(hex);
	std::ostringstream ostr;
	ostr<<lower_hex.substr(2,2)<<lower_hex.substr(0,2)<<":";
	ostr<<lower_hex.substr(6,2)<<lower_hex.substr(4,2)<<":";
	ostr<<lower_hex.substr(10,2)<<lower_hex.substr(8,2)<<":";
	ostr<<lower_hex.substr(14,2)<<lower_hex.substr(12,2)<<":";
	ostr<<lower_hex.substr(18,2)<<lower_hex.substr(16,2)<<":";
	ostr<<lower_hex.substr(22,2)<<lower_hex.substr(20,2)<<":";
	ostr<<lower_hex.substr(26,2)<<lower_hex.substr(24,2)<<":";
	ostr<<lower_hex.substr(30,2)<<lower_hex.substr(28,2);

	return ostr.str();
}

std::string uint8_t_16_to_ipv6(const uint8_t address[16])
{
	std::ostringstream ostr;
	for(int ii=0;ii<16;ii+=2)
	{
		ostr<<std::hex<<std::setw(2)<<std::setfill('0')<<(unsigned int)(unsigned char)address[ii+0];
		ostr<<std::hex<<std::setw(2)<<std::setfill('0')<<(unsigned int)(unsigned char)address[ii+1];

		if(ii<14)
			ostr<<":";
	}

	return ostr.str();
}

std::string uint16_t_to_port(const uint16_t port)
{
	std::ostringstream ostr;
	ostr<<((((uint32_t)((uint8_t*)&port)[0])<<8)+((uint8_t*)&port)[1]);
	return ostr.str();
}

std::string hex_to_port(const std::string& hex)
{
	if(!hex.size()==4||!is_hex(hex))
		throw std::runtime_error("hex_to_port - Invalid hex value \""+hex+"\".");

	std::ostringstream ostr;
	ostr<<(hex_to_decimal(hex.substr(0,2))<<8)+hex_to_decimal(hex.substr(2,2));
	return ostr.str();
}

#if(defined(_WIN32)&&!defined(__CYGWIN__))
	std::string dword_to_port(const DWORD port)
	{
		return uint16_t_to_port((uint16_t)port);
	}
#endif

bool file_to_string(const std::string& filename,std::string& data)
{
	char buffer;
	std::ifstream istr(filename.c_str(),std::ios_base::in|std::ios_base::binary);
	istr.unsetf(std::ios_base::skipws);

	if(!istr)
		return false;

	data="";

	while(istr>>buffer)
		data+=buffer;

	istr.close();
	return true;
}

std::string replace_all(std::string str,const std::string& find,const std::string& replace)
{
	size_t pos=0;

	while((pos=str.find(find,pos))!=std::string::npos)
	{
		str.replace(pos,find.size(),replace);
		pos+=replace.size();
	}

	return str;
}

list_t string_to_lines(const std::string& str)
{
	std::istringstream istr(str.c_str());
	list_t lines;
	std::string line;

	while(std::getline(istr,line))
		lines.push_back(line);

	return lines;
}

list_t read_line_columns(const std::string& line)
{
	std::istringstream istr(line);
	list_t columns;
	std::string column;

	while(istr>>column)
		columns.push_back(column);

	return columns;
}

uint32_t hex_to_decimal(const std::string& hex)
{
	if(hex.size()!=2)
		throw std::runtime_error("hex_to_decimal - Invalid single byte hex value \""+hex+"\".");

	if(!is_hex(hex))
		throw std::runtime_error("hex_to_decimal - Invalid single byte hex value \""+hex+"\".");

	std::string lower_hex=to_lower(hex);
	unsigned int decimal=0;

	if(lower_hex[1]>='0'&&lower_hex[1]<='9')
		decimal+=lower_hex[1]-'0';
	if(lower_hex[1]>='a'&&lower_hex[1]<='f')
		decimal+=10+lower_hex[1]-'a';

	if(lower_hex[0]>='0'&&lower_hex[0]<='9')
		decimal+=(lower_hex[0]-'0')<<4;
	if(lower_hex[0]>='a'&&lower_hex[0]<='f')
		decimal+=(10+lower_hex[0]-'a')<<4;

	return decimal;
}

std::string to_string(const uint32_t val)
{
	std::ostringstream ostr;
	ostr<<val;
	return ostr.str();
}

std::string to_lower(std::string str)
{
	std::transform(str.begin(),str.end(),str.begin(),tolower);
	return str;
}

bool is_int(const std::string& str)
{
	for(size_t ii=0;ii<str.size();++ii)
		if(str[ii]<'0'||str[ii]>'9')
			return false;

	return str.size()!=0;
}

bool is_hex(const std::string& hex)
{
	std::string lower_hex=to_lower(hex);

	for(size_t ii=0;ii<lower_hex.size();++ii)
		if(!(lower_hex[ii]>='0'&&lower_hex[ii]<='9')&&!(lower_hex[ii]>='a'&&lower_hex[ii]<='f'))
			return false;

	return true;
}

bool starts_with(const std::string& str,const std::string& prefix)
{
	return (str.size()>=prefix.size()&&str.compare(0,prefix.size(),prefix)==0);
}

bool ends_with(const std::string& str,const std::string& suffix)
{
	return (str.size()>=suffix.size()&&starts_with(str.substr(str.size()-suffix.size(),suffix.size()),suffix));
}<commit_msg>Removed bool  warning...<commit_after>#include "string_util.hpp"

#include <algorithm>
#include <stdexcept>
#include <fstream>
#include <iomanip>
#include <sstream>

std::string uint32_t_to_ipv4(const uint32_t address)
{
	std::ostringstream ostr;
	ostr<<(uint32_t)((uint8_t*)&address)[0]<<"."<<
		(uint32_t)((uint8_t*)&address)[1]<<"."<<
		(uint32_t)((uint8_t*)&address)[2]<<"."<<
		(uint32_t)((uint8_t*)&address)[3];
	return ostr.str();
}

std::string hex_to_ipv4(const std::string& hex)
{
	if(hex.size()!=4||!is_hex(hex))
		throw std::runtime_error("hex_to_ipv4 - Invalid hex value \""+hex+"\".");

	std::ostringstream ostr;
	ostr<<hex_to_decimal(hex.substr(6,2))<<".";
	ostr<<hex_to_decimal(hex.substr(4,2))<<".";
	ostr<<hex_to_decimal(hex.substr(2,2))<<".";
	ostr<<hex_to_decimal(hex.substr(0,2));

	return ostr.str();
}

std::string hex_to_ipv6(const std::string& hex)
{
	if(hex.size()!=4||!is_hex(hex))
		throw std::runtime_error("hex_to_ipv4 - Invalid hex value \""+hex+"\".");

	std::string lower_hex=to_lower(hex);
	std::ostringstream ostr;
	ostr<<lower_hex.substr(2,2)<<lower_hex.substr(0,2)<<":";
	ostr<<lower_hex.substr(6,2)<<lower_hex.substr(4,2)<<":";
	ostr<<lower_hex.substr(10,2)<<lower_hex.substr(8,2)<<":";
	ostr<<lower_hex.substr(14,2)<<lower_hex.substr(12,2)<<":";
	ostr<<lower_hex.substr(18,2)<<lower_hex.substr(16,2)<<":";
	ostr<<lower_hex.substr(22,2)<<lower_hex.substr(20,2)<<":";
	ostr<<lower_hex.substr(26,2)<<lower_hex.substr(24,2)<<":";
	ostr<<lower_hex.substr(30,2)<<lower_hex.substr(28,2);

	return ostr.str();
}

std::string uint8_t_16_to_ipv6(const uint8_t address[16])
{
	std::ostringstream ostr;
	for(int ii=0;ii<16;ii+=2)
	{
		ostr<<std::hex<<std::setw(2)<<std::setfill('0')<<(unsigned int)(unsigned char)address[ii+0];
		ostr<<std::hex<<std::setw(2)<<std::setfill('0')<<(unsigned int)(unsigned char)address[ii+1];

		if(ii<14)
			ostr<<":";
	}

	return ostr.str();
}

std::string uint16_t_to_port(const uint16_t port)
{
	std::ostringstream ostr;
	ostr<<((((uint32_t)((uint8_t*)&port)[0])<<8)+((uint8_t*)&port)[1]);
	return ostr.str();
}

std::string hex_to_port(const std::string& hex)
{
	if(hex.size()!=4||!is_hex(hex))
		throw std::runtime_error("hex_to_port - Invalid hex value \""+hex+"\".");

	std::ostringstream ostr;
	ostr<<(hex_to_decimal(hex.substr(0,2))<<8)+hex_to_decimal(hex.substr(2,2));
	return ostr.str();
}

#if(defined(_WIN32)&&!defined(__CYGWIN__))
	std::string dword_to_port(const DWORD port)
	{
		return uint16_t_to_port((uint16_t)port);
	}
#endif

bool file_to_string(const std::string& filename,std::string& data)
{
	char buffer;
	std::ifstream istr(filename.c_str(),std::ios_base::in|std::ios_base::binary);
	istr.unsetf(std::ios_base::skipws);

	if(!istr)
		return false;

	data="";

	while(istr>>buffer)
		data+=buffer;

	istr.close();
	return true;
}

std::string replace_all(std::string str,const std::string& find,const std::string& replace)
{
	size_t pos=0;

	while((pos=str.find(find,pos))!=std::string::npos)
	{
		str.replace(pos,find.size(),replace);
		pos+=replace.size();
	}

	return str;
}

list_t string_to_lines(const std::string& str)
{
	std::istringstream istr(str.c_str());
	list_t lines;
	std::string line;

	while(std::getline(istr,line))
		lines.push_back(line);

	return lines;
}

list_t read_line_columns(const std::string& line)
{
	std::istringstream istr(line);
	list_t columns;
	std::string column;

	while(istr>>column)
		columns.push_back(column);

	return columns;
}

uint32_t hex_to_decimal(const std::string& hex)
{
	if(hex.size()!=2)
		throw std::runtime_error("hex_to_decimal - Invalid single byte hex value \""+hex+"\".");

	if(!is_hex(hex))
		throw std::runtime_error("hex_to_decimal - Invalid single byte hex value \""+hex+"\".");

	std::string lower_hex=to_lower(hex);
	unsigned int decimal=0;

	if(lower_hex[1]>='0'&&lower_hex[1]<='9')
		decimal+=lower_hex[1]-'0';
	if(lower_hex[1]>='a'&&lower_hex[1]<='f')
		decimal+=10+lower_hex[1]-'a';

	if(lower_hex[0]>='0'&&lower_hex[0]<='9')
		decimal+=(lower_hex[0]-'0')<<4;
	if(lower_hex[0]>='a'&&lower_hex[0]<='f')
		decimal+=(10+lower_hex[0]-'a')<<4;

	return decimal;
}

std::string to_string(const uint32_t val)
{
	std::ostringstream ostr;
	ostr<<val;
	return ostr.str();
}

std::string to_lower(std::string str)
{
	std::transform(str.begin(),str.end(),str.begin(),tolower);
	return str;
}

bool is_int(const std::string& str)
{
	for(size_t ii=0;ii<str.size();++ii)
		if(str[ii]<'0'||str[ii]>'9')
			return false;

	return str.size()!=0;
}

bool is_hex(const std::string& hex)
{
	std::string lower_hex=to_lower(hex);

	for(size_t ii=0;ii<lower_hex.size();++ii)
		if(!(lower_hex[ii]>='0'&&lower_hex[ii]<='9')&&!(lower_hex[ii]>='a'&&lower_hex[ii]<='f'))
			return false;

	return true;
}

bool starts_with(const std::string& str,const std::string& prefix)
{
	return (str.size()>=prefix.size()&&str.compare(0,prefix.size(),prefix)==0);
}

bool ends_with(const std::string& str,const std::string& suffix)
{
	return (str.size()>=suffix.size()&&starts_with(str.substr(str.size()-suffix.size(),suffix.size()),suffix));
}<|endoftext|>


<commit_before>#include "util.hxx"

#include <cstring>
#include <vector>
#include <stdexcept>

#include <utki/debug.hpp>
#include <utki/math.hpp>

#include <svgdom/length.hpp>

#include "Renderer.hxx"

using namespace svgren;

canvas_context_push::canvas_context_push(canvas& c) :
		c(c)
{
	this->c.push_context();
}

canvas_context_push::~canvas_context_push()noexcept{
	this->c.pop_context();
}

canvas_matrix_push::canvas_matrix_push(canvas& c) :
		c(c)
{
	this->m = this->c.get_matrix();
}

canvas_matrix_push::~canvas_matrix_push()noexcept{
	this->c.set_matrix(this->m);
}

real svgren::percentLengthToFraction(const svgdom::length& l){
	if(l.is_percent()){
		return l.value / real(100);
	}
	if(l.unit == svgdom::length_unit::number){
		return l.value;
	}
	return 0;
}

void DeviceSpaceBoundingBox::set_empty(){
	this->left = std::numeric_limits<decltype(this->left)>::max();
	this->top = std::numeric_limits<decltype(this->top)>::max();
	this->right = std::numeric_limits<decltype(this->right)>::min();
	this->bottom = std::numeric_limits<decltype(this->bottom)>::min();
}

void DeviceSpaceBoundingBox::unite(const DeviceSpaceBoundingBox& bb){
	using std::min;
	using std::max;
	this->left = min(this->left, bb.left);
	this->top = min(this->top, bb.top);
	this->right = max(this->right, bb.right);
	this->bottom = max(this->bottom, bb.bottom);
}

real DeviceSpaceBoundingBox::width()const noexcept{
	using std::max;
	auto w = this->right - this->left;
	return max(w, decltype(w)(0));
}

real DeviceSpaceBoundingBox::height() const noexcept{
	using std::max;
	auto h = this->bottom - this->top;
	return max(h, decltype(h)(0));
}

DeviceSpaceBoundingBoxPush::DeviceSpaceBoundingBoxPush(Renderer& r) :
		r(r),
		oldBb(r.deviceSpaceBoundingBox)
{
	this->r.deviceSpaceBoundingBox.set_empty();
}

DeviceSpaceBoundingBoxPush::~DeviceSpaceBoundingBoxPush() noexcept{
	this->oldBb.unite(this->r.deviceSpaceBoundingBox);
	this->r.deviceSpaceBoundingBox = this->oldBb;
}

ViewportPush::ViewportPush(Renderer& r, const decltype(oldViewport)& viewport) :
		r(r),
		oldViewport(r.viewport)
{
	this->r.viewport = viewport;
}

ViewportPush::~ViewportPush() noexcept{
	this->r.viewport = this->oldViewport;
}

PushCairoGroupIfNeeded::PushCairoGroupIfNeeded(Renderer& renderer, bool isContainer) :
		renderer(renderer)
{
	auto backgroundP = this->renderer.styleStack.get_style_property(svgdom::style_property::enable_background);

	if(backgroundP && backgroundP->enable_background.value == svgdom::enable_background::new_){
		this->oldBackground = this->renderer.background;
	}

	auto filterP = this->renderer.styleStack.get_style_property(svgdom::style_property::filter);

	if(auto maskP = this->renderer.styleStack.get_style_property(svgdom::style_property::mask)){
		if(auto ei = this->renderer.finder.find_by_id(maskP->get_local_id_from_iri())){
			this->maskElement = &ei->e;
		}
	}

	this->groupPushed = filterP || this->maskElement || this->oldBackground.data;

	auto opacity = svgdom::real(1);
	{
		auto strokeP = this->renderer.styleStack.get_style_property(svgdom::style_property::stroke);
		auto fillP = this->renderer.styleStack.get_style_property(svgdom::style_property::fill);

		// OPTIMIZATION: if opacity is set on an element then push cairo group only in case it is a Container element, like 'g' or 'svg',
		//               or in case the fill or stroke is a non-solid color, like gradient or pattern,
		//               or both fill and stroke are non-none.
		//               If element is non-container and one of stroke or fill is solid color and other one is none,
		//               then opacity will be applied later without pushing cairo group.
		if(this->groupPushed
				|| isContainer
				|| (strokeP && strokeP->is_url())
				|| (fillP && fillP->is_url())
				|| (fillP && strokeP && !fillP->is_none() && !strokeP->is_none())
			)
		{
			if(auto p = this->renderer.styleStack.get_style_property(svgdom::style_property::opacity)){
				opacity = p->opacity;
				this->groupPushed = this->groupPushed || opacity < 1;
			}
		}
	}

	if(this->groupPushed){
//		TRACE(<< "setting temp context" << std::endl)
		cairo_push_group(this->renderer.cr);
		if(cairo_status(this->renderer.cr) != CAIRO_STATUS_SUCCESS){
			throw std::runtime_error("cairo_push_group() failed");
		}

		this->opacity = opacity;
	}

	if(this->oldBackground.data){
		this->renderer.background = this->renderer.canvas.get_sub_surface();
	}
}

PushCairoGroupIfNeeded::~PushCairoGroupIfNeeded()noexcept{
	if(!this->groupPushed){
		return;
	}

	// render mask
	cairo_pattern_t* mask = nullptr;
	try{
		if(this->maskElement){
			cairo_push_group(this->renderer.cr);
			if(cairo_status(this->renderer.cr) != CAIRO_STATUS_SUCCESS){
				throw std::runtime_error("cairo_push_group() failed");
			}

			utki::scope_exit scope_exit([&mask, this](){
				mask = cairo_pop_group(this->renderer.cr);
			});

			// TODO: setup the correct coordinate system based on maskContentUnits value (userSpaceOnUse/objectBoundingBox)
			//       Currently nothing on that is done which is equivalent to userSpaceOnUse

			class MaskRenderer : public svgdom::const_visitor{
				Renderer& r;
			public:
				MaskRenderer(Renderer& r) : r(r){}

				void visit(const svgdom::mask_element& e)override{
					svgdom::style_stack::push pushStyles(this->r.styleStack, e);

					this->r.relayAccept(e);
				}
			} maskRenderer(this->renderer);

			this->maskElement->accept(maskRenderer);

			appendLuminanceToAlpha(this->renderer.canvas.get_sub_surface());
		}
	}catch(...){
		// rendering mask failed, just ignore it
	}

	utki::scope_exit scope_exit([mask](){
		if(mask){
			cairo_pattern_destroy(mask);
		}
	});

	cairo_pop_group_to_source(this->renderer.cr);

	if(mask){
		cairo_mask(this->renderer.cr, mask);
	}else{
		ASSERT(0 <= this->opacity && this->opacity <= 1)
		if(this->opacity < svgdom::real(1)){
			cairo_paint_with_alpha(this->renderer.cr, this->opacity);
		}else{
			cairo_paint(this->renderer.cr);
		}
	}

	// restore background if it was pushed
	if(this->oldBackground.data){
		this->renderer.background = this->oldBackground;
	}
}

void svgren::appendLuminanceToAlpha(surface s){
	ASSERT((s.end - s.data) % 4 == 0)

	// Luminance is calculated using formula L = 0.2126 * R + 0.7152 * G + 0.0722 * B
	// For faster calculation it can be simplified to L = (2 * R + 3 * G + B) / 6

	// TODO: take stride into account, do not append luminance to alpha for data out of the surface width
	for(auto p = s.data; p != s.end; ++p){
		uint32_t l = 2 * uint32_t(*p);
		++p;
		l += 3 * uint32_t(*p);
		++p;
		l += uint32_t(*p);
		++p;

		l /= 6;

		// Cairo uses premultiplied alpha, so no need to multiply alpha by liminance.
		ASSERT(l <= 255)
		*p = uint8_t(l);
	}
}
<commit_msg>stuff<commit_after>#include "util.hxx"

#include <cstring>
#include <vector>
#include <stdexcept>

#include <utki/debug.hpp>
#include <utki/math.hpp>

#include <svgdom/length.hpp>

#include "Renderer.hxx"

using namespace svgren;

canvas_context_push::canvas_context_push(canvas& c) :
		c(c)
{
	this->c.push_context();
}

canvas_context_push::~canvas_context_push()noexcept{
	this->c.pop_context();
}

canvas_matrix_push::canvas_matrix_push(canvas& c) :
		c(c)
{
	this->m = this->c.get_matrix();
}

canvas_matrix_push::~canvas_matrix_push()noexcept{
	this->c.set_matrix(this->m);
}

real svgren::percentLengthToFraction(const svgdom::length& l){
	if(l.is_percent()){
		return l.value / real(100);
	}
	if(l.unit == svgdom::length_unit::number){
		return l.value;
	}
	return 0;
}

void DeviceSpaceBoundingBox::set_empty(){
	this->left = std::numeric_limits<decltype(this->left)>::max();
	this->top = std::numeric_limits<decltype(this->top)>::max();
	this->right = std::numeric_limits<decltype(this->right)>::min();
	this->bottom = std::numeric_limits<decltype(this->bottom)>::min();
}

void DeviceSpaceBoundingBox::unite(const DeviceSpaceBoundingBox& bb){
	using std::min;
	using std::max;
	this->left = min(this->left, bb.left);
	this->top = min(this->top, bb.top);
	this->right = max(this->right, bb.right);
	this->bottom = max(this->bottom, bb.bottom);
}

real DeviceSpaceBoundingBox::width()const noexcept{
	using std::max;
	auto w = this->right - this->left;
	return max(w, decltype(w)(0));
}

real DeviceSpaceBoundingBox::height() const noexcept{
	using std::max;
	auto h = this->bottom - this->top;
	return max(h, decltype(h)(0));
}

DeviceSpaceBoundingBoxPush::DeviceSpaceBoundingBoxPush(Renderer& r) :
		r(r),
		oldBb(r.deviceSpaceBoundingBox)
{
	this->r.deviceSpaceBoundingBox.set_empty();
}

DeviceSpaceBoundingBoxPush::~DeviceSpaceBoundingBoxPush() noexcept{
	this->oldBb.unite(this->r.deviceSpaceBoundingBox);
	this->r.deviceSpaceBoundingBox = this->oldBb;
}

ViewportPush::ViewportPush(Renderer& r, const decltype(oldViewport)& viewport) :
		r(r),
		oldViewport(r.viewport)
{
	this->r.viewport = viewport;
}

ViewportPush::~ViewportPush() noexcept{
	this->r.viewport = this->oldViewport;
}

PushCairoGroupIfNeeded::PushCairoGroupIfNeeded(Renderer& renderer, bool isContainer) :
		renderer(renderer)
{
	auto backgroundP = this->renderer.styleStack.get_style_property(svgdom::style_property::enable_background);

	if(backgroundP && backgroundP->enable_background.value == svgdom::enable_background::new_){
		this->oldBackground = this->renderer.background;
	}

	auto filterP = this->renderer.styleStack.get_style_property(svgdom::style_property::filter);

	if(auto maskP = this->renderer.styleStack.get_style_property(svgdom::style_property::mask)){
		if(auto ei = this->renderer.finder.find_by_id(maskP->get_local_id_from_iri())){
			this->maskElement = &ei->e;
		}
	}

	this->groupPushed = filterP || this->maskElement || this->oldBackground.data;

	auto opacity = svgdom::real(1);
	{
		auto strokeP = this->renderer.styleStack.get_style_property(svgdom::style_property::stroke);
		auto fillP = this->renderer.styleStack.get_style_property(svgdom::style_property::fill);

		// OPTIMIZATION: if opacity is set on an element then push cairo group only in case it is a Container element, like 'g' or 'svg',
		//               or in case the fill or stroke is a non-solid color, like gradient or pattern,
		//               or both fill and stroke are non-none.
		//               If element is non-container and one of stroke or fill is solid color and other one is none,
		//               then opacity will be applied later without pushing cairo group.
		if(this->groupPushed
				|| isContainer
				|| (strokeP && strokeP->is_url())
				|| (fillP && fillP->is_url())
				|| (fillP && strokeP && !fillP->is_none() && !strokeP->is_none())
			)
		{
			if(auto p = this->renderer.styleStack.get_style_property(svgdom::style_property::opacity)){
				opacity = p->opacity;
				this->groupPushed = this->groupPushed || opacity < 1;
			}
		}
	}

	if(this->groupPushed){
//		TRACE(<< "setting temp context" << std::endl)
		cairo_push_group(this->renderer.cr);
		if(cairo_status(this->renderer.cr) != CAIRO_STATUS_SUCCESS){
			throw std::runtime_error("cairo_push_group() failed");
		}

		this->opacity = opacity;
	}

	if(this->oldBackground.data){
		this->renderer.background = this->renderer.canvas.get_sub_surface();
	}
}

PushCairoGroupIfNeeded::~PushCairoGroupIfNeeded()noexcept{
	if(!this->groupPushed){
		return;
	}

	// render mask
	cairo_pattern_t* mask = nullptr;
	try{
		if(this->maskElement){
			cairo_push_group(this->renderer.cr);
			if(cairo_status(this->renderer.cr) != CAIRO_STATUS_SUCCESS){
				throw std::runtime_error("cairo_push_group() failed");
			}

			utki::scope_exit scope_exit([&mask, this](){
				mask = cairo_pop_group(this->renderer.cr);
			});

			// TODO: setup the correct coordinate system based on maskContentUnits value (userSpaceOnUse/objectBoundingBox)
			//       Currently nothing on that is done which is equivalent to userSpaceOnUse

			class MaskRenderer : public svgdom::const_visitor{
				Renderer& r;
			public:
				MaskRenderer(Renderer& r) : r(r){}

				void visit(const svgdom::mask_element& e)override{
					svgdom::style_stack::push pushStyles(this->r.styleStack, e);

					this->r.relayAccept(e);
				}
			} maskRenderer(this->renderer);

			this->maskElement->accept(maskRenderer);

			appendLuminanceToAlpha(this->renderer.canvas.get_sub_surface());
		}
	}catch(...){
		// rendering mask failed, just ignore it
	}

	utki::scope_exit scope_exit([mask](){
		if(mask){
			cairo_pattern_destroy(mask);
		}
	});

	cairo_pop_group_to_source(this->renderer.cr);

	if(mask){
		cairo_mask(this->renderer.cr, mask);
	}else{
		ASSERT(0 <= this->opacity && this->opacity <= 1)
		if(this->opacity < real(1)){
			cairo_paint_with_alpha(this->renderer.cr, this->opacity);
		}else{
			cairo_paint(this->renderer.cr);
		}
	}

	// restore background if it was pushed
	if(this->oldBackground.data){
		this->renderer.background = this->oldBackground;
	}
}

void svgren::appendLuminanceToAlpha(surface s){
	ASSERT((s.end - s.data) % 4 == 0)

	// Luminance is calculated using formula L = 0.2126 * R + 0.7152 * G + 0.0722 * B
	// For faster calculation it can be simplified to L = (2 * R + 3 * G + B) / 6

	// TODO: take stride into account, do not append luminance to alpha for data out of the surface width
	for(auto p = s.data; p != s.end; ++p){
		uint32_t l = 2 * uint32_t(*p);
		++p;
		l += 3 * uint32_t(*p);
		++p;
		l += uint32_t(*p);
		++p;

		l /= 6;

		// Cairo uses premultiplied alpha, so no need to multiply alpha by liminance.
		ASSERT(l <= 255)
		*p = uint8_t(l);
	}
}
<|endoftext|>


<commit_before>#include "time.h"

#include <chrono>

extern "C"
{
	#include <time.h>
	#include <unistd.h>
}

namespace benchmark
{

namespace detail
{

double tsc_freq_ghz = .0;

}

std::chrono::nanoseconds operator-(timespec end, timespec start)
{
	return std::chrono::nanoseconds((int64_t)((end.tv_sec - start.tv_sec) * 1e9 + end.tv_nsec - start.tv_nsec));
}

void init()
{
	timespec start, end;
	clock_gettime(CLOCK_MONOTONIC_RAW, &start);

	uint64_t rdtsc_start = detail::rdtsc();
	sleep(1);
	uint64_t rdtsc_end = detail::rdtsc();

	clock_gettime(CLOCK_MONOTONIC_RAW, &end);

	auto ns = end - start;
	uint64_t cycles = rdtsc_end - rdtsc_start;
	detail::tsc_freq_ghz = (double)cycles / ns.count();
}

}

<commit_msg>time.cpp: TSC measurement - 500ms is enough<commit_after>#include "time.h"

#include <chrono>
#include <thread>

extern "C"
{
	#include <time.h>
}

namespace benchmark
{

namespace detail
{

double tsc_freq_ghz = .0;

}

std::chrono::nanoseconds operator-(timespec end, timespec start)
{
	return std::chrono::nanoseconds((int64_t)((end.tv_sec - start.tv_sec) * 1e9 + end.tv_nsec - start.tv_nsec));
}

void init()
{
	timespec start, end;
	clock_gettime(CLOCK_MONOTONIC_RAW, &start);

	uint64_t rdtsc_start = detail::rdtsc();
	std::this_thread::sleep_for(std::chrono::milliseconds(500));
	uint64_t rdtsc_end = detail::rdtsc();

	clock_gettime(CLOCK_MONOTONIC_RAW, &end);

	auto ns = end - start;
	uint64_t cycles = rdtsc_end - rdtsc_start;
	detail::tsc_freq_ghz = (double)cycles / ns.count();
}

}

<|endoftext|>