<commit_before>/*
    This file is part of Magnum.

    Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš <mosra@centrum.cz>

    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 <memory>
#include <unordered_map>

#include <PluginManager/Manager.h>
#include <DefaultFramebuffer.h>
#include <Mesh.h>
#include <Renderer.h>
#include <MeshTools/Tipsify.h>
#include <MeshTools/Interleave.h>
#include <MeshTools/CompressIndices.h>
#include <SceneGraph/Scene.h>
#include <SceneGraph/Camera3D.h>
#include <Shaders/Phong.h>
#include <Trade/AbstractImporter.h>
#include <Trade/MeshData3D.h>
#include <Trade/MeshObjectData3D.h>
#include <Trade/SceneData.h>

#ifndef MAGNUM_TARGET_GLES
#include <Platform/GlutApplication.h>
#else
#include <Platform/XEglApplication.h>
#endif

#include "ViewedObject.h"
#include "configure.h"

namespace Magnum { namespace Examples {

class ViewerExample: public Platform::Application {
    public:
        explicit ViewerExample(const Arguments& arguments);
        ~ViewerExample();

    protected:
        void viewportEvent(const Vector2i& size) override;
        void drawEvent() override;
        void keyPressEvent(KeyEvent& event) override;
        void mousePressEvent(MouseEvent& event) override;
        void mouseReleaseEvent(MouseEvent& event) override;
        void mouseMoveEvent(MouseMoveEvent& event) override;

    private:
        Vector3 positionOnSphere(const Vector2i& _position) const;

        void addObject(Trade::AbstractImporter* colladaImporter, Object3D* parent, std::unordered_map<std::size_t, Trade::PhongMaterialData*>& materials, std::size_t objectId);

        Scene3D scene;
        SceneGraph::DrawableGroup3D drawables;
        Object3D* cameraObject;
        SceneGraph::Camera3D* camera;
        Shaders::Phong shader;
        Object3D* o;
        std::unordered_map<std::size_t, std::tuple<Buffer*, Buffer*, Mesh*>> meshes;
        std::size_t vertexCount, triangleCount, objectCount, meshCount, materialCount;
        bool wireframe;
        Vector3 previousPosition;
};

ViewerExample::ViewerExample(const Arguments& arguments): Platform::Application(arguments, Configuration().setTitle("Magnum Viewer")), vertexCount(0), triangleCount(0), objectCount(0), meshCount(0), materialCount(0), wireframe(false) {
    if(arguments.argc != 2) {
        Debug() << "Usage:" << arguments.argv[0] << "file.dae";
        std::exit(0);
    }

    /* Instance ColladaImporter plugin */
    PluginManager::Manager<Trade::AbstractImporter> manager(MAGNUM_PLUGINS_IMPORTER_DIR);
    if(manager.load("ColladaImporter") != PluginManager::LoadState::Loaded) {
        Error() << "Could not load ColladaImporter plugin";
        std::exit(1);
    }
    std::unique_ptr<Trade::AbstractImporter> colladaImporter(manager.instance("ColladaImporter"));
    if(!colladaImporter) {
        Error() << "Could not instance ColladaImporter plugin";
        std::exit(2);
    }

    /* Every scene needs a camera */
    (cameraObject = new Object3D(&scene))
        ->translate(Vector3::zAxis(5));
    (camera = new SceneGraph::Camera3D(cameraObject))
        ->setAspectRatioPolicy(SceneGraph::AspectRatioPolicy::Extend)
        ->setPerspective(35.0_degf, 1.0f, 0.001f, 100);
    Renderer::setFeature(Renderer::Feature::DepthTest, true);
    Renderer::setFeature(Renderer::Feature::FaceCulling, true);

    Debug() << "Opening file" << arguments.argv[1];

    /* Load file */
    if(!colladaImporter->openFile(arguments.argv[1]))
        std::exit(4);

    if(colladaImporter->sceneCount() == 0)
        std::exit(5);

    /* Map with materials */
    std::unordered_map<std::size_t, Trade::PhongMaterialData*> materials;

    /* Default object, parent of all (for manipulation) */
    o = new Object3D(&scene);

    Debug() << "Adding default scene...";

    /* Load the scene */
    Trade::SceneData* scene = colladaImporter->scene(colladaImporter->defaultScene());

    /* Add all children */
    for(std::size_t objectId: scene->children3D())
        addObject(colladaImporter.get(), o, materials, objectId);

    Debug() << "Imported" << objectCount << "objects with" << meshCount << "meshes and" << materialCount << "materials,";
    Debug() << "    " << vertexCount << "vertices and" << triangleCount << "triangles total.";

    /* Delete materials, as they are now unused */
    for(auto i: materials) delete i.second;

    colladaImporter->close();
    delete colladaImporter.release();
}

ViewerExample::~ViewerExample() {
    for(auto i: meshes) {
        delete std::get<0>(i.second);
        delete std::get<1>(i.second);
        delete std::get<2>(i.second);
    }
}

void ViewerExample::viewportEvent(const Vector2i& size) {
    defaultFramebuffer.setViewport({{}, size});
    camera->setViewport(size);
}

void ViewerExample::drawEvent() {
    defaultFramebuffer.clear(FramebufferClear::Color|FramebufferClear::Depth);
    camera->draw(drawables);
    swapBuffers();
}

void ViewerExample::keyPressEvent(KeyEvent& event) {
    switch(event.key()) {
        case KeyEvent::Key::Up:
            o->rotateX(-10.0_degf);
            break;
        case KeyEvent::Key::Down:
            o->rotateX(10.0_degf);
            break;
        case KeyEvent::Key::Left:
            o->rotateY(-10.0_degf, SceneGraph::TransformationType::Local);
            break;
        case KeyEvent::Key::Right:
            o->rotateY(10.0_degf, SceneGraph::TransformationType::Local);
            break;
        case KeyEvent::Key::PageUp:
            cameraObject->translate(Vector3::zAxis(-0.5), SceneGraph::TransformationType::Local);
            break;
        case KeyEvent::Key::PageDown:
            cameraObject->translate(Vector3::zAxis(0.5), SceneGraph::TransformationType::Local);
            break;
        #ifndef MAGNUM_TARGET_GLES
        case KeyEvent::Key::Home:
            Renderer::setPolygonMode(wireframe ? Renderer::PolygonMode::Fill : Renderer::PolygonMode::Line);
            wireframe = !wireframe;
            break;
        #endif
        default: break;
    }

    redraw();
}

void ViewerExample::mousePressEvent(MouseEvent& event) {
    switch(event.button()) {
        case MouseEvent::Button::Left:
            previousPosition = positionOnSphere(event.position());
            break;
        case MouseEvent::Button::WheelUp:
        case MouseEvent::Button::WheelDown: {
            /* Distance between origin and near camera clipping plane */
            Float distance = cameraObject->transformation().translation().z()-0-camera->near();

            /* Move 15% of the distance back or forward */
            if(event.button() == MouseEvent::Button::WheelUp)
                distance *= 1 - 1/0.85f;
            else
                distance *= 1 - 0.85f;
            cameraObject->translate(Vector3::zAxis(distance), SceneGraph::TransformationType::Global);

            redraw();
            break;
        }
        default: ;
    }
}

void ViewerExample::mouseReleaseEvent(MouseEvent& event) {
    if(event.button() == MouseEvent::Button::Left)
        previousPosition = Vector3();
}

void ViewerExample::mouseMoveEvent(MouseMoveEvent& event) {
    Vector3 currentPosition = positionOnSphere(event.position());

    Vector3 axis = Vector3::cross(previousPosition, currentPosition);

    if(previousPosition.length() < 0.001f || axis.length() < 0.001f) return;

    o->rotate(Vector3::angle(previousPosition, currentPosition), axis.normalized());

    previousPosition = currentPosition;

    redraw();
}

Vector3 ViewerExample::positionOnSphere(const Vector2i& _position) const {
    Vector2i viewport = camera->viewport();
    Vector2 position(_position.x()*2.0f/viewport.x() - 1.0f,
                     _position.y()*2.0f/viewport.y() - 1.0f);

    Float length = position.length();
    Vector3 result(length > 1.0f ? Vector3(position, 0.0f) : Vector3(position, 1.0f - length));
    result.y() *= -1.0f;
    return result.normalized();
}

void ViewerExample::addObject(Trade::AbstractImporter* colladaImporter, Object3D* parent, std::unordered_map<std::size_t, Trade::PhongMaterialData*>& materials, std::size_t objectId) {
    Trade::ObjectData3D* object = colladaImporter->object3D(objectId);

    /* Only meshes for now */
    if(object->instanceType() == Trade::ObjectData3D::InstanceType::Mesh) {
        ++objectCount;

        /* Use already processed mesh, if exists */
        Mesh* mesh;
        auto found = meshes.find(object->instanceId());
        if(found != meshes.end()) mesh = std::get<2>(found->second);

        /* Or create a new one */
        else {
            ++meshCount;

            mesh = new Mesh;
            Buffer* buffer = new Buffer;
            Buffer* indexBuffer = new Buffer;
            meshes.insert(std::make_pair(object->instanceId(), std::make_tuple(buffer, indexBuffer, mesh)));

            Trade::MeshData3D* data = colladaImporter->mesh3D(object->instanceId());
            if(!data || !data->isIndexed() || !data->positionArrayCount() || !data->normalArrayCount())
                std::exit(6);

            vertexCount += data->positions(0).size();
            triangleCount += data->indices().size()/3;

            /* Optimize vertices */
            Debug() << "Optimizing vertices of mesh" << object->instanceId() << "using Tipsify algorithm (cache size 24)...";
            MeshTools::tipsify(data->indices(), data->positions(0).size(), 24);

            /* Interleave mesh data */
            MeshTools::interleave(mesh, buffer, Buffer::Usage::StaticDraw, data->positions(0), data->normals(0));
            mesh->addInterleavedVertexBuffer(buffer, 0, Shaders::Phong::Position(), Shaders::Phong::Normal());

            /* Compress indices */
            MeshTools::compressIndices(mesh, indexBuffer, Buffer::Usage::StaticDraw, data->indices());
            delete data;
        }

        /* Use already processed material, if exists */
        Trade::PhongMaterialData* material;
        auto materialFound = materials.find(static_cast<Trade::MeshObjectData3D*>(object)->material());
        if(materialFound != materials.end()) material = materialFound->second;

        /* Else get material or create default one */
        else {
            ++materialCount;

            material = static_cast<Trade::PhongMaterialData*>(colladaImporter->material(static_cast<Trade::MeshObjectData3D*>(object)->material()));
            if(!material) {
                material = new Trade::PhongMaterialData({}, 50.0f);
                material->ambientColor() = {0.0f, 0.0f, 0.0f};
                material->diffuseColor() = {0.9f, 0.9f, 0.9f};
                material->specularColor() = {1.0f, 1.0f, 1.0f};
            }
        }

        /* Add object */
        Object3D* o = new ViewedObject(mesh, material, &shader, parent, &drawables);
        delete material;
        o->setTransformation(object->transformation());
    }

    /* Recursively add children */
    for(std::size_t id: object->children())
        addObject(colladaImporter, o, materials, id);
}

}}

MAGNUM_APPLICATION_MAIN(Magnum::Examples::ViewerExample)
<commit_msg>viewer: no need to use std::unique_ptr.<commit_after>/*
    This file is part of Magnum.

    Copyright © 2010, 2011, 2012, 2013 Vladimír Vondruš <mosra@centrum.cz>

    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 <unordered_map>

#include <PluginManager/Manager.h>
#include <DefaultFramebuffer.h>
#include <Mesh.h>
#include <Renderer.h>
#include <MeshTools/Tipsify.h>
#include <MeshTools/Interleave.h>
#include <MeshTools/CompressIndices.h>
#include <SceneGraph/Scene.h>
#include <SceneGraph/Camera3D.h>
#include <Shaders/Phong.h>
#include <Trade/AbstractImporter.h>
#include <Trade/MeshData3D.h>
#include <Trade/MeshObjectData3D.h>
#include <Trade/SceneData.h>

#ifndef MAGNUM_TARGET_GLES
#include <Platform/GlutApplication.h>
#else
#include <Platform/XEglApplication.h>
#endif

#include "ViewedObject.h"
#include "configure.h"

namespace Magnum { namespace Examples {

class ViewerExample: public Platform::Application {
    public:
        explicit ViewerExample(const Arguments& arguments);
        ~ViewerExample();

    protected:
        void viewportEvent(const Vector2i& size) override;
        void drawEvent() override;
        void keyPressEvent(KeyEvent& event) override;
        void mousePressEvent(MouseEvent& event) override;
        void mouseReleaseEvent(MouseEvent& event) override;
        void mouseMoveEvent(MouseMoveEvent& event) override;

    private:
        Vector3 positionOnSphere(const Vector2i& _position) const;

        void addObject(Trade::AbstractImporter* colladaImporter, Object3D* parent, std::unordered_map<std::size_t, Trade::PhongMaterialData*>& materials, std::size_t objectId);

        Scene3D scene;
        SceneGraph::DrawableGroup3D drawables;
        Object3D* cameraObject;
        SceneGraph::Camera3D* camera;
        Shaders::Phong shader;
        Object3D* o;
        std::unordered_map<std::size_t, std::tuple<Buffer*, Buffer*, Mesh*>> meshes;
        std::size_t vertexCount, triangleCount, objectCount, meshCount, materialCount;
        bool wireframe;
        Vector3 previousPosition;
};

ViewerExample::ViewerExample(const Arguments& arguments): Platform::Application(arguments, Configuration().setTitle("Magnum Viewer")), vertexCount(0), triangleCount(0), objectCount(0), meshCount(0), materialCount(0), wireframe(false) {
    if(arguments.argc != 2) {
        Debug() << "Usage:" << arguments.argv[0] << "file.dae";
        std::exit(0);
    }

    /* Instance ColladaImporter plugin */
    PluginManager::Manager<Trade::AbstractImporter> manager(MAGNUM_PLUGINS_IMPORTER_DIR);
    if(manager.load("ColladaImporter") != PluginManager::LoadState::Loaded) {
        Error() << "Could not load ColladaImporter plugin";
        std::exit(1);
    }
    Trade::AbstractImporter* colladaImporter = manager.instance("ColladaImporter");
    if(!colladaImporter) {
        Error() << "Could not instance ColladaImporter plugin";
        std::exit(2);
    }

    /* Every scene needs a camera */
    (cameraObject = new Object3D(&scene))
        ->translate(Vector3::zAxis(5));
    (camera = new SceneGraph::Camera3D(cameraObject))
        ->setAspectRatioPolicy(SceneGraph::AspectRatioPolicy::Extend)
        ->setPerspective(35.0_degf, 1.0f, 0.001f, 100);
    Renderer::setFeature(Renderer::Feature::DepthTest, true);
    Renderer::setFeature(Renderer::Feature::FaceCulling, true);

    Debug() << "Opening file" << arguments.argv[1];

    /* Load file */
    if(!colladaImporter->openFile(arguments.argv[1])) {
        delete colladaImporter;
        std::exit(4);
    }

    if(!colladaImporter->sceneCount()) {
        delete colladaImporter;
        std::exit(5);
    }

    /* Map with materials */
    std::unordered_map<std::size_t, Trade::PhongMaterialData*> materials;

    /* Default object, parent of all (for manipulation) */
    o = new Object3D(&scene);

    Debug() << "Adding default scene...";

    /* Load the scene */
    Trade::SceneData* scene = colladaImporter->scene(colladaImporter->defaultScene());

    /* Add all children */
    for(std::size_t objectId: scene->children3D())
        addObject(colladaImporter, o, materials, objectId);

    Debug() << "Imported" << objectCount << "objects with" << meshCount << "meshes and" << materialCount << "materials,";
    Debug() << "    " << vertexCount << "vertices and" << triangleCount << "triangles total.";

    /* Delete materials, as they are now unused */
    for(auto i: materials) delete i.second;

    delete colladaImporter;
}

ViewerExample::~ViewerExample() {
    for(auto i: meshes) {
        delete std::get<0>(i.second);
        delete std::get<1>(i.second);
        delete std::get<2>(i.second);
    }
}

void ViewerExample::viewportEvent(const Vector2i& size) {
    defaultFramebuffer.setViewport({{}, size});
    camera->setViewport(size);
}

void ViewerExample::drawEvent() {
    defaultFramebuffer.clear(FramebufferClear::Color|FramebufferClear::Depth);
    camera->draw(drawables);
    swapBuffers();
}

void ViewerExample::keyPressEvent(KeyEvent& event) {
    switch(event.key()) {
        case KeyEvent::Key::Up:
            o->rotateX(-10.0_degf);
            break;
        case KeyEvent::Key::Down:
            o->rotateX(10.0_degf);
            break;
        case KeyEvent::Key::Left:
            o->rotateY(-10.0_degf, SceneGraph::TransformationType::Local);
            break;
        case KeyEvent::Key::Right:
            o->rotateY(10.0_degf, SceneGraph::TransformationType::Local);
            break;
        case KeyEvent::Key::PageUp:
            cameraObject->translate(Vector3::zAxis(-0.5), SceneGraph::TransformationType::Local);
            break;
        case KeyEvent::Key::PageDown:
            cameraObject->translate(Vector3::zAxis(0.5), SceneGraph::TransformationType::Local);
            break;
        #ifndef MAGNUM_TARGET_GLES
        case KeyEvent::Key::Home:
            Renderer::setPolygonMode(wireframe ? Renderer::PolygonMode::Fill : Renderer::PolygonMode::Line);
            wireframe = !wireframe;
            break;
        #endif
        default: break;
    }

    redraw();
}

void ViewerExample::mousePressEvent(MouseEvent& event) {
    switch(event.button()) {
        case MouseEvent::Button::Left:
            previousPosition = positionOnSphere(event.position());
            break;
        case MouseEvent::Button::WheelUp:
        case MouseEvent::Button::WheelDown: {
            /* Distance between origin and near camera clipping plane */
            Float distance = cameraObject->transformation().translation().z()-0-camera->near();

            /* Move 15% of the distance back or forward */
            if(event.button() == MouseEvent::Button::WheelUp)
                distance *= 1 - 1/0.85f;
            else
                distance *= 1 - 0.85f;
            cameraObject->translate(Vector3::zAxis(distance), SceneGraph::TransformationType::Global);

            redraw();
            break;
        }
        default: ;
    }
}

void ViewerExample::mouseReleaseEvent(MouseEvent& event) {
    if(event.button() == MouseEvent::Button::Left)
        previousPosition = Vector3();
}

void ViewerExample::mouseMoveEvent(MouseMoveEvent& event) {
    Vector3 currentPosition = positionOnSphere(event.position());

    Vector3 axis = Vector3::cross(previousPosition, currentPosition);

    if(previousPosition.length() < 0.001f || axis.length() < 0.001f) return;

    o->rotate(Vector3::angle(previousPosition, currentPosition), axis.normalized());

    previousPosition = currentPosition;

    redraw();
}

Vector3 ViewerExample::positionOnSphere(const Vector2i& _position) const {
    Vector2i viewport = camera->viewport();
    Vector2 position(_position.x()*2.0f/viewport.x() - 1.0f,
                     _position.y()*2.0f/viewport.y() - 1.0f);

    Float length = position.length();
    Vector3 result(length > 1.0f ? Vector3(position, 0.0f) : Vector3(position, 1.0f - length));
    result.y() *= -1.0f;
    return result.normalized();
}

void ViewerExample::addObject(Trade::AbstractImporter* colladaImporter, Object3D* parent, std::unordered_map<std::size_t, Trade::PhongMaterialData*>& materials, std::size_t objectId) {
    Trade::ObjectData3D* object = colladaImporter->object3D(objectId);

    /* Only meshes for now */
    if(object->instanceType() == Trade::ObjectData3D::InstanceType::Mesh) {
        ++objectCount;

        /* Use already processed mesh, if exists */
        Mesh* mesh;
        auto found = meshes.find(object->instanceId());
        if(found != meshes.end()) mesh = std::get<2>(found->second);

        /* Or create a new one */
        else {
            ++meshCount;

            mesh = new Mesh;
            Buffer* buffer = new Buffer;
            Buffer* indexBuffer = new Buffer;
            meshes.insert(std::make_pair(object->instanceId(), std::make_tuple(buffer, indexBuffer, mesh)));

            Trade::MeshData3D* data = colladaImporter->mesh3D(object->instanceId());
            if(!data || !data->isIndexed() || !data->positionArrayCount() || !data->normalArrayCount())
                std::exit(6);

            vertexCount += data->positions(0).size();
            triangleCount += data->indices().size()/3;

            /* Optimize vertices */
            Debug() << "Optimizing vertices of mesh" << object->instanceId() << "using Tipsify algorithm (cache size 24)...";
            MeshTools::tipsify(data->indices(), data->positions(0).size(), 24);

            /* Interleave mesh data */
            MeshTools::interleave(mesh, buffer, Buffer::Usage::StaticDraw, data->positions(0), data->normals(0));
            mesh->addInterleavedVertexBuffer(buffer, 0, Shaders::Phong::Position(), Shaders::Phong::Normal());

            /* Compress indices */
            MeshTools::compressIndices(mesh, indexBuffer, Buffer::Usage::StaticDraw, data->indices());
            delete data;
        }

        /* Use already processed material, if exists */
        Trade::PhongMaterialData* material;
        auto materialFound = materials.find(static_cast<Trade::MeshObjectData3D*>(object)->material());
        if(materialFound != materials.end()) material = materialFound->second;

        /* Else get material or create default one */
        else {
            ++materialCount;

            material = static_cast<Trade::PhongMaterialData*>(colladaImporter->material(static_cast<Trade::MeshObjectData3D*>(object)->material()));
            if(!material) {
                material = new Trade::PhongMaterialData({}, 50.0f);
                material->ambientColor() = {0.0f, 0.0f, 0.0f};
                material->diffuseColor() = {0.9f, 0.9f, 0.9f};
                material->specularColor() = {1.0f, 1.0f, 1.0f};
            }
        }

        /* Add object */
        Object3D* o = new ViewedObject(mesh, material, &shader, parent, &drawables);
        delete material;
        o->setTransformation(object->transformation());
    }

    /* Recursively add children */
    for(std::size_t id: object->children())
        addObject(colladaImporter, o, materials, id);
}

}}

MAGNUM_APPLICATION_MAIN(Magnum::Examples::ViewerExample)
<|endoftext|>


<commit_before>#include "ViewController.h"
#include "../Log.h"
#include "../SystemData.h"
#include "../Settings.h"

#include "gamelist/BasicGameListView.h"
#include "gamelist/DetailedGameListView.h"
#include "gamelist/GridGameListView.h"
#include "../guis/GuiMenu.h"
#include "../guis/GuiMsgBox.h"
#include "../animations/LaunchAnimation.h"
#include "../animations/MoveCameraAnimation.h"
#include "../animations/LambdaAnimation.h"

ViewController::ViewController(Window* window)
	: GuiComponent(window), mCurrentView(nullptr), mCamera(Eigen::Affine3f::Identity()), mFadeOpacity(0), mLockInput(false)
{
	mState.viewing = NOTHING;
}

void ViewController::goToStart()
{
	// TODO
	/* mState.viewing = START_SCREEN;
	mCurrentView.reset();
	playViewTransition(); */
	goToSystemView(SystemData::sSystemVector.at(0));
}

int ViewController::getSystemId(SystemData* system)
{
	std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
	return std::find(sysVec.begin(), sysVec.end(), system) - sysVec.begin();
}

void ViewController::goToSystemView(SystemData* system)
{
	mState.viewing = SYSTEM_SELECT;
	mState.system = system;

	auto systemList = getSystemListView();
	systemList->setPosition(getSystemId(system) * (float)Renderer::getScreenWidth(), systemList->getPosition().y());

	systemList->goToSystem(system, false);
	mCurrentView = systemList;

	playViewTransition();
}

void ViewController::goToNextGameList()
{
	assert(mState.viewing == GAME_LIST);
	SystemData* system = getState().getSystem();
	assert(system);
	goToGameList(system->getNext());
}

void ViewController::goToPrevGameList()
{
	assert(mState.viewing == GAME_LIST);
	SystemData* system = getState().getSystem();
	assert(system);
	goToGameList(system->getPrev());
}

void ViewController::goToGameList(SystemData* system)
{
	if(mState.viewing == SYSTEM_SELECT)
	{
		// move system list
		auto sysList = getSystemListView();
		float offX = sysList->getPosition().x();
		int sysId = getSystemId(system);
		sysList->setPosition(sysId * (float)Renderer::getScreenWidth(), sysList->getPosition().y());
		offX = sysList->getPosition().x() - offX;
		mCamera.translation().x() -= offX;
	}

	mState.viewing = GAME_LIST;
	mState.system = system;

	mCurrentView = getGameListView(system);
	playViewTransition();
}

void ViewController::playViewTransition()
{
	Eigen::Vector3f target(Eigen::Vector3f::Identity());
	if(mCurrentView)
		target = mCurrentView->getPosition();

	// no need to animate, we're not going anywhere (probably goToNextGamelist() or goToPrevGamelist() when there's only 1 system)
	if(target == -mCamera.translation())
		return;

	if(Settings::getInstance()->getString("TransitionStyle") == "fade")
	{
		// fade
		// stop whatever's currently playing, leaving mFadeOpacity wherever it is
		cancelAnimation(0);

		auto fadeFunc = [this](float t) {
			mFadeOpacity = lerp<float>(0, 1, t);
		};

		const static int FADE_DURATION = 240; // fade in/out time
		const static int FADE_WAIT = 320; // time to wait between in/out
		setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), 0, [this, fadeFunc, target] {
			this->mCamera.translation() = -target;
			updateHelpPrompts();
			setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), FADE_WAIT, nullptr, true);
		});

		// fast-forward animation if we're partway faded
		if(target == -mCamera.translation())
		{
			// not changing screens, so cancel the first half entirely
			advanceAnimation(0, FADE_DURATION);
			advanceAnimation(0, FADE_WAIT);
			advanceAnimation(0, FADE_DURATION - (int)(mFadeOpacity * FADE_DURATION));
		}else{
			advanceAnimation(0, (int)(mFadeOpacity * FADE_DURATION));
		}
	}else{
		// slide
		setAnimation(new MoveCameraAnimation(mCamera, target));
		updateHelpPrompts(); // update help prompts immediately
	}
}

void ViewController::onFileChanged(FileData* file, FileChangeType change)
{
	auto it = mGameListViews.find(file->getSystem());
	if(it != mGameListViews.end())
		it->second->onFileChanged(file, change);
}

void ViewController::launch(FileData* game, Eigen::Vector3f center)
{
	if(game->getType() != GAME)
	{
		LOG(LogError) << "tried to launch something that isn't a game";
		return;
	}

	Eigen::Affine3f origCamera = mCamera;
	origCamera.translation() = -mCurrentView->getPosition();

	center += mCurrentView->getPosition();
	stopAnimation(1); // make sure the fade in isn't still playing
	mLockInput = true;

	if(Settings::getInstance()->getString("TransitionStyle") == "fade")
	{
		// fade out, launch game, fade back in
		auto fadeFunc = [this](float t) {
			//t -= 1;
			//mFadeOpacity = lerp<float>(0.0f, 1.0f, t*t*t + 1);
			mFadeOpacity = lerp<float>(0.0f, 1.0f, t);
		};
		setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc]
		{
			game->getSystem()->launchGame(mWindow, game);
			mLockInput = false;
			setAnimation(new LambdaAnimation(fadeFunc, 800), 0, nullptr, true);
			this->onFileChanged(game, FILE_METADATA_CHANGED);
		});
	}else{
		// move camera to zoom in on center + fade out, launch game, come back in
		setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 1500), 0, [this, origCamera, center, game]
		{
			game->getSystem()->launchGame(mWindow, game);
			mCamera = origCamera;
			mLockInput = false;
			setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, nullptr, true);
			this->onFileChanged(game, FILE_METADATA_CHANGED);
		});
	}
}

std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* system)
{
	//if we already made one, return that one
	auto exists = mGameListViews.find(system);
	if(exists != mGameListViews.end())
		return exists->second;

	//if we didn't, make it, remember it, and return it
	std::shared_ptr<IGameListView> view;

	//decide type
	bool detailed = false;
	std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
	for(auto it = files.begin(); it != files.end(); it++)
	{
		if(!(*it)->getThumbnailPath().empty())
		{
			detailed = true;
			break;
		}
	}

	if(detailed)
		view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
	else
		view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));

	// uncomment for experimental "image grid" view
	//view = std::shared_ptr<IGameListView>(new GridGameListView(mWindow, system->getRootFolder()));

	view->setTheme(system->getTheme());

	std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
	int id = std::find(sysVec.begin(), sysVec.end(), system) - sysVec.begin();
	view->setPosition(id * (float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight() * 2);

	mGameListViews[system] = view;
	return view;
}

std::shared_ptr<SystemView> ViewController::getSystemListView()
{
	//if we already made one, return that one
	if(mSystemListView)
		return mSystemListView;

	mSystemListView = std::shared_ptr<SystemView>(new SystemView(mWindow));
	mSystemListView->setPosition(0, (float)Renderer::getScreenHeight());
	return mSystemListView;
}

bool ViewController::input(InputConfig* config, Input input)
{
	if(mLockInput)
		return true;

	// open menu
	if(config->isMappedTo("start", input) && input.value != 0)
	{
		// open menu
		mWindow->pushGui(new GuiMenu(mWindow));
		return true;
	}

	if(mCurrentView)
		return mCurrentView->input(config, input);

	return false;
}

void ViewController::update(int deltaTime)
{
	if(mCurrentView)
	{
		mCurrentView->update(deltaTime);
	}

	GuiComponent::update(deltaTime);
}

void ViewController::render(const Eigen::Affine3f& parentTrans)
{
	Eigen::Affine3f trans = mCamera * parentTrans;

	// camera position, position + size
	Eigen::Vector3f viewStart = trans.inverse().translation();
	Eigen::Vector3f viewEnd = trans.inverse() * Eigen::Vector3f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight(), 0);

	// draw systemview
	getSystemListView()->render(trans);

	// draw gamelists
	for(auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
	{
		// clipping
		Eigen::Vector3f guiStart = it->second->getPosition();
		Eigen::Vector3f guiEnd = it->second->getPosition() + Eigen::Vector3f(it->second->getSize().x(), it->second->getSize().y(), 0);

		if(guiEnd.x() >= viewStart.x() && guiEnd.y() >= viewStart.y() &&
			guiStart.x() <= viewEnd.x() && guiStart.y() <= viewEnd.y())
				it->second->render(trans);
	}

	if(mWindow->peekGui() == this)
		mWindow->renderHelpPromptsEarly();

	// fade out
	if(mFadeOpacity)
	{
		Renderer::setMatrix(parentTrans);
		Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x00000000 | (unsigned char)(mFadeOpacity * 255));
	}
}

void ViewController::preload()
{
	for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++)
	{
		getGameListView(*it);
	}
}

void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
{
	for(auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
	{
		if(it->second.get() == view)
		{
			bool isCurrent = (mCurrentView == it->second);
			SystemData* system = it->first;
			FileData* cursor = view->getCursor();
			mGameListViews.erase(it);

			if(reloadTheme)
				system->loadTheme();

			std::shared_ptr<IGameListView> newView = getGameListView(system);
			newView->setCursor(cursor);

			if(isCurrent)
				mCurrentView = newView;

			break;
		}
	}
}

void ViewController::reloadAll()
{
	std::map<SystemData*, FileData*> cursorMap;
	for(auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
	{
		cursorMap[it->first] = it->second->getCursor();
	}
	mGameListViews.clear();

	for(auto it = cursorMap.begin(); it != cursorMap.end(); it++)
	{
		it->first->loadTheme();
		getGameListView(it->first)->setCursor(it->second);
	}

	mSystemListView.reset();
	getSystemListView();

	// update mCurrentView since the pointers changed
	if(mState.viewing == GAME_LIST)
	{
		mCurrentView = getGameListView(mState.getSystem());
	}else if(mState.viewing == SYSTEM_SELECT)
	{
		mSystemListView->goToSystem(mState.getSystem(), false);
		mCurrentView = mSystemListView;
	}else{
		goToSystemView(SystemData::sSystemVector.front());
	}
}

std::vector<HelpPrompt> ViewController::getHelpPrompts()
{
	std::vector<HelpPrompt> prompts;
	if(!mCurrentView)
		return prompts;

	prompts = mCurrentView->getHelpPrompts();
	prompts.push_back(HelpPrompt("start", "menu"));

	return prompts;
}

HelpStyle ViewController::getHelpStyle()
{
	if(!mCurrentView)
		return GuiComponent::getHelpStyle();

	return mCurrentView->getHelpStyle();
}
<commit_msg>Fix for rapid ViewController fade transitions causing the camera to get "lost."<commit_after>#include "ViewController.h"
#include "../Log.h"
#include "../SystemData.h"
#include "../Settings.h"

#include "gamelist/BasicGameListView.h"
#include "gamelist/DetailedGameListView.h"
#include "gamelist/GridGameListView.h"
#include "../guis/GuiMenu.h"
#include "../guis/GuiMsgBox.h"
#include "../animations/LaunchAnimation.h"
#include "../animations/MoveCameraAnimation.h"
#include "../animations/LambdaAnimation.h"

ViewController::ViewController(Window* window)
	: GuiComponent(window), mCurrentView(nullptr), mCamera(Eigen::Affine3f::Identity()), mFadeOpacity(0), mLockInput(false)
{
	mState.viewing = NOTHING;
}

void ViewController::goToStart()
{
	// TODO
	/* mState.viewing = START_SCREEN;
	mCurrentView.reset();
	playViewTransition(); */
	goToSystemView(SystemData::sSystemVector.at(0));
}

int ViewController::getSystemId(SystemData* system)
{
	std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
	return std::find(sysVec.begin(), sysVec.end(), system) - sysVec.begin();
}

void ViewController::goToSystemView(SystemData* system)
{
	mState.viewing = SYSTEM_SELECT;
	mState.system = system;

	auto systemList = getSystemListView();
	systemList->setPosition(getSystemId(system) * (float)Renderer::getScreenWidth(), systemList->getPosition().y());

	systemList->goToSystem(system, false);
	mCurrentView = systemList;

	playViewTransition();
}

void ViewController::goToNextGameList()
{
	assert(mState.viewing == GAME_LIST);
	SystemData* system = getState().getSystem();
	assert(system);
	goToGameList(system->getNext());
}

void ViewController::goToPrevGameList()
{
	assert(mState.viewing == GAME_LIST);
	SystemData* system = getState().getSystem();
	assert(system);
	goToGameList(system->getPrev());
}

void ViewController::goToGameList(SystemData* system)
{
	if(mState.viewing == SYSTEM_SELECT)
	{
		// move system list
		auto sysList = getSystemListView();
		float offX = sysList->getPosition().x();
		int sysId = getSystemId(system);
		sysList->setPosition(sysId * (float)Renderer::getScreenWidth(), sysList->getPosition().y());
		offX = sysList->getPosition().x() - offX;
		mCamera.translation().x() -= offX;
	}

	mState.viewing = GAME_LIST;
	mState.system = system;

	mCurrentView = getGameListView(system);
	playViewTransition();
}

void ViewController::playViewTransition()
{
	Eigen::Vector3f target(Eigen::Vector3f::Identity());
	if(mCurrentView)
		target = mCurrentView->getPosition();

	// no need to animate, we're not going anywhere (probably goToNextGamelist() or goToPrevGamelist() when there's only 1 system)
	if(target == -mCamera.translation() && !isAnimationPlaying(0))
		return;

	if(Settings::getInstance()->getString("TransitionStyle") == "fade")
	{
		// fade
		// stop whatever's currently playing, leaving mFadeOpacity wherever it is
		cancelAnimation(0);

		auto fadeFunc = [this](float t) {
			mFadeOpacity = lerp<float>(0, 1, t);
		};

		const static int FADE_DURATION = 240; // fade in/out time
		const static int FADE_WAIT = 320; // time to wait between in/out
		setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), 0, [this, fadeFunc, target] {
			this->mCamera.translation() = -target;
			updateHelpPrompts();
			setAnimation(new LambdaAnimation(fadeFunc, FADE_DURATION), FADE_WAIT, nullptr, true);
		});

		// fast-forward animation if we're partway faded
		if(target == -mCamera.translation())
		{
			// not changing screens, so cancel the first half entirely
			advanceAnimation(0, FADE_DURATION);
			advanceAnimation(0, FADE_WAIT);
			advanceAnimation(0, FADE_DURATION - (int)(mFadeOpacity * FADE_DURATION));
		}else{
			advanceAnimation(0, (int)(mFadeOpacity * FADE_DURATION));
		}
	}else{
		// slide
		setAnimation(new MoveCameraAnimation(mCamera, target));
		updateHelpPrompts(); // update help prompts immediately
	}
}

void ViewController::onFileChanged(FileData* file, FileChangeType change)
{
	auto it = mGameListViews.find(file->getSystem());
	if(it != mGameListViews.end())
		it->second->onFileChanged(file, change);
}

void ViewController::launch(FileData* game, Eigen::Vector3f center)
{
	if(game->getType() != GAME)
	{
		LOG(LogError) << "tried to launch something that isn't a game";
		return;
	}

	Eigen::Affine3f origCamera = mCamera;
	origCamera.translation() = -mCurrentView->getPosition();

	center += mCurrentView->getPosition();
	stopAnimation(1); // make sure the fade in isn't still playing
	mLockInput = true;

	if(Settings::getInstance()->getString("TransitionStyle") == "fade")
	{
		// fade out, launch game, fade back in
		auto fadeFunc = [this](float t) {
			//t -= 1;
			//mFadeOpacity = lerp<float>(0.0f, 1.0f, t*t*t + 1);
			mFadeOpacity = lerp<float>(0.0f, 1.0f, t);
		};
		setAnimation(new LambdaAnimation(fadeFunc, 800), 0, [this, game, fadeFunc]
		{
			game->getSystem()->launchGame(mWindow, game);
			mLockInput = false;
			setAnimation(new LambdaAnimation(fadeFunc, 800), 0, nullptr, true);
			this->onFileChanged(game, FILE_METADATA_CHANGED);
		});
	}else{
		// move camera to zoom in on center + fade out, launch game, come back in
		setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 1500), 0, [this, origCamera, center, game]
		{
			game->getSystem()->launchGame(mWindow, game);
			mCamera = origCamera;
			mLockInput = false;
			setAnimation(new LaunchAnimation(mCamera, mFadeOpacity, center, 600), 0, nullptr, true);
			this->onFileChanged(game, FILE_METADATA_CHANGED);
		});
	}
}

std::shared_ptr<IGameListView> ViewController::getGameListView(SystemData* system)
{
	//if we already made one, return that one
	auto exists = mGameListViews.find(system);
	if(exists != mGameListViews.end())
		return exists->second;

	//if we didn't, make it, remember it, and return it
	std::shared_ptr<IGameListView> view;

	//decide type
	bool detailed = false;
	std::vector<FileData*> files = system->getRootFolder()->getFilesRecursive(GAME | FOLDER);
	for(auto it = files.begin(); it != files.end(); it++)
	{
		if(!(*it)->getThumbnailPath().empty())
		{
			detailed = true;
			break;
		}
	}

	if(detailed)
		view = std::shared_ptr<IGameListView>(new DetailedGameListView(mWindow, system->getRootFolder()));
	else
		view = std::shared_ptr<IGameListView>(new BasicGameListView(mWindow, system->getRootFolder()));

	// uncomment for experimental "image grid" view
	//view = std::shared_ptr<IGameListView>(new GridGameListView(mWindow, system->getRootFolder()));

	view->setTheme(system->getTheme());

	std::vector<SystemData*>& sysVec = SystemData::sSystemVector;
	int id = std::find(sysVec.begin(), sysVec.end(), system) - sysVec.begin();
	view->setPosition(id * (float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight() * 2);

	mGameListViews[system] = view;
	return view;
}

std::shared_ptr<SystemView> ViewController::getSystemListView()
{
	//if we already made one, return that one
	if(mSystemListView)
		return mSystemListView;

	mSystemListView = std::shared_ptr<SystemView>(new SystemView(mWindow));
	mSystemListView->setPosition(0, (float)Renderer::getScreenHeight());
	return mSystemListView;
}

bool ViewController::input(InputConfig* config, Input input)
{
	if(mLockInput)
		return true;

	// open menu
	if(config->isMappedTo("start", input) && input.value != 0)
	{
		// open menu
		mWindow->pushGui(new GuiMenu(mWindow));
		return true;
	}

	if(mCurrentView)
		return mCurrentView->input(config, input);

	return false;
}

void ViewController::update(int deltaTime)
{
	if(mCurrentView)
	{
		mCurrentView->update(deltaTime);
	}

	GuiComponent::update(deltaTime);
}

void ViewController::render(const Eigen::Affine3f& parentTrans)
{
	Eigen::Affine3f trans = mCamera * parentTrans;

	// camera position, position + size
	Eigen::Vector3f viewStart = trans.inverse().translation();
	Eigen::Vector3f viewEnd = trans.inverse() * Eigen::Vector3f((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight(), 0);

	// draw systemview
	getSystemListView()->render(trans);

	// draw gamelists
	for(auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
	{
		// clipping
		Eigen::Vector3f guiStart = it->second->getPosition();
		Eigen::Vector3f guiEnd = it->second->getPosition() + Eigen::Vector3f(it->second->getSize().x(), it->second->getSize().y(), 0);

		if(guiEnd.x() >= viewStart.x() && guiEnd.y() >= viewStart.y() &&
			guiStart.x() <= viewEnd.x() && guiStart.y() <= viewEnd.y())
				it->second->render(trans);
	}

	if(mWindow->peekGui() == this)
		mWindow->renderHelpPromptsEarly();

	// fade out
	if(mFadeOpacity)
	{
		Renderer::setMatrix(parentTrans);
		Renderer::drawRect(0, 0, Renderer::getScreenWidth(), Renderer::getScreenHeight(), 0x00000000 | (unsigned char)(mFadeOpacity * 255));
	}
}

void ViewController::preload()
{
	for(auto it = SystemData::sSystemVector.begin(); it != SystemData::sSystemVector.end(); it++)
	{
		getGameListView(*it);
	}
}

void ViewController::reloadGameListView(IGameListView* view, bool reloadTheme)
{
	for(auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
	{
		if(it->second.get() == view)
		{
			bool isCurrent = (mCurrentView == it->second);
			SystemData* system = it->first;
			FileData* cursor = view->getCursor();
			mGameListViews.erase(it);

			if(reloadTheme)
				system->loadTheme();

			std::shared_ptr<IGameListView> newView = getGameListView(system);
			newView->setCursor(cursor);

			if(isCurrent)
				mCurrentView = newView;

			break;
		}
	}
}

void ViewController::reloadAll()
{
	std::map<SystemData*, FileData*> cursorMap;
	for(auto it = mGameListViews.begin(); it != mGameListViews.end(); it++)
	{
		cursorMap[it->first] = it->second->getCursor();
	}
	mGameListViews.clear();

	for(auto it = cursorMap.begin(); it != cursorMap.end(); it++)
	{
		it->first->loadTheme();
		getGameListView(it->first)->setCursor(it->second);
	}

	mSystemListView.reset();
	getSystemListView();

	// update mCurrentView since the pointers changed
	if(mState.viewing == GAME_LIST)
	{
		mCurrentView = getGameListView(mState.getSystem());
	}else if(mState.viewing == SYSTEM_SELECT)
	{
		mSystemListView->goToSystem(mState.getSystem(), false);
		mCurrentView = mSystemListView;
	}else{
		goToSystemView(SystemData::sSystemVector.front());
	}
}

std::vector<HelpPrompt> ViewController::getHelpPrompts()
{
	std::vector<HelpPrompt> prompts;
	if(!mCurrentView)
		return prompts;

	prompts = mCurrentView->getHelpPrompts();
	prompts.push_back(HelpPrompt("start", "menu"));

	return prompts;
}

HelpStyle ViewController::getHelpStyle()
{
	if(!mCurrentView)
		return GuiComponent::getHelpStyle();

	return mCurrentView->getHelpStyle();
}
<|endoftext|>


<commit_before>// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/lib/ui/painting/single_frame_codec.h"

#include "flutter/lib/ui/painting/frame_info.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/tonic/logging/dart_invoke.h"

namespace flutter {

SingleFrameCodec::SingleFrameCodec(ImageDecoder::ImageDescriptor descriptor)
    : status_(Status::kNew), descriptor_(std::move(descriptor)) {}

SingleFrameCodec::~SingleFrameCodec() = default;

int SingleFrameCodec::frameCount() const {
  return 1;
}

int SingleFrameCodec::repetitionCount() const {
  return 0;
}

Dart_Handle SingleFrameCodec::getNextFrame(Dart_Handle callback_handle) {
  if (!Dart_IsClosure(callback_handle)) {
    return tonic::ToDart("Callback must be a function");
  }

  if (status_ == Status::kComplete) {
    tonic::DartInvoke(callback_handle, {tonic::ToDart(cached_frame_)});
    return Dart_Null();
  }

  // This has to be valid because this method is called from Dart.
  auto dart_state = UIDartState::Current();

  pending_callbacks_.emplace_back(dart_state, callback_handle);

  if (status_ == Status::kInProgress) {
    // Another call to getNextFrame is in progress and will invoke the
    // pending callbacks when decoding completes.
    return Dart_Null();
  }

  auto decoder = dart_state->GetImageDecoder();

  if (!decoder) {
    return tonic::ToDart("Image decoder not available.");
  }

  auto raw_codec_wrapper = new DartPersistentValue(
      dart_state, Dart_HandleFromWeakPersistent(dart_wrapper()));

  // We dont want to to put the raw codec in a lambda capture because we have
  // to mutate (i.e destroy) it in the callback. Using MakeCopyable will create
  // a shared pointer for the captures which can be destroyed on any thread. But
  // we have to ensure that the DartPersistentValue is only destroyed on the UI
  // thread.
  decoder->Decode(descriptor_, [raw_codec_wrapper](auto image) {
    std::unique_ptr<DartPersistentValue> codec_wrapper(raw_codec_wrapper);

    auto state = codec_wrapper->dart_state().lock();

    if (!state) {
      // This is probably because the isolate has been terminated before the
      // image could be decoded.

      return;
    }

    tonic::DartState::Scope scope(state.get());

    SingleFrameCodec* codec = tonic::DartConverter<SingleFrameCodec*>::FromDart(
        codec_wrapper->value());

    if (image.get()) {
      auto canvas_image = fml::MakeRefCounted<CanvasImage>();
      canvas_image->set_image(std::move(image));

      codec->cached_frame_ = fml::MakeRefCounted<FrameInfo>(
          std::move(canvas_image), 0 /* duration */);
    }

    Dart_Handle frame = tonic::ToDart(codec->cached_frame_);
    for (const DartPersistentValue& callback : codec->pending_callbacks_) {
      tonic::DartInvoke(callback.value(), {frame});
    }
    codec->pending_callbacks_.clear();

    codec->status_ = Status::kComplete;
  });

  // The encoded data is no longer needed now that it has been handed off
  // to the decoder.
  descriptor_.data.reset();

  status_ = Status::kInProgress;

  return Dart_Null();
}

size_t SingleFrameCodec::GetAllocationSize() {
  const auto& data = descriptor_.data;
  const auto data_byte_size = data ? data->size() : 0;
  const auto frame_byte_size = (cached_frame_ && cached_frame_->image())
                                   ? cached_frame_->image()->GetAllocationSize()
                                   : 0;
  return data_byte_size + frame_byte_size + sizeof(this);
}

}  // namespace flutter
<commit_msg>Fix reentrancy handling in SingleFrameCodec (#9931)<commit_after>// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/lib/ui/painting/single_frame_codec.h"

#include "flutter/lib/ui/painting/frame_info.h"
#include "flutter/lib/ui/ui_dart_state.h"
#include "third_party/tonic/logging/dart_invoke.h"

namespace flutter {

SingleFrameCodec::SingleFrameCodec(ImageDecoder::ImageDescriptor descriptor)
    : status_(Status::kNew), descriptor_(std::move(descriptor)) {}

SingleFrameCodec::~SingleFrameCodec() = default;

int SingleFrameCodec::frameCount() const {
  return 1;
}

int SingleFrameCodec::repetitionCount() const {
  return 0;
}

Dart_Handle SingleFrameCodec::getNextFrame(Dart_Handle callback_handle) {
  if (!Dart_IsClosure(callback_handle)) {
    return tonic::ToDart("Callback must be a function");
  }

  if (status_ == Status::kComplete) {
    tonic::DartInvoke(callback_handle, {tonic::ToDart(cached_frame_)});
    return Dart_Null();
  }

  // This has to be valid because this method is called from Dart.
  auto dart_state = UIDartState::Current();

  pending_callbacks_.emplace_back(dart_state, callback_handle);

  if (status_ == Status::kInProgress) {
    // Another call to getNextFrame is in progress and will invoke the
    // pending callbacks when decoding completes.
    return Dart_Null();
  }

  auto decoder = dart_state->GetImageDecoder();

  if (!decoder) {
    return tonic::ToDart("Image decoder not available.");
  }

  auto raw_codec_wrapper = new DartPersistentValue(
      dart_state, Dart_HandleFromWeakPersistent(dart_wrapper()));

  // We dont want to to put the raw codec in a lambda capture because we have
  // to mutate (i.e destroy) it in the callback. Using MakeCopyable will create
  // a shared pointer for the captures which can be destroyed on any thread. But
  // we have to ensure that the DartPersistentValue is only destroyed on the UI
  // thread.
  decoder->Decode(descriptor_, [raw_codec_wrapper](auto image) {
    std::unique_ptr<DartPersistentValue> codec_wrapper(raw_codec_wrapper);

    auto state = codec_wrapper->dart_state().lock();

    if (!state) {
      // This is probably because the isolate has been terminated before the
      // image could be decoded.

      return;
    }

    tonic::DartState::Scope scope(state.get());

    SingleFrameCodec* codec = tonic::DartConverter<SingleFrameCodec*>::FromDart(
        codec_wrapper->value());

    if (image.get()) {
      auto canvas_image = fml::MakeRefCounted<CanvasImage>();
      canvas_image->set_image(std::move(image));

      codec->cached_frame_ = fml::MakeRefCounted<FrameInfo>(
          std::move(canvas_image), 0 /* duration */);
    }

    // The cached frame is now available and should be returned to any future
    // callers.
    codec->status_ = Status::kComplete;

    // Invoke any callbacks that were provided before the frame was decoded.
    Dart_Handle frame = tonic::ToDart(codec->cached_frame_);
    for (const DartPersistentValue& callback : codec->pending_callbacks_) {
      tonic::DartInvoke(callback.value(), {frame});
    }
    codec->pending_callbacks_.clear();
  });

  // The encoded data is no longer needed now that it has been handed off
  // to the decoder.
  descriptor_.data.reset();

  status_ = Status::kInProgress;

  return Dart_Null();
}

size_t SingleFrameCodec::GetAllocationSize() {
  const auto& data = descriptor_.data;
  const auto data_byte_size = data ? data->size() : 0;
  const auto frame_byte_size = (cached_frame_ && cached_frame_->image())
                                   ? cached_frame_->image()->GetAllocationSize()
                                   : 0;
  return data_byte_size + frame_byte_size + sizeof(this);
}

}  // namespace flutter
<|endoftext|>


<commit_before>/*
Highly Optimized Object-oriented Many-particle Dynamics -- Blue Edition
(HOOMD-blue) Open Source Software License Copyright 2008-2011 Ames Laboratory
Iowa State University and The Regents of the University of Michigan All rights
reserved.

HOOMD-blue may contain modifications ("Contributions") provided, and to which
copyright is held, by various Contributors who have granted The Regents of the
University of Michigan the right to modify and/or distribute such Contributions.

You may redistribute, use, and create derivate works of HOOMD-blue, in source
and binary forms, provided you abide by the following conditions:

* Redistributions of source code must retain the above copyright notice, this
list of conditions, and the following disclaimer both in the code and
prominently in any materials provided with the distribution.

* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions, and the following disclaimer in the documentation and/or
other materials provided with the distribution.

* All publications and presentations based on HOOMD-blue, including any reports
or published results obtained, in whole or in part, with HOOMD-blue, will
acknowledge its use according to the terms posted at the time of submission on:
http://codeblue.umich.edu/hoomd-blue/citations.html

* Any electronic documents citing HOOMD-Blue will link to the HOOMD-Blue website:
http://codeblue.umich.edu/hoomd-blue/

* Apart from the above required attributions, neither the name of the copyright
holder nor the names of HOOMD-blue's contributors may be used to endorse or
promote products derived from this software without specific prior written
permission.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND/OR ANY
WARRANTIES THAT THIS SOFTWARE IS FREE OF INFRINGEMENT ARE DISCLAIMED.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Maintainer: joaander

/*! \file BoxResizeUpdater.cc
    \brief Defines the BoxResizeUpdater class
*/

#ifdef WIN32
#pragma warning( push )
#pragma warning( disable : 4103 4244 )
#endif

#include <boost/python.hpp>
using namespace boost::python;

#include "BoxResizeUpdater.h"
#include "RigidData.h"

#include <math.h>
#include <iostream>
#include <stdexcept>

using namespace std;

/*! \param sysdef System definition containing the particle data to set the box size on
    \param Lx length of the x dimension over time
    \param Ly length of the y dimension over time
    \param Lz length of the z dimension over time

    The default setting is to scale particle positions along with the box.
*/
BoxResizeUpdater::BoxResizeUpdater(boost::shared_ptr<SystemDefinition> sysdef,
                                   boost::shared_ptr<Variant> Lx,
                                   boost::shared_ptr<Variant> Ly,
                                   boost::shared_ptr<Variant> Lz,
                                   boost::shared_ptr<Variant> xy,
                                   boost::shared_ptr<Variant> xz,
                                   boost::shared_ptr<Variant> yz)
    : Updater(sysdef), m_Lx(Lx), m_Ly(Ly), m_Lz(Lz), m_xy(xy), m_xz(xz), m_yz(yz), m_scale_particles(true)
    {
    assert(m_pdata);
    assert(m_Lx);
    assert(m_Ly);
    assert(m_Lz);

    m_exec_conf->msg->notice(5) << "Constructing BoxResizeUpdater" << endl;
    }

BoxResizeUpdater::~BoxResizeUpdater()
    {
    m_exec_conf->msg->notice(5) << "Destroying BoxResizeUpdater" << endl;
    }

/*! \param scale_particles Set to true to scale particles with the box. Set to false to leave particle positions alone
    when scaling the box.
*/
void BoxResizeUpdater::setParams(bool scale_particles)
    {
    m_scale_particles = scale_particles;
    }

/*! Perform the needed calculations to scale the box size
    \param timestep Current time step of the simulation
*/
void BoxResizeUpdater::update(unsigned int timestep)
    {
    if (m_prof) m_prof->push("BoxResize");

    boost::shared_ptr<RigidData> rigid_data = m_sysdef->getRigidData();

    // first, compute what the current box size and tilt factors should be
    Scalar Lx = m_Lx->getValue(timestep);
    Scalar Ly = m_Ly->getValue(timestep);
    Scalar Lz = m_Lz->getValue(timestep);
    Scalar xy = m_xy->getValue(timestep);
    Scalar xz = m_xz->getValue(timestep);
    Scalar yz = m_yz->getValue(timestep);

    // check if the current box size is the same
    BoxDim curBox = m_pdata->getGlobalBox();
    Scalar3 curL = curBox.getL();
    Scalar curxy = curBox.getTiltFactorXY();
    Scalar curxz = curBox.getTiltFactorXZ();
    Scalar curyz = curBox.getTiltFactorYZ();

    // copy and setL + setTiltFactors instead of creating a new box
    BoxDim newBox = curBox;
    newBox.setL(make_scalar3(Lx, Ly, Lz));
    newBox.setTiltFactors(xy,xz,yz);

    bool no_change = fabs((Lx - curL.x) / Lx) < 1e-5 &&
                     fabs((Ly - curL.y) / Ly) < 1e-5 &&
                     fabs((Lz - curL.z) / Lz) < 1e-5 &&
                     fabs(xy - curxy) < 1e-5 &&
                     fabs(xz - curxz) < 1e-5 &&
                     fabs(yz - curyz) < 1e-5;

    // set the new box
    m_pdata->setGlobalBox(newBox);

    // only change the box if there is a change in the box dimensions
    if (!no_change)
        {
        // scale the particle positions (if we have been asked to)
        if (m_scale_particles)
            {
            // move the particles to be inside the new box
            ArrayHandle<Scalar4> h_pos(m_pdata->getPositions(), access_location::host, access_mode::readwrite);

            for (unsigned int i = 0; i < m_pdata->getN(); i++)
                {
                // obtain scaled coordinates in the old global box
                Scalar3 f = curBox.makeFraction(make_scalar3(h_pos.data[i].x, h_pos.data[i].y, h_pos.data[i].z));

                // intentionally scale both rigid body and free particles, this may waste a few cycles but it enables
                // the debug inBox checks to be left as is (otherwise, setRV cannot fixup rigid body positions without
                // failing the check)
                Scalar3 scaled_pos = newBox.makeCoordinates(f);
                h_pos.data[i].x = scaled_pos.x;
                h_pos.data[i].y = scaled_pos.y;
                h_pos.data[i].z = scaled_pos.z;
                }

            // also rescale rigid body COMs
            unsigned int n_bodies = rigid_data->getNumBodies();
            if (n_bodies > 0)
                {
                ArrayHandle<Scalar4> com_handle(rigid_data->getCOM(), access_location::host, access_mode::readwrite);

                for (unsigned int body = 0; body < n_bodies; body++)
                    {
                    // obtain scaled coordinates in the old global box
                    Scalar3 f = curBox.makeFraction(make_scalar3(com_handle.data[body].x,
                                                                 com_handle.data[body].y,
                                                                 com_handle.data[body].z));
                    Scalar3 scaled_cm = newBox.makeCoordinates(f);
                    com_handle.data[body].x = scaled_cm.x;
                    com_handle.data[body].y = scaled_cm.y;
                    com_handle.data[body].z = scaled_cm.z;
                    }
                }

            }
        else
            {
            // otherwise, we need to ensure that the particles are still in the (local) box
            // move the particles to be inside the new box
            ArrayHandle<Scalar4> h_pos(m_pdata->getPositions(), access_location::host, access_mode::readwrite);
            ArrayHandle<int3> h_image(m_pdata->getImages(), access_location::host, access_mode::readwrite);

            const BoxDim& local_box = m_pdata->getBox();

            for (unsigned int i = 0; i < m_pdata->getN(); i++)
                {
                // intentionally scale both rigid body and free particles, this may waste a few cycles but it enables
                // the debug inBox checks to be left as is (otherwise, setRV cannot fixup rigid body positions without
                // failing the check)

                // need to update the image if we move particles from one side of the box to the other
                local_box.wrap(h_pos.data[i], h_image.data[i]);
                }

            // do the same for rigid body COMs
            unsigned int n_bodies = rigid_data->getNumBodies();
            if (n_bodies > 0)
                {
                ArrayHandle<Scalar4> h_body_com(rigid_data->getCOM(), access_location::host, access_mode::readwrite);
                ArrayHandle<int3> h_body_image(rigid_data->getBodyImage(), access_location::host, access_mode::readwrite);

                for (unsigned int body = 0; body < n_bodies; body++)
                    {
                    // need to update the image if we move particles from one side of the box to the other
                    local_box.wrap(h_body_com.data[body], h_body_image.data[body]);
                    }
                }
            }

        // update the body particle positions to reflect the new rigid body positions
        rigid_data->setRV(true);
        }

    if (m_prof) m_prof->pop();
    }

void export_BoxResizeUpdater()
    {
    class_<BoxResizeUpdater, boost::shared_ptr<BoxResizeUpdater>, bases<Updater>, boost::noncopyable>
    ("BoxResizeUpdater", init< boost::shared_ptr<SystemDefinition>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant> >())
    .def("setParams", &BoxResizeUpdater::setParams);
    }

#ifdef WIN32
#pragma warning( pop )
#endif

<commit_msg>Fixed a bug where extremely slow resize rates didn't scale<commit_after>/*
Highly Optimized Object-oriented Many-particle Dynamics -- Blue Edition
(HOOMD-blue) Open Source Software License Copyright 2008-2011 Ames Laboratory
Iowa State University and The Regents of the University of Michigan All rights
reserved.

HOOMD-blue may contain modifications ("Contributions") provided, and to which
copyright is held, by various Contributors who have granted The Regents of the
University of Michigan the right to modify and/or distribute such Contributions.

You may redistribute, use, and create derivate works of HOOMD-blue, in source
and binary forms, provided you abide by the following conditions:

* Redistributions of source code must retain the above copyright notice, this
list of conditions, and the following disclaimer both in the code and
prominently in any materials provided with the distribution.

* Redistributions in binary form must reproduce the above copyright notice, this
list of conditions, and the following disclaimer in the documentation and/or
other materials provided with the distribution.

* All publications and presentations based on HOOMD-blue, including any reports
or published results obtained, in whole or in part, with HOOMD-blue, will
acknowledge its use according to the terms posted at the time of submission on:
http://codeblue.umich.edu/hoomd-blue/citations.html

* Any electronic documents citing HOOMD-Blue will link to the HOOMD-Blue website:
http://codeblue.umich.edu/hoomd-blue/

* Apart from the above required attributions, neither the name of the copyright
holder nor the names of HOOMD-blue's contributors may be used to endorse or
promote products derived from this software without specific prior written
permission.

Disclaimer

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND/OR ANY
WARRANTIES THAT THIS SOFTWARE IS FREE OF INFRINGEMENT ARE DISCLAIMED.

IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Maintainer: joaander

/*! \file BoxResizeUpdater.cc
    \brief Defines the BoxResizeUpdater class
*/

#ifdef WIN32
#pragma warning( push )
#pragma warning( disable : 4103 4244 )
#endif

#include <boost/python.hpp>
using namespace boost::python;

#include "BoxResizeUpdater.h"
#include "RigidData.h"

#include <math.h>
#include <iostream>
#include <stdexcept>

using namespace std;

/*! \param sysdef System definition containing the particle data to set the box size on
    \param Lx length of the x dimension over time
    \param Ly length of the y dimension over time
    \param Lz length of the z dimension over time

    The default setting is to scale particle positions along with the box.
*/
BoxResizeUpdater::BoxResizeUpdater(boost::shared_ptr<SystemDefinition> sysdef,
                                   boost::shared_ptr<Variant> Lx,
                                   boost::shared_ptr<Variant> Ly,
                                   boost::shared_ptr<Variant> Lz,
                                   boost::shared_ptr<Variant> xy,
                                   boost::shared_ptr<Variant> xz,
                                   boost::shared_ptr<Variant> yz)
    : Updater(sysdef), m_Lx(Lx), m_Ly(Ly), m_Lz(Lz), m_xy(xy), m_xz(xz), m_yz(yz), m_scale_particles(true)
    {
    assert(m_pdata);
    assert(m_Lx);
    assert(m_Ly);
    assert(m_Lz);

    m_exec_conf->msg->notice(5) << "Constructing BoxResizeUpdater" << endl;
    }

BoxResizeUpdater::~BoxResizeUpdater()
    {
    m_exec_conf->msg->notice(5) << "Destroying BoxResizeUpdater" << endl;
    }

/*! \param scale_particles Set to true to scale particles with the box. Set to false to leave particle positions alone
    when scaling the box.
*/
void BoxResizeUpdater::setParams(bool scale_particles)
    {
    m_scale_particles = scale_particles;
    }

/*! Perform the needed calculations to scale the box size
    \param timestep Current time step of the simulation
*/
void BoxResizeUpdater::update(unsigned int timestep)
    {
    if (m_prof) m_prof->push("BoxResize");

    boost::shared_ptr<RigidData> rigid_data = m_sysdef->getRigidData();

    // first, compute what the current box size and tilt factors should be
    Scalar Lx = m_Lx->getValue(timestep);
    Scalar Ly = m_Ly->getValue(timestep);
    Scalar Lz = m_Lz->getValue(timestep);
    Scalar xy = m_xy->getValue(timestep);
    Scalar xz = m_xz->getValue(timestep);
    Scalar yz = m_yz->getValue(timestep);

    // check if the current box size is the same
    BoxDim curBox = m_pdata->getGlobalBox();
    Scalar3 curL = curBox.getL();
    Scalar curxy = curBox.getTiltFactorXY();
    Scalar curxz = curBox.getTiltFactorXZ();
    Scalar curyz = curBox.getTiltFactorYZ();

    // copy and setL + setTiltFactors instead of creating a new box
    BoxDim newBox = curBox;
    newBox.setL(make_scalar3(Lx, Ly, Lz));
    newBox.setTiltFactors(xy,xz,yz);

    bool no_change = fabs((Lx - curL.x) / Lx) < 1e-7 &&
                     fabs((Ly - curL.y) / Ly) < 1e-7 &&
                     fabs((Lz - curL.z) / Lz) < 1e-7 &&
                     fabs((xy - curxy) / xy) < 1e-7 &&
                     fabs((xz - curxz) / xz) < 1e-7 &&
                     fabs((yz - curyz) / yz) < 1e-7;

    // only change the box if there is a change in the box dimensions
    if (!no_change)
        {
        // scale the particle positions (if we have been asked to)
        if (m_scale_particles)
            {
            // move the particles to be inside the new box
            ArrayHandle<Scalar4> h_pos(m_pdata->getPositions(), access_location::host, access_mode::readwrite);

            for (unsigned int i = 0; i < m_pdata->getN(); i++)
                {
                // obtain scaled coordinates in the old global box
                Scalar3 f = curBox.makeFraction(make_scalar3(h_pos.data[i].x, h_pos.data[i].y, h_pos.data[i].z));

                // intentionally scale both rigid body and free particles, this may waste a few cycles but it enables
                // the debug inBox checks to be left as is (otherwise, setRV cannot fixup rigid body positions without
                // failing the check)
                Scalar3 scaled_pos = newBox.makeCoordinates(f);
                h_pos.data[i].x = scaled_pos.x;
                h_pos.data[i].y = scaled_pos.y;
                h_pos.data[i].z = scaled_pos.z;
                }

            // also rescale rigid body COMs
            unsigned int n_bodies = rigid_data->getNumBodies();
            if (n_bodies > 0)
                {
                ArrayHandle<Scalar4> com_handle(rigid_data->getCOM(), access_location::host, access_mode::readwrite);

                for (unsigned int body = 0; body < n_bodies; body++)
                    {
                    // obtain scaled coordinates in the old global box
                    Scalar3 f = curBox.makeFraction(make_scalar3(com_handle.data[body].x,
                                                                 com_handle.data[body].y,
                                                                 com_handle.data[body].z));
                    Scalar3 scaled_cm = newBox.makeCoordinates(f);
                    com_handle.data[body].x = scaled_cm.x;
                    com_handle.data[body].y = scaled_cm.y;
                    com_handle.data[body].z = scaled_cm.z;
                    }
                }

            }
        else
            {
            // otherwise, we need to ensure that the particles are still in the (local) box
            // move the particles to be inside the new box
            ArrayHandle<Scalar4> h_pos(m_pdata->getPositions(), access_location::host, access_mode::readwrite);
            ArrayHandle<int3> h_image(m_pdata->getImages(), access_location::host, access_mode::readwrite);

            const BoxDim& local_box = m_pdata->getBox();

            for (unsigned int i = 0; i < m_pdata->getN(); i++)
                {
                // intentionally scale both rigid body and free particles, this may waste a few cycles but it enables
                // the debug inBox checks to be left as is (otherwise, setRV cannot fixup rigid body positions without
                // failing the check)

                // need to update the image if we move particles from one side of the box to the other
                local_box.wrap(h_pos.data[i], h_image.data[i]);
                }

            // do the same for rigid body COMs
            unsigned int n_bodies = rigid_data->getNumBodies();
            if (n_bodies > 0)
                {
                ArrayHandle<Scalar4> h_body_com(rigid_data->getCOM(), access_location::host, access_mode::readwrite);
                ArrayHandle<int3> h_body_image(rigid_data->getBodyImage(), access_location::host, access_mode::readwrite);

                for (unsigned int body = 0; body < n_bodies; body++)
                    {
                    // need to update the image if we move particles from one side of the box to the other
                    local_box.wrap(h_body_com.data[body], h_body_image.data[body]);
                    }
                }
            }

        // set the new box
        m_pdata->setGlobalBox(newBox);

        // update the body particle positions to reflect the new rigid body positions
        rigid_data->setRV(true);
        }

    if (m_prof) m_prof->pop();
    }

void export_BoxResizeUpdater()
    {
    class_<BoxResizeUpdater, boost::shared_ptr<BoxResizeUpdater>, bases<Updater>, boost::noncopyable>
    ("BoxResizeUpdater", init< boost::shared_ptr<SystemDefinition>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant>,
     boost::shared_ptr<Variant> >())
    .def("setParams", &BoxResizeUpdater::setParams);
    }

#ifdef WIN32
#pragma warning( pop )
#endif

<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015, Cryptonomex, Inc.
 * All rights reserved.
 *
 * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
 * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
 * are permitted until September 8, 2015, provided that the following conditions are met:
 *
 * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <graphene/witness/witness.hpp>

#include <graphene/chain/database.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/time/time.hpp>

#include <graphene/utilities/key_conversion.hpp>

#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>

#include <iostream>

using namespace graphene::witness_plugin;
using std::string;
using std::vector;

namespace bpo = boost::program_options;

void new_chain_banner( const graphene::chain::database& db )
{
   std::cerr << "\n"
      "********************************\n"
      "*                              *\n"
      "*   ------- NEW CHAIN ------   *\n"
      "*   - Welcome to Graphene! -   *\n"
      "*   ------------------------   *\n"
      "*                              *\n"
      "********************************\n"
      "\n";
   if( db.get_slot_at_time( graphene::time::now() ) > 200 )
   {
      std::cerr << "Your genesis seems to have an old timestamp\n"
         "Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\n"
         "\n"
         ;
   }
   return;
}

void witness_plugin::plugin_set_program_options(
   boost::program_options::options_description& command_line_options,
   boost::program_options::options_description& config_file_options)
{
   auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan")));
   string witness_id_example = fc::json::to_string(chain::witness_id_type(5));
   command_line_options.add_options()
         ("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}), "Enable block production, even if the chain is stale.")
         ("required-participation", bpo::bool_switch()->notifier([this](int e){_required_witness_participation = uint32_t(e*GRAPHENE_1_PERCENT);}), "Percent of witnesses (0-99) that must be participating in order to produce blocks")
         ("allow-consecutive", bpo::bool_switch()->notifier([this](bool e){_consecutive_production_enabled = e;}), "Allow block production, even if the last block was produced by the same witness.")
         ("witness-id,w", bpo::value<vector<string>>()->composing()->multitoken(),
          ("ID of witness controlled by this node (e.g. " + witness_id_example + ", quotes are required, may specify multiple times)").c_str())
         ("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
          DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))),
          "Tuple of [PublicKey, WIF private key] (may specify multiple times)")
         ;
   config_file_options.add(command_line_options);
}

std::string witness_plugin::plugin_name()const
{
   return "witness";
}

void witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{ try {
   ilog("witness plugin:  plugin_initialize() begin");
   _options = &options;
   LOAD_VALUE_SET(options, "witness-id", _witnesses, chain::witness_id_type)

   if( options.count("private-key") )
   {
      const std::vector<std::string> key_id_to_wif_pair_strings = options["private-key"].as<std::vector<std::string>>();
      for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)
      {
         auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::public_key_type, std::string> >(key_id_to_wif_pair_string);
         idump((key_id_to_wif_pair));
         fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);
         if (!private_key)
         {
            // the key isn't in WIF format; see if they are still passing the old native private key format.  This is
            // just here to ease the transition, can be removed soon
            try
            {
               private_key = fc::variant(key_id_to_wif_pair.second).as<fc::ecc::private_key>();
            }
            catch (const fc::exception&)
            {
               FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second));
            }
         }
         _private_keys[key_id_to_wif_pair.first] = *private_key;
      }
   }
   ilog("witness plugin:  plugin_initialize() end");
} FC_LOG_AND_RETHROW() }

void witness_plugin::plugin_startup()
{ try {
   ilog("witness plugin:  plugin_startup() begin");
   chain::database& d = database();
   std::set<chain::witness_id_type> bad_wits;
   //Start NTP time client
   graphene::time::now();
   for( auto wit : _witnesses )
   {
      if( d.find(wit) == nullptr )
      {
         if( app().is_finished_syncing() )
         {
            elog("ERROR: Unable to find witness ${w}, even though syncing has finished. This witness will be ignored.",
                 ("w", wit));
            continue;
         } else {
            wlog("WARNING: Unable to find witness ${w}. Postponing initialization until syncing finishes.",
                 ("w", wit));
            app().syncing_finished.connect([this]{plugin_startup();});
            return;
         }
      }

      auto signing_key = wit(d).signing_key;
      if( !_private_keys.count(signing_key) )
      {
         // Check if it's a duplicate key of one I do have
         bool found_duplicate = false;
         for( const auto& private_key : _private_keys )
            if( chain::public_key_type(private_key.second.get_public_key()) == signing_key )
            {
               ilog("Found duplicate key: ${k1} matches ${k2}; using this key to sign for ${w}",
                    ("k1", private_key.first)("k2", signing_key)("w", wit));
               _private_keys[signing_key] = private_key.second;
               found_duplicate = true;
               break;
            }
         if( found_duplicate )
            continue;

         elog("Unable to find key for witness ${w}. Removing it from my witnesses.", ("w", wit));
         bad_wits.insert(wit);
      }
   }
   for( auto wit : bad_wits )
      _witnesses.erase(wit);

   if( !_witnesses.empty() )
   {
      ilog("Launching block production for ${n} witnesses.", ("n", _witnesses.size()));
      app().set_block_production(true);
      if( _production_enabled && (d.head_block_num() == 0) )
         new_chain_banner(d);
      schedule_production_loop();
   } else
      elog("No witnesses configured! Please add witness IDs and private keys to configuration.");
   ilog("witness plugin:  plugin_startup() end");
} FC_CAPTURE_AND_RETHROW() }

void witness_plugin::plugin_shutdown()
{
   graphene::time::shutdown_ntp_time();
   return;
}

void witness_plugin::schedule_production_loop()
{
   //Schedule for the next second's tick regardless of chain state
   // If we would wait less than 200ms, wait for the whole second.
   fc::time_point now = graphene::time::now();
   fc::time_point_sec next_second( now + fc::microseconds( 1200000 ) );
   wdump( (now.time_since_epoch().count())(next_second) );
   _block_production_task = fc::schedule([this]{block_production_loop();},
                                         next_second, "Witness Block Production");
}

block_production_condition::block_production_condition_enum witness_plugin::block_production_loop()
{
   block_production_condition::block_production_condition_enum result;
   fc::mutable_variant_object capture;
   try
   {
      result = maybe_produce_block(capture);
   }
   catch( const fc::canceled_exception& )
   {
      //We're trying to exit. Go ahead and let this one out.
      throw;
   }
   catch( const fc::exception& e )
   {
      elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string()));
      result = block_production_condition::exception_producing_block;
   }

   switch( result )
   {
      case block_production_condition::produced:
         ilog("Generated block #${n} with timestamp ${t} at time ${c}", ("n",capture)("t",capture)("c",capture));
         break;
      case block_production_condition::not_synced:
         ilog("Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production)");
         break;
      case block_production_condition::not_my_turn:
         ilog("Not producing block because it isn't my turn");
         break;
      case block_production_condition::not_time_yet:
         ilog("Not producing block because slot has not yet arrived");
         break;
      case block_production_condition::no_private_key:
         ilog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key",capture) );
         break;
      case block_production_condition::low_participation:
         elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation", ("pct",capture) );
         break;
      case block_production_condition::lag:
         elog("Not producing block because node didn't wake up within 500ms of the slot time.");
         break;
      case block_production_condition::consecutive:
         elog("Not producing block because the last block was generated by the same witness.\nThis node is probably disconnected from the network so block production has been disabled.\nDisable this check with --allow-consecutive option.");
         break;
      case block_production_condition::exception_producing_block:
         break;
   }

   schedule_production_loop();
   return result;
}

block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block( fc::mutable_variant_object& capture )
{
   chain::database& db = database();
   fc::time_point now_fine = graphene::time::now();
   fc::time_point_sec now = now_fine + fc::microseconds( 500000 );

   // If the next block production opportunity is in the present or future, we're synced.
   if( !_production_enabled )
   {
      if( db.get_slot_time(1) >= now )
         _production_enabled = true;
      else
         return block_production_condition::not_synced;
   }

   // is anyone scheduled to produce now or one second in the future?
   uint32_t slot = db.get_slot_at_time( now );
   if( slot == 0 )
   {
      capture("next_time", db.get_slot_time(1));
      return block_production_condition::not_time_yet;
   }

   //
   // this assert should not fail, because now <= db.head_block_time()
   // should have resulted in slot == 0.
   //
   // if this assert triggers, there is a serious bug in get_slot_at_time()
   // which would result in allowing a later block to have a timestamp
   // less than or equal to the previous block
   //
   assert( now > db.head_block_time() );

   graphene::chain::witness_id_type scheduled_witness = db.get_scheduled_witness( slot );
   // we must control the witness scheduled to produce the next block.
   if( _witnesses.find( scheduled_witness ) == _witnesses.end() )
   {
      capture("scheduled_witness", scheduled_witness);
      return block_production_condition::not_my_turn;
   }

   fc::time_point_sec scheduled_time = db.get_slot_time( slot );
   graphene::chain::public_key_type scheduled_key = scheduled_witness( db ).signing_key;
   auto private_key_itr = _private_keys.find( scheduled_key );

   if( private_key_itr == _private_keys.end() )
   {
      capture("scheduled_key", scheduled_key);
      return block_production_condition::no_private_key;
   }

   uint32_t prate = db.witness_participation_rate();
   if( prate < _required_witness_participation )
   {
      capture("pct", uint32_t(100*uint64_t(prate) / GRAPHENE_1_PERCENT));
      return block_production_condition::low_participation;
   }

   if( llabs((scheduled_time - now).count()) > fc::milliseconds( 500 ).count() )
   {
      capture("scheduled_time", scheduled_time)("now", now);
      return block_production_condition::lag;
   }

   if( !_consecutive_production_enabled )
   {
      if( db.get_dynamic_global_properties().current_witness == scheduled_witness )
      {
         capture("scheduled_witness", scheduled_witness);
         return block_production_condition::consecutive;
      }
   }

   auto block = db.generate_block(
      scheduled_time,
      scheduled_witness,
      private_key_itr->second,
      graphene::chain::database::skip_nothing
      );
   capture("n", block.block_num())("t", block.timestamp)("c", now);
   p2p_node().broadcast(net::block_message(block));

   return block_production_condition::produced;
}
<commit_msg>witness.cpp:  Don't filter witnesses prematurely<commit_after>/*
 * Copyright (c) 2015, Cryptonomex, Inc.
 * All rights reserved.
 *
 * This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
 * the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
 * are permitted until September 8, 2015, provided that the following conditions are met:
 *
 * 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <graphene/witness/witness.hpp>

#include <graphene/chain/database.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/time/time.hpp>

#include <graphene/utilities/key_conversion.hpp>

#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>

#include <iostream>

using namespace graphene::witness_plugin;
using std::string;
using std::vector;

namespace bpo = boost::program_options;

void new_chain_banner( const graphene::chain::database& db )
{
   std::cerr << "\n"
      "********************************\n"
      "*                              *\n"
      "*   ------- NEW CHAIN ------   *\n"
      "*   - Welcome to Graphene! -   *\n"
      "*   ------------------------   *\n"
      "*                              *\n"
      "********************************\n"
      "\n";
   if( db.get_slot_at_time( graphene::time::now() ) > 200 )
   {
      std::cerr << "Your genesis seems to have an old timestamp\n"
         "Please consider using the --genesis-timestamp option to give your genesis a recent timestamp\n"
         "\n"
         ;
   }
   return;
}

void witness_plugin::plugin_set_program_options(
   boost::program_options::options_description& command_line_options,
   boost::program_options::options_description& config_file_options)
{
   auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan")));
   string witness_id_example = fc::json::to_string(chain::witness_id_type(5));
   command_line_options.add_options()
         ("enable-stale-production", bpo::bool_switch()->notifier([this](bool e){_production_enabled = e;}), "Enable block production, even if the chain is stale.")
         ("required-participation", bpo::bool_switch()->notifier([this](int e){_required_witness_participation = uint32_t(e*GRAPHENE_1_PERCENT);}), "Percent of witnesses (0-99) that must be participating in order to produce blocks")
         ("allow-consecutive", bpo::bool_switch()->notifier([this](bool e){_consecutive_production_enabled = e;}), "Allow block production, even if the last block was produced by the same witness.")
         ("witness-id,w", bpo::value<vector<string>>()->composing()->multitoken(),
          ("ID of witness controlled by this node (e.g. " + witness_id_example + ", quotes are required, may specify multiple times)").c_str())
         ("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
          DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))),
          "Tuple of [PublicKey, WIF private key] (may specify multiple times)")
         ;
   config_file_options.add(command_line_options);
}

std::string witness_plugin::plugin_name()const
{
   return "witness";
}

void witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{ try {
   ilog("witness plugin:  plugin_initialize() begin");
   _options = &options;
   LOAD_VALUE_SET(options, "witness-id", _witnesses, chain::witness_id_type)

   if( options.count("private-key") )
   {
      const std::vector<std::string> key_id_to_wif_pair_strings = options["private-key"].as<std::vector<std::string>>();
      for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)
      {
         auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::public_key_type, std::string> >(key_id_to_wif_pair_string);
         idump((key_id_to_wif_pair));
         fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);
         if (!private_key)
         {
            // the key isn't in WIF format; see if they are still passing the old native private key format.  This is
            // just here to ease the transition, can be removed soon
            try
            {
               private_key = fc::variant(key_id_to_wif_pair.second).as<fc::ecc::private_key>();
            }
            catch (const fc::exception&)
            {
               FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second));
            }
         }
         _private_keys[key_id_to_wif_pair.first] = *private_key;
      }
   }
   ilog("witness plugin:  plugin_initialize() end");
} FC_LOG_AND_RETHROW() }

void witness_plugin::plugin_startup()
{ try {
   ilog("witness plugin:  plugin_startup() begin");
   chain::database& d = database();
   //Start NTP time client
   graphene::time::now();

   if( !_witnesses.empty() )
   {
      ilog("Launching block production for ${n} witnesses.", ("n", _witnesses.size()));
      app().set_block_production(true);
      if( _production_enabled && (d.head_block_num() == 0) )
         new_chain_banner(d);
      schedule_production_loop();
   } else
      elog("No witnesses configured! Please add witness IDs and private keys to configuration.");
   ilog("witness plugin:  plugin_startup() end");
} FC_CAPTURE_AND_RETHROW() }

void witness_plugin::plugin_shutdown()
{
   graphene::time::shutdown_ntp_time();
   return;
}

void witness_plugin::schedule_production_loop()
{
   //Schedule for the next second's tick regardless of chain state
   // If we would wait less than 200ms, wait for the whole second.
   fc::time_point now = graphene::time::now();
   fc::time_point_sec next_second( now + fc::microseconds( 1200000 ) );
   wdump( (now.time_since_epoch().count())(next_second) );
   _block_production_task = fc::schedule([this]{block_production_loop();},
                                         next_second, "Witness Block Production");
}

block_production_condition::block_production_condition_enum witness_plugin::block_production_loop()
{
   block_production_condition::block_production_condition_enum result;
   fc::mutable_variant_object capture;
   try
   {
      result = maybe_produce_block(capture);
   }
   catch( const fc::canceled_exception& )
   {
      //We're trying to exit. Go ahead and let this one out.
      throw;
   }
   catch( const fc::exception& e )
   {
      elog("Got exception while generating block:\n${e}", ("e", e.to_detail_string()));
      result = block_production_condition::exception_producing_block;
   }

   switch( result )
   {
      case block_production_condition::produced:
         ilog("Generated block #${n} with timestamp ${t} at time ${c}", ("n",capture)("t",capture)("c",capture));
         break;
      case block_production_condition::not_synced:
         ilog("Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production)");
         break;
      case block_production_condition::not_my_turn:
         ilog("Not producing block because it isn't my turn");
         break;
      case block_production_condition::not_time_yet:
         ilog("Not producing block because slot has not yet arrived");
         break;
      case block_production_condition::no_private_key:
         ilog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key",capture) );
         break;
      case block_production_condition::low_participation:
         elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation", ("pct",capture) );
         break;
      case block_production_condition::lag:
         elog("Not producing block because node didn't wake up within 500ms of the slot time.");
         break;
      case block_production_condition::consecutive:
         elog("Not producing block because the last block was generated by the same witness.\nThis node is probably disconnected from the network so block production has been disabled.\nDisable this check with --allow-consecutive option.");
         break;
      case block_production_condition::exception_producing_block:
         break;
   }

   schedule_production_loop();
   return result;
}

block_production_condition::block_production_condition_enum witness_plugin::maybe_produce_block( fc::mutable_variant_object& capture )
{
   chain::database& db = database();
   fc::time_point now_fine = graphene::time::now();
   fc::time_point_sec now = now_fine + fc::microseconds( 500000 );

   // If the next block production opportunity is in the present or future, we're synced.
   if( !_production_enabled )
   {
      if( db.get_slot_time(1) >= now )
         _production_enabled = true;
      else
         return block_production_condition::not_synced;
   }

   // is anyone scheduled to produce now or one second in the future?
   uint32_t slot = db.get_slot_at_time( now );
   if( slot == 0 )
   {
      capture("next_time", db.get_slot_time(1));
      return block_production_condition::not_time_yet;
   }

   //
   // this assert should not fail, because now <= db.head_block_time()
   // should have resulted in slot == 0.
   //
   // if this assert triggers, there is a serious bug in get_slot_at_time()
   // which would result in allowing a later block to have a timestamp
   // less than or equal to the previous block
   //
   assert( now > db.head_block_time() );

   graphene::chain::witness_id_type scheduled_witness = db.get_scheduled_witness( slot );
   // we must control the witness scheduled to produce the next block.
   if( _witnesses.find( scheduled_witness ) == _witnesses.end() )
   {
      capture("scheduled_witness", scheduled_witness);
      return block_production_condition::not_my_turn;
   }

   fc::time_point_sec scheduled_time = db.get_slot_time( slot );
   graphene::chain::public_key_type scheduled_key = scheduled_witness( db ).signing_key;
   auto private_key_itr = _private_keys.find( scheduled_key );

   if( private_key_itr == _private_keys.end() )
   {
      capture("scheduled_key", scheduled_key);
      return block_production_condition::no_private_key;
   }

   uint32_t prate = db.witness_participation_rate();
   if( prate < _required_witness_participation )
   {
      capture("pct", uint32_t(100*uint64_t(prate) / GRAPHENE_1_PERCENT));
      return block_production_condition::low_participation;
   }

   if( llabs((scheduled_time - now).count()) > fc::milliseconds( 500 ).count() )
   {
      capture("scheduled_time", scheduled_time)("now", now);
      return block_production_condition::lag;
   }

   if( !_consecutive_production_enabled )
   {
      if( db.get_dynamic_global_properties().current_witness == scheduled_witness )
      {
         capture("scheduled_witness", scheduled_witness);
         return block_production_condition::consecutive;
      }
   }

   auto block = db.generate_block(
      scheduled_time,
      scheduled_witness,
      private_key_itr->second,
      graphene::chain::database::skip_nothing
      );
   capture("n", block.block_num())("t", block.timestamp)("c", now);
   p2p_node().broadcast(net::block_message(block));

   return block_production_condition::produced;
}
<|endoftext|>