<commit_before>/*
 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
 *
 * File: examples/fit-model.cpp
 *
 * Copyright 2016 Patrik Huber
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "eos/core/Image.hpp"
#include "eos/core/image/opencv_interop.hpp"
#include "eos/core/Landmark.hpp"
#include "eos/core/LandmarkMapper.hpp"
#include "eos/core/read_pts_landmarks.hpp"
#include "eos/core/write_obj.hpp"
#include "eos/fitting/fitting.hpp"
#include "eos/morphablemodel/Blendshape.hpp"
#include "eos/morphablemodel/MorphableModel.hpp"
#include "eos/render/draw_utils.hpp"
#include "eos/render/texture_extraction.hpp"
#include "eos/cpp17/optional.hpp"

#include "Eigen/Core"

#include "boost/filesystem.hpp"
#include "boost/program_options.hpp"

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>
#include <string>
#include <vector>

using namespace eos;
namespace po = boost::program_options;
namespace fs = boost::filesystem;
using eos::core::Landmark;
using eos::core::LandmarkCollection;
using cv::Mat;
using std::cout;
using std::endl;
using std::string;
using std::vector;

/**
 * This app demonstrates estimation of the camera and fitting of the shape
 * model of a 3D Morphable Model from an ibug LFPW image with its landmarks.
 * In addition to fit-model-simple, this example uses blendshapes, contour-
 * fitting, and can iterate the fitting.
 *
 * 68 ibug landmarks are loaded from the .pts file and converted
 * to vertex indices using the LandmarkMapper.
 */
int main(int argc, char* argv[])
{
    string modelfile, isomapfile, imagefile, landmarksfile, mappingsfile, contourfile, edgetopologyfile,
        blendshapesfile, outputbasename;
    try
    {
        po::options_description desc("Allowed options");
        // clang-format off
        desc.add_options()
            ("help,h", "display the help message")
            ("model,m", po::value<string>(&modelfile)->required()->default_value("../share/sfm_shape_3448.bin"),
                "a Morphable Model stored as cereal BinaryArchive")
            ("image,i", po::value<string>(&imagefile)->required()->default_value("data/image_0010.png"),
                "an input image")
            ("landmarks,l", po::value<string>(&landmarksfile)->required()->default_value("data/image_0010.pts"),
                "2D landmarks for the image, in ibug .pts format")
            ("mapping,p", po::value<string>(&mappingsfile)->required()->default_value("../share/ibug_to_sfm.txt"),
                "landmark identifier to model vertex number mapping")
            ("model-contour,c", po::value<string>(&contourfile)->required()->default_value("../share/sfm_model_contours.json"),
                "file with model contour indices")
            ("edge-topology,e", po::value<string>(&edgetopologyfile)->required()->default_value("../share/sfm_3448_edge_topology.json"),
                "file with model's precomputed edge topology")
            ("blendshapes,b", po::value<string>(&blendshapesfile)->required()->default_value("../share/expression_blendshapes_3448.bin"),
                "file with blendshapes")
            ("output,o", po::value<string>(&outputbasename)->required()->default_value("out"),
                "basename for the output rendering and obj files");
        // clang-format on
        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
        if (vm.count("help"))
        {
            cout << "Usage: fit-model [options]" << endl;
            cout << desc;
            return EXIT_SUCCESS;
        }
        po::notify(vm);
    } catch (const po::error& e)
    {
        cout << "Error while parsing command-line arguments: " << e.what() << endl;
        cout << "Use --help to display a list of options." << endl;
        return EXIT_FAILURE;
    }

    // Load the image, landmarks, LandmarkMapper and the Morphable Model:
    Mat image = cv::imread(imagefile);
    LandmarkCollection<Eigen::Vector2f> landmarks;
    try
    {
        landmarks = core::read_pts_landmarks(landmarksfile);
    } catch (const std::runtime_error& e)
    {
        cout << "Error reading the landmarks: " << e.what() << endl;
        return EXIT_FAILURE;
    }
    morphablemodel::MorphableModel morphable_model;
    try
    {
        morphable_model = morphablemodel::load_model(modelfile);
    } catch (const std::runtime_error& e)
    {
        cout << "Error loading the Morphable Model: " << e.what() << endl;
        return EXIT_FAILURE;
    }
    // The landmark mapper is used to map 2D landmark points (e.g. from the ibug scheme) to vertex ids:
    core::LandmarkMapper landmark_mapper;
    try
    {
        landmark_mapper = core::LandmarkMapper(mappingsfile);
    } catch (const std::exception& e)
    {
        cout << "Error loading the landmark mappings: " << e.what() << endl;
        return EXIT_FAILURE;
    }

    // The expression blendshapes:
    const vector<morphablemodel::Blendshape> blendshapes = morphablemodel::load_blendshapes(blendshapesfile);

    morphablemodel::MorphableModel morphable_model_with_expressions(
        morphable_model.get_shape_model(), blendshapes, morphable_model.get_color_model(), cpp17::nullopt,
        morphable_model.get_texture_coordinates());

    // These two are used to fit the front-facing contour to the ibug contour landmarks:
    const fitting::ModelContour model_contour =
        contourfile.empty() ? fitting::ModelContour() : fitting::ModelContour::load(contourfile);
    const fitting::ContourLandmarks ibug_contour = fitting::ContourLandmarks::load(mappingsfile);

    // The edge topology is used to speed up computation of the occluding face contour fitting:
    const morphablemodel::EdgeTopology edge_topology = morphablemodel::load_edge_topology(edgetopologyfile);

    // Draw the loaded landmarks:
    Mat outimg = image.clone();
    for (auto&& lm : landmarks)
    {
        cv::rectangle(outimg, cv::Point2f(lm.coordinates[0] - 2.0f, lm.coordinates[1] - 2.0f),
                      cv::Point2f(lm.coordinates[0] + 2.0f, lm.coordinates[1] + 2.0f), {255, 0, 0});
    }

    // Fit the model, get back a mesh and the pose:
    core::Mesh mesh;
    fitting::RenderingParameters rendering_params;
    std::tie(mesh, rendering_params) = fitting::fit_shape_and_pose(
        morphable_model_with_expressions, landmarks, landmark_mapper, image.cols, image.rows, edge_topology,
        ibug_contour, model_contour, 5, cpp17::nullopt, 30.0f);

    // The 3D head pose can be recovered as follows:
    float yaw_angle = glm::degrees(glm::yaw(rendering_params.get_rotation()));
    // and similarly for pitch and roll.

    // Extract the texture from the image using given mesh and camera parameters:
    const Eigen::Matrix<float, 3, 4> affine_from_ortho =
        fitting::get_3x4_affine_camera_matrix(rendering_params, image.cols, image.rows);
    const core::Image4u isomap =
        render::extract_texture(mesh, affine_from_ortho, core::from_mat(image), true);

    // Draw the fitted mesh as wireframe, and save the image:
    render::draw_wireframe(outimg, mesh, rendering_params.get_modelview(), rendering_params.get_projection(),
                           fitting::get_opencv_viewport(image.cols, image.rows));
    fs::path outputfile = outputbasename + ".png";
    cv::imwrite(outputfile.string(), outimg);

    // Save the mesh as textured obj:
    outputfile.replace_extension(".obj");
    core::write_textured_obj(mesh, outputfile.string());

    // And save the isomap:
    outputfile.replace_extension(".isomap.png");
    cv::imwrite(outputfile.string(), core::to_mat(isomap));

    cout << "Finished fitting and wrote result mesh and isomap to files with basename "
         << outputfile.stem().stem() << "." << endl;

    return EXIT_SUCCESS;
}
<commit_msg>Updated fit-model includes to use render/opencv/draw_utils.hpp<commit_after>/*
 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
 *
 * File: examples/fit-model.cpp
 *
 * Copyright 2016 Patrik Huber
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "eos/core/Image.hpp"
#include "eos/core/image/opencv_interop.hpp"
#include "eos/core/Landmark.hpp"
#include "eos/core/LandmarkMapper.hpp"
#include "eos/core/read_pts_landmarks.hpp"
#include "eos/core/write_obj.hpp"
#include "eos/fitting/fitting.hpp"
#include "eos/morphablemodel/Blendshape.hpp"
#include "eos/morphablemodel/MorphableModel.hpp"
#include "eos/render/opencv/draw_utils.hpp"
#include "eos/render/texture_extraction.hpp"
#include "eos/cpp17/optional.hpp"

#include "Eigen/Core"

#include "boost/filesystem.hpp"
#include "boost/program_options.hpp"

#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <iostream>
#include <string>
#include <vector>

using namespace eos;
namespace po = boost::program_options;
namespace fs = boost::filesystem;
using eos::core::Landmark;
using eos::core::LandmarkCollection;
using cv::Mat;
using std::cout;
using std::endl;
using std::string;
using std::vector;

/**
 * This app demonstrates estimation of the camera and fitting of the shape
 * model of a 3D Morphable Model from an ibug LFPW image with its landmarks.
 * In addition to fit-model-simple, this example uses blendshapes, contour-
 * fitting, and can iterate the fitting.
 *
 * 68 ibug landmarks are loaded from the .pts file and converted
 * to vertex indices using the LandmarkMapper.
 */
int main(int argc, char* argv[])
{
    string modelfile, isomapfile, imagefile, landmarksfile, mappingsfile, contourfile, edgetopologyfile,
        blendshapesfile, outputbasename;
    try
    {
        po::options_description desc("Allowed options");
        // clang-format off
        desc.add_options()
            ("help,h", "display the help message")
            ("model,m", po::value<string>(&modelfile)->required()->default_value("../share/sfm_shape_3448.bin"),
                "a Morphable Model stored as cereal BinaryArchive")
            ("image,i", po::value<string>(&imagefile)->required()->default_value("data/image_0010.png"),
                "an input image")
            ("landmarks,l", po::value<string>(&landmarksfile)->required()->default_value("data/image_0010.pts"),
                "2D landmarks for the image, in ibug .pts format")
            ("mapping,p", po::value<string>(&mappingsfile)->required()->default_value("../share/ibug_to_sfm.txt"),
                "landmark identifier to model vertex number mapping")
            ("model-contour,c", po::value<string>(&contourfile)->required()->default_value("../share/sfm_model_contours.json"),
                "file with model contour indices")
            ("edge-topology,e", po::value<string>(&edgetopologyfile)->required()->default_value("../share/sfm_3448_edge_topology.json"),
                "file with model's precomputed edge topology")
            ("blendshapes,b", po::value<string>(&blendshapesfile)->required()->default_value("../share/expression_blendshapes_3448.bin"),
                "file with blendshapes")
            ("output,o", po::value<string>(&outputbasename)->required()->default_value("out"),
                "basename for the output rendering and obj files");
        // clang-format on
        po::variables_map vm;
        po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
        if (vm.count("help"))
        {
            cout << "Usage: fit-model [options]" << endl;
            cout << desc;
            return EXIT_SUCCESS;
        }
        po::notify(vm);
    } catch (const po::error& e)
    {
        cout << "Error while parsing command-line arguments: " << e.what() << endl;
        cout << "Use --help to display a list of options." << endl;
        return EXIT_FAILURE;
    }

    // Load the image, landmarks, LandmarkMapper and the Morphable Model:
    Mat image = cv::imread(imagefile);
    LandmarkCollection<Eigen::Vector2f> landmarks;
    try
    {
        landmarks = core::read_pts_landmarks(landmarksfile);
    } catch (const std::runtime_error& e)
    {
        cout << "Error reading the landmarks: " << e.what() << endl;
        return EXIT_FAILURE;
    }
    morphablemodel::MorphableModel morphable_model;
    try
    {
        morphable_model = morphablemodel::load_model(modelfile);
    } catch (const std::runtime_error& e)
    {
        cout << "Error loading the Morphable Model: " << e.what() << endl;
        return EXIT_FAILURE;
    }
    // The landmark mapper is used to map 2D landmark points (e.g. from the ibug scheme) to vertex ids:
    core::LandmarkMapper landmark_mapper;
    try
    {
        landmark_mapper = core::LandmarkMapper(mappingsfile);
    } catch (const std::exception& e)
    {
        cout << "Error loading the landmark mappings: " << e.what() << endl;
        return EXIT_FAILURE;
    }

    // The expression blendshapes:
    const vector<morphablemodel::Blendshape> blendshapes = morphablemodel::load_blendshapes(blendshapesfile);

    morphablemodel::MorphableModel morphable_model_with_expressions(
        morphable_model.get_shape_model(), blendshapes, morphable_model.get_color_model(), cpp17::nullopt,
        morphable_model.get_texture_coordinates());

    // These two are used to fit the front-facing contour to the ibug contour landmarks:
    const fitting::ModelContour model_contour =
        contourfile.empty() ? fitting::ModelContour() : fitting::ModelContour::load(contourfile);
    const fitting::ContourLandmarks ibug_contour = fitting::ContourLandmarks::load(mappingsfile);

    // The edge topology is used to speed up computation of the occluding face contour fitting:
    const morphablemodel::EdgeTopology edge_topology = morphablemodel::load_edge_topology(edgetopologyfile);

    // Draw the loaded landmarks:
    Mat outimg = image.clone();
    for (auto&& lm : landmarks)
    {
        cv::rectangle(outimg, cv::Point2f(lm.coordinates[0] - 2.0f, lm.coordinates[1] - 2.0f),
                      cv::Point2f(lm.coordinates[0] + 2.0f, lm.coordinates[1] + 2.0f), {255, 0, 0});
    }

    // Fit the model, get back a mesh and the pose:
    core::Mesh mesh;
    fitting::RenderingParameters rendering_params;
    std::tie(mesh, rendering_params) = fitting::fit_shape_and_pose(
        morphable_model_with_expressions, landmarks, landmark_mapper, image.cols, image.rows, edge_topology,
        ibug_contour, model_contour, 5, cpp17::nullopt, 30.0f);

    // The 3D head pose can be recovered as follows:
    float yaw_angle = glm::degrees(glm::yaw(rendering_params.get_rotation()));
    // and similarly for pitch and roll.

    // Extract the texture from the image using given mesh and camera parameters:
    const Eigen::Matrix<float, 3, 4> affine_from_ortho =
        fitting::get_3x4_affine_camera_matrix(rendering_params, image.cols, image.rows);
    const core::Image4u isomap =
        render::extract_texture(mesh, affine_from_ortho, core::from_mat(image), true);

    // Draw the fitted mesh as wireframe, and save the image:
    render::draw_wireframe(outimg, mesh, rendering_params.get_modelview(), rendering_params.get_projection(),
                           fitting::get_opencv_viewport(image.cols, image.rows));
    fs::path outputfile = outputbasename + ".png";
    cv::imwrite(outputfile.string(), outimg);

    // Save the mesh as textured obj:
    outputfile.replace_extension(".obj");
    core::write_textured_obj(mesh, outputfile.string());

    // And save the isomap:
    outputfile.replace_extension(".isomap.png");
    cv::imwrite(outputfile.string(), core::to_mat(isomap));

    cout << "Finished fitting and wrote result mesh and isomap to files with basename "
         << outputfile.stem().stem() << "." << endl;

    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>#include "NavSystem.h"
#include <Game/GameLevel.h>
#include <Game/ECS/GameWorld.h>
#include <Game/ECS/Components/ActionQueueComponent.h>
#include <Game/ECS/Components/SceneComponent.h>
#include <AI/Navigation/NavigationComponent.h>
#include <DetourCommon.h>

namespace DEM::AI
{

//???set height limit to check? agent height is good, must distinguish between different floors.
//otherwise false equalities may happen!
static inline bool dtVequal2D(const float* p0, const float* p1)
{
	static const float thr = dtSqr(1.0f / 16384.0f);
	const float d = dtVdist2DSqr(p0, p1);
	return d < thr;
}
//---------------------------------------------------------------------

static void UpdatePosition(const vector3& Position, CNavigationComponent& Navigation, bool& Replan)
{
	const bool PositionChanged = !dtVequal2D(Navigation.Corridor.getPos(), Position.v);

	if (PositionChanged)
	{
		if (Navigation.Mode == ENavigationMode::Offmesh)
		{
			// Not moving along the navmesh surface
			//!!!if traversing offmesh, must check its poly validity! see (!PositionChanged) below, the same is done!
		}
		else
		{
			// Try to move along the navmesh surface to the new position
			if (Navigation.Corridor.movePosition(Position.v, Navigation.pNavQuery, Navigation.pNavFilter))
			{
				dtPolyRef Ref = Navigation.Corridor.getFirstPoly();
				if (dtVequal2D(Position.v, Navigation.Corridor.getPos()))
				{
					// we are valid, must set valid state because we could be invalid before
				}
				else
				{
					// Moved too far or off the navmesh surface
					const float Extents[3] = { 0.f, pActor->Height, 0.f };
					float Nearest[3];
					Navigation.pNavQuery->findNearestPoly(Position.v, Extents, Navigation.pNavFilter, &Ref, Nearest);

					if (dtVequal2D(Position.v, Nearest))
					{
						// we are valid, must set valid state because we could be invalid before
						//???reset corridor and replan?
					}
					else
					{
						// invalid and recovery
					}
				}
			}
			else
			{
				// Invalid starting poly or args
				// set invalid and recovery
			}

			//!!!and/or trigger an offmesh connection here!
			//if triggered offmesh, finish current edge traversal!

			//!!!if corridor pos != agent pos, need to set invalid state and recovery!
		}
	}
	else
	{
		// Position is the same, but poly might become invalid
		if (!Navigation.pNavQuery->isValidPolyRef(Navigation.Corridor.getFirstPoly(), Navigation.pNavFilter))
		{
			if (Navigation.State == ENavigationState::Idle)
			{
				//???end edge traversal? must ensure that offmesh connection will be processed correctly!
				Navigation.Corridor.reset(0, Position.v);
			}
			else
			{
				//ag->state = DT_CROWDAGENT_STATE_INVALID;
				// recover to the nearest position in the corridor
				//???here or in corner building?
			}
		}
	}

	// if valid and not idle (and position changed?) (and not replanning?), check current edge traversal end

	///////////////////////////////

	if (Navigation.State == ENavigationState::Idle)
	{
		if (PositionChanged)
		{
			// reset poly
			// single-poly corridor: Corridor.reset(Corridor.getFirstPoly(), Corridor.getPos());
		}
	}
	else
	{
		//!!!instead of this condition detect entering offmesh connection!
		if (Navigation.Mode == ENavigationMode::Offmesh)
		{
			//???!!!always call moveOverOffmeshConnectionat start? then wait reaching offmesh connection end
			// if started or ended offmesh traversal and this side is "done", moveOverOffmeshConnection
		}
		else
		{
			// FIXME: if moved too far AND starting poly changed, better to replan (or check return false?)
			Navigation.Corridor.movePosition(Position.v, Navigation.pNavQuery, Navigation.pNavFilter);
			// else movePosition, if failed update position poly
		}

		// check if edge traversal sub-action is done, finalize it
		// detect both normal path edge and offmesh connection arrivals

		// DT:
		// if not on offmesh
		//   if curr poly invalid, find nearest poly and navmesh point in a target recovery radius
		//     if not found, reset corridor and drop navigation task
		//     else remember new destination point (in corridor is enough?) and replan
	}

	// set position validity flag (real == corridor, in other words real is on navmesh)
}
//---------------------------------------------------------------------

static void UpdateDestination(const vector3& Destination, CNavigationComponent& Navigation, bool& Replan)
{
	const bool TargetChanged = !dtVequal(Navigation.Destination.v, Destination.v);

	// FIXME: poly validity check still must happen!
	//if (Navigation.State != ENavigationState::Idle && !TargetChanged) return;

	Navigation.Destination = Destination;

	// FIXME: if moved too far AND target poly changed, better to replan (or check return false?)
	// if not idle, moveTargetPosition
	// else set requested, no replan

	// update dest poly if needed
	// if dest is invalid, fix to navmesh and chande desired destination, may need replan

	// DT:
	// if not idle and not on offmesh
	//   if target poly invalid, find nearest poly in a recovery radius
	//     if on found poly, corridor.fixPathStart and replan
	//     else reset corridor to zero poly and go into invalid state
}
//---------------------------------------------------------------------

void ProcessNavigation(DEM::Game::CGameWorld& World)
{
	World.ForEachEntityWith<CNavigationComponent, DEM::Game::CActionQueueComponent, const DEM::Game::CSceneComponent>(
		[](auto EntityID, auto& Entity,
			CNavigationComponent& Navigation,
			DEM::Game::CActionQueueComponent* pActions,
			const DEM::Game::CSceneComponent* pSceneComponent)
	{
		if (!pSceneComponent->RootNode) return;

		//!!!Set idle: change state, reset intermediate data, handle breaking in the middle of the offmesh connection
		//???can avoid recalculating straight path edges every frame? do on traversal end or pos changed or corridor invalid?

		if (auto pNavigateAction = pActions->FindActive<Navigate>())
		{
			//!!!TODO:
			// update time since last replan and other timers, if based on elapsed time
			// NB: in Detour replan time increases only when on navmesh (not offmesh, not invalid)

			bool Replan = false;
			const auto& Position = pSceneComponent->RootNode->GetWorldPosition();
			UpdatePosition(Position, Navigation, Replan);
			UpdateDestination(pNavigateAction->_Destination, Navigation, Replan);

			// if actor is already at the new destination, finish Navigate action (success), set idle and exit
			// if navigation failed, finish Navigate action (failure), set idle and exit
			//???if position is invalid and recovery point not found, finish Navigate action (failure), set idle and exit?

			// validate corridor CHECK_LOOKAHEAD polys ahead, if failed replan
			// if following path not waiting anything, and end is near and it's not target, replan
			if (Replan)
			{
				//request replanning
			}

			// if state is REQUEST
			//  initSlicedFindPath + updateSlicedFindPath(few iterations) for quickpath
			//  finalizeSlicedFindPath[Partial if replan]
			//  if finalized and not empty, set corridor to partial or full path, partial requires target adjusting
			//  else reset corridor to the current position (will wait for full path)
			//  if full path, set state VALID
			//  else run async request and set state WAIT_ASYNC_PATH

			//!!!async queue runs outside here! or could manage async request inside the agent itself, but multithreading
			// then will be enabled only if the current function will be dispatched across different threads. Also total
			// balancing prevents async request to be managed here!!! Need external!

			// if WAIT_ASYNC_PATH (may check only if was not just set!)
			//   get request state
			//   if failed, retry REQUEST if target is valid or set FAILED if not
			//   else if done
			//     get result, fail if empty or not retrieved or curr end != new start
			//     if has curr path, merge with new and remove trackbacks
			//     set corridor to partial or full path, partial requires target adjusting (FAILED if fail)
			//     set state VALID

			// if on navmesh and has target
			//   optimizePathTopology if it is the time and/or necessary events happened
			//   findCorners / findStraightPath
			//   optimizePathVisibility if it is the time and/or necessary events happened
			//   if in trigger range of an offmesh connection, moveOverOffmeshConnection and set OFFMESH state
			//   create sub-action for current edge traversal

			//==============

			// if corridor is invalid, trim and fix or even do full replanning
			// if we are on the navmesh and are nearing corridor end poly which is not target poly and replan time has come, replan
			// if replan flag is set after all above, we will do replanning this frame
			// replanning:
			//   try calc quick path
			//   if found, use quick path and build remaining part in parallel
			//   else find from scratch
			// if quick path was not full:
			//   if no async request, run one
			//   else check its status
			//     if failed, all navigation is failed
			//     else if done, get result and merge into a quick path and fix curr dest to the final one, setCorridor
			//     clear request info
			// if following full path (no planning awaited):
			//   optimizePathTopology periodicaly (???better on some changes?)

			// <process offmesh traversal - where and how?>
		}
		else if (Navigation.State != ENavigationState::Idle)
		{
			// set idle
		}
	});
}
//---------------------------------------------------------------------

}
<commit_msg>Navigation update from agent position progress 2<commit_after>#include "NavSystem.h"
#include <Game/GameLevel.h>
#include <Game/ECS/GameWorld.h>
#include <Game/ECS/Components/ActionQueueComponent.h>
#include <Game/ECS/Components/SceneComponent.h>
#include <AI/Navigation/NavigationComponent.h>
#include <DetourCommon.h>

namespace DEM::AI
{

//???set height limit to check? agent height is good, must distinguish between different floors.
//otherwise false equalities may happen!
static inline bool dtVequal2D(const float* p0, const float* p1)
{
	static const float thr = dtSqr(1.0f / 16384.0f);
	const float d = dtVdist2DSqr(p0, p1);
	return d < thr;
}
//---------------------------------------------------------------------

static void UpdatePosition(const vector3& Position, CNavigationComponent& Navigation, bool& Replan)
{
	const bool PositionChanged = !dtVequal2D(Navigation.Corridor.getPos(), Position.v);

	//???!!!movePosition etc only if not idle?

	if (PositionChanged)
	{
		if (Navigation.Mode == ENavigationMode::Offmesh)
		{
			// Not moving along the navmesh surface
			//!!!if traversing offmesh, must check its poly validity! see (!PositionChanged) below, the same is done!
		}
		else
		{
			// Try to move along the navmesh surface to the new position
			if (Navigation.Corridor.movePosition(Position.v, Navigation.pNavQuery, Navigation.pNavFilter))
			{
				if (dtVequal2D(Position.v, Navigation.Corridor.getPos()))
				{
					Navigation.Mode = ENavigationMode::Surface;
				}
				else
				{
					// Moved too far or off the navmesh surface
					const float Extents[3] = { 0.f, pActor->Height, 0.f };
					dtPolyRef Ref = 0;
					float Nearest[3];
					Navigation.pNavQuery->findNearestPoly(Position.v, Extents, Navigation.pNavFilter, &Ref, Nearest);

					if (Ref && dtVequal2D(Position.v, Nearest))
					{
						// We are on the navmesh, but our corridor needs rebuilding
						Navigation.Mode = ENavigationMode::Surface;
						Navigation.Corridor.reset(Ref, Position.v);
						Replan = true;
					}
					else
					{
						Navigation.Mode = ENavigationMode::Recovery;
					}
				}
			}
			else
			{
				// Invalid starting poly or args
				Navigation.Mode = ENavigationMode::Recovery;
			}

			//!!!and/or trigger an offmesh connection here!
			//if triggered offmesh, finish current edge traversal!

			//!!!if corridor pos != agent pos, need to set invalid state and recovery!
		}
	}
	else
	{
		// Position is the same, but poly might become invalid
		if (!Navigation.pNavQuery->isValidPolyRef(Navigation.Corridor.getFirstPoly(), Navigation.pNavFilter))
		{
			// find another poly under the feet, it may be valid
			//???use fixPathStart or do as above?

			if (Navigation.State == ENavigationState::Idle)
			{
				//???end edge traversal? must ensure that offmesh connection will be processed correctly!
				Navigation.Corridor.reset(0, Position.v);
			}
			else
			{
				Navigation.Mode = ENavigationMode::Recovery;
			}
		}
	}

	// if valid and not idle (and position changed?) (and not replanning?), check current edge traversal end

	///////////////////////////////

	if (Navigation.State == ENavigationState::Idle)
	{
		if (PositionChanged)
		{
			// reset poly
			// single-poly corridor: Corridor.reset(Corridor.getFirstPoly(), Corridor.getPos());
		}
	}
	else
	{
		//!!!instead of this condition detect entering offmesh connection!
		if (Navigation.Mode == ENavigationMode::Offmesh)
		{
			//???!!!always call moveOverOffmeshConnectionat start? then wait reaching offmesh connection end
			// if started or ended offmesh traversal and this side is "done", moveOverOffmeshConnection
		}
		else
		{
			// FIXME: if moved too far AND starting poly changed, better to replan (or check return false?)
			Navigation.Corridor.movePosition(Position.v, Navigation.pNavQuery, Navigation.pNavFilter);
			// else movePosition, if failed update position poly
		}

		// check if edge traversal sub-action is done, finalize it
		// detect both normal path edge and offmesh connection arrivals

		// DT:
		// if not on offmesh
		//   if curr poly invalid, find nearest poly and navmesh point in a target recovery radius
		//     if not found, reset corridor and drop navigation task
		//     else remember new destination point (in corridor is enough?) and replan
	}

	// set position validity flag (real == corridor, in other words real is on navmesh)
}
//---------------------------------------------------------------------

static void UpdateDestination(const vector3& Destination, CNavigationComponent& Navigation, bool& Replan)
{
	const bool TargetChanged = !dtVequal(Navigation.Destination.v, Destination.v);

	// FIXME: poly validity check still must happen!
	//if (Navigation.State != ENavigationState::Idle && !TargetChanged) return;

	Navigation.Destination = Destination;

	// FIXME: if moved too far AND target poly changed, better to replan (or check return false?)
	// if not idle, moveTargetPosition
	// else set requested, no replan

	// update dest poly if needed
	// if dest is invalid, fix to navmesh and chande desired destination, may need replan

	// DT:
	// if not idle and not on offmesh
	//   if target poly invalid, find nearest poly in a recovery radius
	//     if on found poly, corridor.fixPathStart and replan
	//     else reset corridor to zero poly and go into invalid state
}
//---------------------------------------------------------------------

void ProcessNavigation(DEM::Game::CGameWorld& World)
{
	World.ForEachEntityWith<CNavigationComponent, DEM::Game::CActionQueueComponent, const DEM::Game::CSceneComponent>(
		[](auto EntityID, auto& Entity,
			CNavigationComponent& Navigation,
			DEM::Game::CActionQueueComponent* pActions,
			const DEM::Game::CSceneComponent* pSceneComponent)
	{
		if (!pSceneComponent->RootNode) return;

		//!!!Set idle: change state, reset intermediate data, handle breaking in the middle of the offmesh connection
		//???can avoid recalculating straight path edges every frame? do on traversal end or pos changed or corridor invalid?

		if (auto pNavigateAction = pActions->FindActive<Navigate>())
		{
			//!!!TODO:
			// update time since last replan and other timers, if based on elapsed time
			// NB: in Detour replan time increases only when on navmesh (not offmesh, not invalid)

			bool Replan = false;
			const auto& Position = pSceneComponent->RootNode->GetWorldPosition();
			UpdatePosition(Position, Navigation, Replan);
			UpdateDestination(pNavigateAction->_Destination, Navigation, Replan);

			// if actor is already at the new destination, finish Navigate action (success), set idle and exit
			// if navigation failed, finish Navigate action (failure), set idle and exit
			//???if position is invalid and recovery point not found, finish Navigate action (failure), set idle and exit?

			// validate corridor CHECK_LOOKAHEAD polys ahead, if failed replan
			// if following path not waiting anything, and end is near and it's not target, replan
			if (Replan)
			{
				//request replanning
			}

			// if state is REQUEST
			//  initSlicedFindPath + updateSlicedFindPath(few iterations) for quickpath
			//  finalizeSlicedFindPath[Partial if replan]
			//  if finalized and not empty, set corridor to partial or full path, partial requires target adjusting
			//  else reset corridor to the current position (will wait for full path)
			//  if full path, set state VALID
			//  else run async request and set state WAIT_ASYNC_PATH

			//!!!async queue runs outside here! or could manage async request inside the agent itself, but multithreading
			// then will be enabled only if the current function will be dispatched across different threads. Also total
			// balancing prevents async request to be managed here!!! Need external!

			// if WAIT_ASYNC_PATH (may check only if was not just set!)
			//   get request state
			//   if failed, retry REQUEST if target is valid or set FAILED if not
			//   else if done
			//     get result, fail if empty or not retrieved or curr end != new start
			//     if has curr path, merge with new and remove trackbacks
			//     set corridor to partial or full path, partial requires target adjusting (FAILED if fail)
			//     set state VALID

			// if on navmesh and has target
			//   optimizePathTopology if it is the time and/or necessary events happened
			//   findCorners / findStraightPath
			//   optimizePathVisibility if it is the time and/or necessary events happened
			//   if in trigger range of an offmesh connection, moveOverOffmeshConnection and set OFFMESH state
			//   create sub-action for current edge traversal

			//==============

			// if corridor is invalid, trim and fix or even do full replanning
			// if we are on the navmesh and are nearing corridor end poly which is not target poly and replan time has come, replan
			// if replan flag is set after all above, we will do replanning this frame
			// replanning:
			//   try calc quick path
			//   if found, use quick path and build remaining part in parallel
			//   else find from scratch
			// if quick path was not full:
			//   if no async request, run one
			//   else check its status
			//     if failed, all navigation is failed
			//     else if done, get result and merge into a quick path and fix curr dest to the final one, setCorridor
			//     clear request info
			// if following full path (no planning awaited):
			//   optimizePathTopology periodicaly (???better on some changes?)

			// <process offmesh traversal - where and how?>
		}
		else if (Navigation.State != ENavigationState::Idle)
		{
			// set idle
		}
	});
}
//---------------------------------------------------------------------

}
<|endoftext|>


<commit_before>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
// See ../ops/image_ops.cc for details.
#define EIGEN_USE_THREADS

#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/platform/logging.h"

namespace tensorflow {

template <class T>
class DrawBoundingBoxesOp : public OpKernel {
 public:
  explicit DrawBoundingBoxesOp(OpKernelConstruction* context)
      : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    const Tensor& images = context->input(0);
    const Tensor& boxes = context->input(1);
    const int64 depth = images.dim_size(3);

    OP_REQUIRES(context, images.dims() == 4,
                errors::InvalidArgument("The rank of the images should be 4"));
    OP_REQUIRES(
        context, boxes.dims() == 3,
        errors::InvalidArgument("The rank of the boxes tensor should be 3"));
    OP_REQUIRES(context, images.dim_size(0) == boxes.dim_size(0),
                errors::InvalidArgument("The batch sizes should be the same"));

    OP_REQUIRES(
        context, depth == 4 || depth == 1 || depth == 3,
        errors::InvalidArgument("Channel depth should be either 1 (GRY), "
                                "3 (RGB), or 4 (RGBA)"));

    const int64 batch_size = images.dim_size(0);
    const int64 height = images.dim_size(1);
    const int64 width = images.dim_size(2);
    const int64 default_color_table_length = 10;

    // 0: yellow
    // 1: blue
    // 2: red
    // 3: lime
    // 4: purple
    // 5: olive
    // 6: maroon
    // 7: navy blue
    // 8: aqua
    // 9: fuchsia
    float default_color_table[default_color_table_length][4] = {
        {1, 1, 0, 1},     {0, 0, 1, 1},     {1, 0, 0, 1},   {0, 1, 0, 1},
        {0.5, 0, 0.5, 1}, {0.5, 0.5, 0, 1}, {0.5, 0, 0, 1}, {0, 0, 0.5, 1},
        {0, 1, 1, 1},     {1, 0, 1, 1},
    };

    std::vector<std::vector<float>> color_table;
    for (int64 i = 0; i < default_color_table_length; i++) {
      std::vector<float> color_value(4);
      for (int64 j = 0; j < 4; j++) {
        color_value[j] = default_color_table[i][j];
      }
      color_table.emplace_back(color_value);
    }

    // Reset first color channel to 1 if image is GRY.
    // For GRY images, this means all bounding boxes will be white.
    if (depth == 1) {
      for (int64 i = 0; i < color_table.size(); i++) {
        color_table[i][0] = 1;
      }
    }
    if (context->num_inputs() == 3) {
      const Tensor& colors_tensor = context->input(2);
      OP_REQUIRES(context, colors_tensor.shape().dims() == 2,
                  errors::InvalidArgument("colors must be a 2-D matrix",
                                          colors_tensor.shape().DebugString()));
      OP_REQUIRES(context, colors_tensor.shape().dim_size(1) == 4,
                  errors::InvalidArgument("colors must be n x 4 (RGBA)",
                                          colors_tensor.shape().DebugString()));
      if (colors_tensor.NumElements() != 0) {
        color_table.clear();

        auto colors = colors_tensor.matrix<float>();
        for (int64 i = 0; i < colors.dimension(0); i++) {
          std::vector<float> color_value(4);
          for (int64 j = 0; j < 4; j++) {
            color_value[j] = colors(i, j);
          }
          color_table.emplace_back(color_value);
        }
      }
    }
    Tensor* output;
    OP_REQUIRES_OK(
        context,
        context->allocate_output(
            0, TensorShape({batch_size, height, width, depth}), &output));

    output->tensor<T, 4>() = images.tensor<T, 4>();
    auto canvas = output->tensor<T, 4>();

    for (int64 b = 0; b < batch_size; ++b) {
      const int64 num_boxes = boxes.dim_size(1);
      const auto tboxes = boxes.tensor<T, 3>();
      for (int64 bb = 0; bb < num_boxes; ++bb) {
        int64 color_index = bb % color_table.size();
        const int64 min_box_row =
            static_cast<float>(tboxes(b, bb, 0)) * (height - 1);
        const int64 min_box_row_clamp = std::max<int64>(min_box_row, int64{0});
        const int64 max_box_row =
            static_cast<float>(tboxes(b, bb, 2)) * (height - 1);
        const int64 max_box_row_clamp =
            std::min<int64>(max_box_row, height - 1);
        const int64 min_box_col =
            static_cast<float>(tboxes(b, bb, 1)) * (width - 1);
        const int64 min_box_col_clamp = std::max<int64>(min_box_col, int64{0});
        const int64 max_box_col =
            static_cast<float>(tboxes(b, bb, 3)) * (width - 1);
        const int64 max_box_col_clamp = std::min<int64>(max_box_col, width - 1);

        if (min_box_row > max_box_row || min_box_col > max_box_col) {
          LOG(WARNING) << "Bounding box (" << min_box_row << "," << min_box_col
                       << "," << max_box_row << "," << max_box_col
                       << ") is inverted and will not be drawn.";
          continue;
        }
        if (min_box_row >= height || max_box_row < 0 || min_box_col >= width ||
            max_box_col < 0) {
          LOG(WARNING) << "Bounding box (" << min_box_row << "," << min_box_col
                       << "," << max_box_row << "," << max_box_col
                       << ") is completely outside the image"
                       << " and will not be drawn.";
          continue;
        }

        // At this point, {min,max}_box_{row,col}_clamp are inside the
        // image.
        CHECK_GE(min_box_row_clamp, 0);
        CHECK_GE(max_box_row_clamp, 0);
        CHECK_LT(min_box_row_clamp, height);
        CHECK_LT(max_box_row_clamp, height);
        CHECK_GE(min_box_col_clamp, 0);
        CHECK_GE(max_box_col_clamp, 0);
        CHECK_LT(min_box_col_clamp, width);
        CHECK_LT(max_box_col_clamp, width);

        // At this point, the min_box_row and min_box_col are either
        // in the image or above/left of it, and max_box_row and
        // max_box_col are either in the image or below/right or it.
        CHECK_LT(min_box_row, height);
        CHECK_GE(max_box_row, 0);
        CHECK_LT(min_box_col, width);
        CHECK_GE(max_box_col, 0);

        // Draw top line.
        if (min_box_row >= 0) {
          for (int64 j = min_box_col_clamp; j <= max_box_col_clamp; ++j)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, min_box_row, j, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
        // Draw bottom line.
        if (max_box_row < height) {
          for (int64 j = min_box_col_clamp; j <= max_box_col_clamp; ++j)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, max_box_row, j, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
        // Draw left line.
        if (min_box_col >= 0) {
          for (int64 i = min_box_row_clamp; i <= max_box_row_clamp; ++i)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, i, min_box_col, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
        // Draw right line.
        if (max_box_col < width) {
          for (int64 i = min_box_row_clamp; i <= max_box_row_clamp; ++i)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, i, max_box_col, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
      }
    }
  }
};

#define REGISTER_CPU_KERNEL(T)                                               \
  REGISTER_KERNEL_BUILDER(                                                   \
      Name("DrawBoundingBoxes").Device(DEVICE_CPU).TypeConstraint<T>("T"),   \
      DrawBoundingBoxesOp<T>);                                               \
  REGISTER_KERNEL_BUILDER(                                                   \
      Name("DrawBoundingBoxesV2").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
      DrawBoundingBoxesOp<T>);
TF_CALL_half(REGISTER_CPU_KERNEL);
TF_CALL_float(REGISTER_CPU_KERNEL);

}  // namespace tensorflow
<commit_msg>Use DefaultColorTable utility function<commit_after>/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
// See ../ops/image_ops.cc for details.
#define EIGEN_USE_THREADS

#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/platform/logging.h"

namespace tensorflow {

namespace {

std::vector<std::vector<float>> DefaultColorTable(int depth) {
  std::vector<std::vector<float>> color_table;
  color_table.emplace_back(std::vector<float>({1, 1, 0, 1})); // 0: yellow
  color_table.emplace_back(std::vector<float>({0, 0, 1, 1})); // 1: blue
  color_table.emplace_back(std::vector<float>({1, 0, 0, 1})); // 2: red
  color_table.emplace_back(std::vector<float>({0, 1, 0, 1})); // 3: lime
  color_table.emplace_back(std::vector<float>({0.5, 0, 0.5, 1})); // 4: purple
  color_table.emplace_back(std::vector<float>({0.5, 0.5, 0, 1})); // 5: olive
  color_table.emplace_back(std::vector<float>({0.5, 0, 0, 1})); // 6: maroon
  color_table.emplace_back(std::vector<float>({0, 0, 0.5, 1})); // 7: navy blue
  color_table.emplace_back(std::vector<float>({0, 1, 1, 1})); // 8: aqua
  color_table.emplace_back(std::vector<float>({1, 0, 1, 1})); // 9: fuchsia

  if (depth == 1) {
    for (int64 i = 0; i < color_table.size(); i++) {
      color_table[i][0] = 1;
    }
  }
  return  color_table;
}
}  // namespace

template <class T>
class DrawBoundingBoxesOp : public OpKernel {
 public:
  explicit DrawBoundingBoxesOp(OpKernelConstruction* context)
      : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    const Tensor& images = context->input(0);
    const Tensor& boxes = context->input(1);
    const int64 depth = images.dim_size(3);

    OP_REQUIRES(context, images.dims() == 4,
                errors::InvalidArgument("The rank of the images should be 4"));
    OP_REQUIRES(
        context, boxes.dims() == 3,
        errors::InvalidArgument("The rank of the boxes tensor should be 3"));
    OP_REQUIRES(context, images.dim_size(0) == boxes.dim_size(0),
                errors::InvalidArgument("The batch sizes should be the same"));

    OP_REQUIRES(
        context, depth == 4 || depth == 1 || depth == 3,
        errors::InvalidArgument("Channel depth should be either 1 (GRY), "
                                "3 (RGB), or 4 (RGBA)"));

    const int64 batch_size = images.dim_size(0);
    const int64 height = images.dim_size(1);
    const int64 width = images.dim_size(2);
    std::vector<std::vector<float>> color_table;
    if (context->num_inputs() == 3) {
      const Tensor& colors_tensor = context->input(2);
      OP_REQUIRES(context, colors_tensor.shape().dims() == 2,
                  errors::InvalidArgument("colors must be a 2-D matrix",
                                          colors_tensor.shape().DebugString()));
      OP_REQUIRES(context, colors_tensor.shape().dim_size(1) >= depth,
                  errors::InvalidArgument("colors must have equal or more ",
                                          "channels than the image provided: ",
                                          colors_tensor.shape().DebugString()));
      if (colors_tensor.NumElements() != 0) {
        color_table.clear();

        auto colors = colors_tensor.matrix<float>();
        for (int64 i = 0; i < colors.dimension(0); i++) {
          std::vector<float> color_value(4);
          for (int64 j = 0; j < 4; j++) {
            color_value[j] = colors(i, j);
          }
          color_table.emplace_back(color_value);
        }
      }
    }
    if (color_table.empty()) {
      color_table = DefaultColorTable(depth);
    }
    Tensor* output;
    OP_REQUIRES_OK(
        context,
        context->allocate_output(
            0, TensorShape({batch_size, height, width, depth}), &output));

    output->tensor<T, 4>() = images.tensor<T, 4>();
    auto canvas = output->tensor<T, 4>();

    for (int64 b = 0; b < batch_size; ++b) {
      const int64 num_boxes = boxes.dim_size(1);
      const auto tboxes = boxes.tensor<T, 3>();
      for (int64 bb = 0; bb < num_boxes; ++bb) {
        int64 color_index = bb % color_table.size();
        const int64 min_box_row =
            static_cast<float>(tboxes(b, bb, 0)) * (height - 1);
        const int64 min_box_row_clamp = std::max<int64>(min_box_row, int64{0});
        const int64 max_box_row =
            static_cast<float>(tboxes(b, bb, 2)) * (height - 1);
        const int64 max_box_row_clamp =
            std::min<int64>(max_box_row, height - 1);
        const int64 min_box_col =
            static_cast<float>(tboxes(b, bb, 1)) * (width - 1);
        const int64 min_box_col_clamp = std::max<int64>(min_box_col, int64{0});
        const int64 max_box_col =
            static_cast<float>(tboxes(b, bb, 3)) * (width - 1);
        const int64 max_box_col_clamp = std::min<int64>(max_box_col, width - 1);

        if (min_box_row > max_box_row || min_box_col > max_box_col) {
          LOG(WARNING) << "Bounding box (" << min_box_row << "," << min_box_col
                       << "," << max_box_row << "," << max_box_col
                       << ") is inverted and will not be drawn.";
          continue;
        }
        if (min_box_row >= height || max_box_row < 0 || min_box_col >= width ||
            max_box_col < 0) {
          LOG(WARNING) << "Bounding box (" << min_box_row << "," << min_box_col
                       << "," << max_box_row << "," << max_box_col
                       << ") is completely outside the image"
                       << " and will not be drawn.";
          continue;
        }

        // At this point, {min,max}_box_{row,col}_clamp are inside the
        // image.
        CHECK_GE(min_box_row_clamp, 0);
        CHECK_GE(max_box_row_clamp, 0);
        CHECK_LT(min_box_row_clamp, height);
        CHECK_LT(max_box_row_clamp, height);
        CHECK_GE(min_box_col_clamp, 0);
        CHECK_GE(max_box_col_clamp, 0);
        CHECK_LT(min_box_col_clamp, width);
        CHECK_LT(max_box_col_clamp, width);

        // At this point, the min_box_row and min_box_col are either
        // in the image or above/left of it, and max_box_row and
        // max_box_col are either in the image or below/right or it.
        CHECK_LT(min_box_row, height);
        CHECK_GE(max_box_row, 0);
        CHECK_LT(min_box_col, width);
        CHECK_GE(max_box_col, 0);

        // Draw top line.
        if (min_box_row >= 0) {
          for (int64 j = min_box_col_clamp; j <= max_box_col_clamp; ++j)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, min_box_row, j, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
        // Draw bottom line.
        if (max_box_row < height) {
          for (int64 j = min_box_col_clamp; j <= max_box_col_clamp; ++j)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, max_box_row, j, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
        // Draw left line.
        if (min_box_col >= 0) {
          for (int64 i = min_box_row_clamp; i <= max_box_row_clamp; ++i)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, i, min_box_col, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
        // Draw right line.
        if (max_box_col < width) {
          for (int64 i = min_box_row_clamp; i <= max_box_row_clamp; ++i)
            for (int64 c = 0; c < depth; c++) {
              canvas(b, i, max_box_col, c) =
                  static_cast<T>(color_table[color_index][c]);
            }
        }
      }
    }
  }
};

#define REGISTER_CPU_KERNEL(T)                                               \
  REGISTER_KERNEL_BUILDER(                                                   \
      Name("DrawBoundingBoxes").Device(DEVICE_CPU).TypeConstraint<T>("T"),   \
      DrawBoundingBoxesOp<T>);                                               \
  REGISTER_KERNEL_BUILDER(                                                   \
      Name("DrawBoundingBoxesV2").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
      DrawBoundingBoxesOp<T>);
TF_CALL_half(REGISTER_CPU_KERNEL);
TF_CALL_float(REGISTER_CPU_KERNEL);

}  // namespace tensorflow
<|endoftext|>


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

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

#include <boost/range/irange.hpp>
#include "tests/cql_test_env.hh"
#include "tests/perf/perf.hh"
#include "core/app-template.hh"

#include "disk-error-handler.hh"

thread_local disk_error_signal_type commit_error;
thread_local disk_error_signal_type general_disk_error;

static const sstring table_name = "cf";

static bytes make_key(uint64_t sequence) {
    bytes b(bytes::initialized_later(), sizeof(sequence));
    auto i = b.begin();
    write<uint64_t>(i, sequence);
    return b;
};

static auto execute_update_for_key(cql_test_env& env, const bytes& key) {
    return env.execute_cql(sprint("UPDATE cf SET "
        "\"C0\" = 0x8f75da6b3dcec90c8a404fb9a5f6b0621e62d39c69ba5758e5f41b78311fbb26cc7a,"
        "\"C1\" = 0xa8761a2127160003033a8f4f3d1069b7833ebe24ef56b3beee728c2b686ca516fa51,"
        "\"C2\" = 0x583449ce81bfebc2e1a695eb59aad5fcc74d6d7311fc6197b10693e1a161ca2e1c64,"
        "\"C3\" = 0x62bcb1dbc0ff953abc703bcb63ea954f437064c0c45366799658bd6b91d0f92908d7,"
        "\"C4\" = 0x222fcbe31ffa1e689540e1499b87fa3f9c781065fccd10e4772b4c7039c2efd0fb27 "
        "WHERE \"KEY\"= 0x%s;", to_hex(key))).discard_result();
};

struct test_config {
    enum class run_mode { read, write };

    run_mode mode;
    unsigned partitions;
    unsigned concurrency;
    bool query_single_key;
    unsigned duration_in_seconds;
};

std::ostream& operator<<(std::ostream& os, const test_config::run_mode& m) {
    switch (m) {
        case test_config::run_mode::write: return os << "write";
        case test_config::run_mode::read: return os << "read";
    }
    assert(0);
}

std::ostream& operator<<(std::ostream& os, const test_config& cfg) {
    return os << "{partitions=" << cfg.partitions
           << ", concurrency=" << cfg.concurrency
           << ", mode=" << cfg.mode
           << ", query_single_key=" << (cfg.query_single_key ? "yes" : "no")
           << "}";
}

future<> test_read(cql_test_env& env, test_config& cfg) {
    std::cout << "Creating " << cfg.partitions << " partitions..." << std::endl;
    auto partitions = boost::irange(0, (int)cfg.partitions);
    return do_for_each(partitions.begin(), partitions.end(), [&env](int sequence) {
        return execute_update_for_key(env, make_key(sequence));
    }).then([&env] {
        return env.prepare("select \"C0\", \"C1\", \"C2\", \"C3\", \"C4\" from cf where \"KEY\" = ?");
    }).then([&env, &cfg](auto id) {
        return time_parallel([&env, &cfg, id] {
            bytes key = make_key(cfg.query_single_key ? 0 : std::rand() % cfg.partitions);
            return env.execute_prepared(id, {{cql3::raw_value::make_value(std::move(key))}}).discard_result();
        }, cfg.concurrency, cfg.duration_in_seconds);
    });
}

future<> test_write(cql_test_env& env, test_config& cfg) {
    return env.prepare("UPDATE cf SET "
                           "\"C0\" = 0x8f75da6b3dcec90c8a404fb9a5f6b0621e62d39c69ba5758e5f41b78311fbb26cc7a,"
                           "\"C1\" = 0xa8761a2127160003033a8f4f3d1069b7833ebe24ef56b3beee728c2b686ca516fa51,"
                           "\"C2\" = 0x583449ce81bfebc2e1a695eb59aad5fcc74d6d7311fc6197b10693e1a161ca2e1c64,"
                           "\"C3\" = 0x62bcb1dbc0ff953abc703bcb63ea954f437064c0c45366799658bd6b91d0f92908d7,"
                           "\"C4\" = 0x222fcbe31ffa1e689540e1499b87fa3f9c781065fccd10e4772b4c7039c2efd0fb27 "
                           "WHERE \"KEY\" = ?;")
        .then([&env, &cfg](auto id) {
            return time_parallel([&env, &cfg, id] {
                bytes key = make_key(cfg.query_single_key ? 0 : std::rand() % cfg.partitions);
                return env.execute_prepared(id, {{cql3::raw_value::make_value(std::move(key))}}).discard_result();
            }, cfg.concurrency, cfg.duration_in_seconds);
        });
}

future<> do_test(cql_test_env& env, test_config& cfg) {
    std::cout << "Running test with config: " << cfg << std::endl;
    return env.create_table([] (auto ks_name) {
        return schema({}, ks_name, "cf",
                {{"KEY", bytes_type}},
                {},
                {{"C0", bytes_type}, {"C1", bytes_type}, {"C2", bytes_type}, {"C3", bytes_type}, {"C4", bytes_type}},
                {},
                utf8_type);
    }).then([&env, &cfg] {
        switch (cfg.mode) {
            case test_config::run_mode::read:
                return test_read(env, cfg);
            case test_config::run_mode::write:
                return test_write(env, cfg);
        };
        assert(0);
    });
}

int main(int argc, char** argv) {
    namespace bpo = boost::program_options;
    app_template app;
    app.add_options()
        ("partitions", bpo::value<unsigned>()->default_value(10000), "number of partitions")
        ("write", "test write path instead of read path")
        ("duration", bpo::value<unsigned>()->default_value(5), "test duration in seconds")
        ("query-single-key", "test write path instead of read path")
        ("concurrency", bpo::value<unsigned>()->default_value(100), "workers per core");

    return app.run(argc, argv, [&app] {
        return do_with_cql_env([&app] (auto&& env) {
            auto cfg = make_lw_shared<test_config>();
            cfg->partitions = app.configuration()["partitions"].as<unsigned>();
            cfg->duration_in_seconds = app.configuration()["duration"].as<unsigned>();
            cfg->concurrency = app.configuration()["concurrency"].as<unsigned>();
            cfg->mode = app.configuration().count("write") ? test_config::run_mode::write : test_config::run_mode::read;
            cfg->query_single_key = app.configuration().count("query-single-key");
            return do_test(env, *cfg).finally([cfg] {});
        });
    });
}
<commit_msg>perf_simple_query: add counter tables tests<commit_after>/*
 * Copyright (C) 2015 ScyllaDB
 */

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

#include <boost/range/irange.hpp>
#include "tests/cql_test_env.hh"
#include "tests/perf/perf.hh"
#include "core/app-template.hh"
#include "schema_builder.hh"

#include "disk-error-handler.hh"

thread_local disk_error_signal_type commit_error;
thread_local disk_error_signal_type general_disk_error;

static const sstring table_name = "cf";

static bytes make_key(uint64_t sequence) {
    bytes b(bytes::initialized_later(), sizeof(sequence));
    auto i = b.begin();
    write<uint64_t>(i, sequence);
    return b;
};

static auto execute_update_for_key(cql_test_env& env, const bytes& key) {
    return env.execute_cql(sprint("UPDATE cf SET "
        "\"C0\" = 0x8f75da6b3dcec90c8a404fb9a5f6b0621e62d39c69ba5758e5f41b78311fbb26cc7a,"
        "\"C1\" = 0xa8761a2127160003033a8f4f3d1069b7833ebe24ef56b3beee728c2b686ca516fa51,"
        "\"C2\" = 0x583449ce81bfebc2e1a695eb59aad5fcc74d6d7311fc6197b10693e1a161ca2e1c64,"
        "\"C3\" = 0x62bcb1dbc0ff953abc703bcb63ea954f437064c0c45366799658bd6b91d0f92908d7,"
        "\"C4\" = 0x222fcbe31ffa1e689540e1499b87fa3f9c781065fccd10e4772b4c7039c2efd0fb27 "
        "WHERE \"KEY\"= 0x%s;", to_hex(key))).discard_result();
};

static auto execute_counter_update_for_key(cql_test_env& env, const bytes& key) {
    return env.execute_cql(sprint("UPDATE cf SET "
        "\"C0\" = \"C0\" + 1,"
        "\"C1\" = \"C1\" + 2,"
        "\"C2\" = \"C2\" + 3,"
        "\"C3\" = \"C3\" + 4,"
        "\"C4\" = \"C4\" + 5 "
        "WHERE \"KEY\"= 0x%s;", to_hex(key))).discard_result();
};

struct test_config {
    enum class run_mode { read, write };

    run_mode mode;
    unsigned partitions;
    unsigned concurrency;
    bool query_single_key;
    unsigned duration_in_seconds;
    bool counters;
};

std::ostream& operator<<(std::ostream& os, const test_config::run_mode& m) {
    switch (m) {
        case test_config::run_mode::write: return os << "write";
        case test_config::run_mode::read: return os << "read";
    }
    assert(0);
}

std::ostream& operator<<(std::ostream& os, const test_config& cfg) {
    return os << "{partitions=" << cfg.partitions
           << ", concurrency=" << cfg.concurrency
           << ", mode=" << cfg.mode
           << ", query_single_key=" << (cfg.query_single_key ? "yes" : "no")
           << ", counters=" << (cfg.counters ? "yes" : "no")
           << "}";
}

future<> test_read(cql_test_env& env, test_config& cfg) {
    std::cout << "Creating " << cfg.partitions << " partitions..." << std::endl;
    auto partitions = boost::irange(0, (int)cfg.partitions);
    return do_for_each(partitions.begin(), partitions.end(), [&] (int sequence) {
        if (cfg.counters) {
            return execute_counter_update_for_key(env, make_key(sequence));
        }
        return execute_update_for_key(env, make_key(sequence));
    }).then([&env] {
        return env.prepare("select \"C0\", \"C1\", \"C2\", \"C3\", \"C4\" from cf where \"KEY\" = ?");
    }).then([&env, &cfg](auto id) {
        return time_parallel([&env, &cfg, id] {
            bytes key = make_key(cfg.query_single_key ? 0 : std::rand() % cfg.partitions);
            return env.execute_prepared(id, {{cql3::raw_value::make_value(std::move(key))}}).discard_result();
        }, cfg.concurrency, cfg.duration_in_seconds);
    });
}

future<> test_write(cql_test_env& env, test_config& cfg) {
    return env.prepare("UPDATE cf SET "
                           "\"C0\" = 0x8f75da6b3dcec90c8a404fb9a5f6b0621e62d39c69ba5758e5f41b78311fbb26cc7a,"
                           "\"C1\" = 0xa8761a2127160003033a8f4f3d1069b7833ebe24ef56b3beee728c2b686ca516fa51,"
                           "\"C2\" = 0x583449ce81bfebc2e1a695eb59aad5fcc74d6d7311fc6197b10693e1a161ca2e1c64,"
                           "\"C3\" = 0x62bcb1dbc0ff953abc703bcb63ea954f437064c0c45366799658bd6b91d0f92908d7,"
                           "\"C4\" = 0x222fcbe31ffa1e689540e1499b87fa3f9c781065fccd10e4772b4c7039c2efd0fb27 "
                           "WHERE \"KEY\" = ?;")
        .then([&env, &cfg](auto id) {
            return time_parallel([&env, &cfg, id] {
                bytes key = make_key(cfg.query_single_key ? 0 : std::rand() % cfg.partitions);
                return env.execute_prepared(id, {{cql3::raw_value::make_value(std::move(key))}}).discard_result();
            }, cfg.concurrency, cfg.duration_in_seconds);
        });
}

future<> test_counter_update(cql_test_env& env, test_config& cfg) {
    return env.prepare("UPDATE cf SET "
                           "\"C0\" = \"C0\" + 1,"
                           "\"C1\" = \"C1\" + 2,"
                           "\"C2\" = \"C2\" + 3,"
                           "\"C3\" = \"C3\" + 4,"
                           "\"C4\" = \"C4\" + 5"
                           "WHERE \"KEY\" = ?;")
        .then([&env, &cfg] (auto id) {
            return time_parallel([&env, &cfg, id] {
                bytes key = make_key(cfg.query_single_key ? 0 : std::rand() % cfg.partitions);
                return env.execute_prepared(id, {{cql3::raw_value::make_value(std::move(key))}}).discard_result();
            }, cfg.concurrency, cfg.duration_in_seconds);
        });
}

schema_ptr make_counter_schema(const sstring& ks_name) {
    return schema_builder(ks_name, "cf")
            .with_column("KEY", bytes_type, column_kind::partition_key)
            .with_column("C0", counter_type)
            .with_column("C1", counter_type)
            .with_column("C2", counter_type)
            .with_column("C3", counter_type)
            .with_column("C4", counter_type)
            .build();
}

future<> do_test(cql_test_env& env, test_config& cfg) {
    std::cout << "Running test with config: " << cfg << std::endl;
    return env.create_table([&cfg] (auto ks_name) {
        if (cfg.counters) {
            return *make_counter_schema(ks_name);
        }
        return schema({}, ks_name, "cf",
                {{"KEY", bytes_type}},
                {},
                {{"C0", bytes_type}, {"C1", bytes_type}, {"C2", bytes_type}, {"C3", bytes_type}, {"C4", bytes_type}},
                {},
                utf8_type);
    }).then([&env, &cfg] {
        switch (cfg.mode) {
            case test_config::run_mode::read:
                return test_read(env, cfg);
            case test_config::run_mode::write:
                if (cfg.counters) {
                    return test_counter_update(env, cfg);
                } else {
                    return test_write(env, cfg);
                }
        };
        assert(0);
    });
}

int main(int argc, char** argv) {
    namespace bpo = boost::program_options;
    app_template app;
    app.add_options()
        ("partitions", bpo::value<unsigned>()->default_value(10000), "number of partitions")
        ("write", "test write path instead of read path")
        ("duration", bpo::value<unsigned>()->default_value(5), "test duration in seconds")
        ("query-single-key", "test write path instead of read path")
        ("concurrency", bpo::value<unsigned>()->default_value(100), "workers per core")
        ("counters", "test counters");

    return app.run(argc, argv, [&app] {
        return do_with_cql_env([&app] (auto&& env) {
            auto cfg = make_lw_shared<test_config>();
            cfg->partitions = app.configuration()["partitions"].as<unsigned>();
            cfg->duration_in_seconds = app.configuration()["duration"].as<unsigned>();
            cfg->concurrency = app.configuration()["concurrency"].as<unsigned>();
            cfg->mode = app.configuration().count("write") ? test_config::run_mode::write : test_config::run_mode::read;
            cfg->query_single_key = app.configuration().count("query-single-key");
            cfg->counters = app.configuration().count("counters");
            return do_test(env, *cfg).finally([cfg] {});
        });
    });
}
<|endoftext|>


<commit_before>#include <QtCore/QDebug>
#include <QtCore/QTimer>

#include <QtDBus/QtDBus>

#include <TelepathyQt4/Constants>
#include <TelepathyQt4/Client/DBus>
#include <TelepathyQt4/Client/Channel>
#include <TelepathyQt4/Client/Connection>
#include <TelepathyQt4/Client/ConnectionManager>

#include <tests/pinocchio/lib.h>

using namespace Telepathy::Client;

class TestChanBasics : public PinocchioTest
{
    Q_OBJECT

private:
    ConnectionManagerInterface* mCM;

    QString mConnBusName;
    QString mConnObjectPath;
    Connection *mConn;

    uint mSubscribeHandle;
    QString mSubscribeChanObjectPath;
    Channel *mChan;

protected Q_SLOTS:
    void expectConnReady(uint);
    void expectChanReady(uint);
    void expectSuccessfulCall(QDBusPendingCallWatcher*);

private Q_SLOTS:
    void initTestCase();
    void init();

    void testBasics();

    void cleanup();
    void cleanupTestCase();
};

void TestChanBasics::expectSuccessfulCall(QDBusPendingCallWatcher* watcher)
{
    QDBusPendingReply<> reply = *watcher;

    if (reply.isError()) {
        qWarning().nospace() << reply.error().name()
            << ": " << reply.error().message();
        mLoop->exit(1);
        return;
    }

    mLoop->exit(0);
}

void TestChanBasics::expectConnReady(uint newReadiness)
{
    switch (newReadiness) {
    case Connection::ReadinessJustCreated:
        qWarning() << "Changing from NYC to JustCreated is silly";
        mLoop->exit(1);
        break;
    case Connection::ReadinessNotYetConnected:
        qWarning() << "Changing from NYC to NYC is silly";
        mLoop->exit(2);
        break;
    case Connection::ReadinessConnecting:
        /* do nothing */
        break;
    case Connection::ReadinessFull:
        qDebug() << "Ready";
        mLoop->exit(0);
        break;
    case Connection::ReadinessDead:
        qWarning() << "Dead!";
        mLoop->exit(3);
        break;
    default:
        qWarning().nospace() << "What sort of readiness is "
            << newReadiness << "?!";
        mLoop->exit(4);
        break;
    }
}

void TestChanBasics::initTestCase()
{
    initTestCaseImpl();

    // Wait for the CM to start up
    QVERIFY(waitForPinocchio(5000));

    // Escape to the low-level API to make a Connection; this uses
    // pseudo-blocking calls for simplicity. Do not do this in production code

    mCM = new Telepathy::Client::ConnectionManagerInterface(
        pinocchioBusName(), pinocchioObjectPath());

    QDBusPendingReply<QString, QDBusObjectPath> reply;
    QVariantMap parameters;
    parameters.insert(QLatin1String("account"),
        QVariant::fromValue(QString::fromAscii("empty")));
    parameters.insert(QLatin1String("password"),
        QVariant::fromValue(QString::fromAscii("s3kr1t")));

    reply = mCM->RequestConnection("dummy", parameters);
    reply.waitForFinished();
    if (!reply.isValid()) {
        qWarning().nospace() << reply.error().name()
            << ": " << reply.error().message();
        QVERIFY(reply.isValid());
    }
    mConnBusName = reply.argumentAt<0>();
    mConnObjectPath = reply.argumentAt<1>().path();

    // Get a connected Connection
    mConn = new Connection(mConnBusName, mConnObjectPath);

    QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(
            mConn->Connect());
    QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher*))));
    QCOMPARE(mLoop->exec(), 0);
    delete watcher;

    QVERIFY(connect(mConn, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectConnReady(uint))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(mConn, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectConnReady(uint))));

    // Using direct access to low-level stuff here, so we can test the
    // Channel constructor directly
    QDBusPendingReply<Telepathy::UIntList> requestHandlesReply =
        mConn->RequestHandles(Telepathy::HandleTypeList,
            QStringList() << "subscribe");
    requestHandlesReply.waitForFinished();
    mSubscribeHandle = requestHandlesReply.value().at(0);

    QDBusPendingReply<QDBusObjectPath> requestChannelReply =
        mConn->RequestChannel(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST,
            Telepathy::HandleTypeList, mSubscribeHandle, true);
    requestChannelReply.waitForFinished();
    mSubscribeChanObjectPath = requestChannelReply.value().path();
}

void TestChanBasics::init()
{
    initImpl();
}

void TestChanBasics::expectChanReady(uint newReadiness)
{
    switch (newReadiness) {
    case Channel::ReadinessJustCreated:
        qWarning() << "Changing from JustCreated to JustCreated is silly";
        mLoop->exit(1);
        break;
    case Channel::ReadinessFull:
        qDebug() << "Ready";
        mLoop->exit(0);
        break;
    case Channel::ReadinessClosed:
        // fall through
    case Channel::ReadinessDead:
        qWarning() << "Dead or closed!";
        mLoop->exit(3);
        break;
    default:
        qWarning().nospace() << "What sort of readiness is "
            << newReadiness << "?!";
        mLoop->exit(4);
        break;
    }
}

void TestChanBasics::testBasics()
{
    mChan = new Channel(mConn, mSubscribeChanObjectPath);

    QCOMPARE(mChan->readiness(), Channel::ReadinessJustCreated);
    QEXPECT_FAIL("", "Doesn't seem to work", Continue);
    QCOMPARE(mChan->connection(), mConn);

    // Wait for readiness to reach Full

    qDebug() << "waiting for Full readiness";
    QVERIFY(connect(mChan, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectChanReady(uint))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(mChan, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectChanReady(uint))));

    QCOMPARE(mChan->readiness(), Channel::ReadinessFull);

    QCOMPARE(mChan->channelType(),
        QString::fromAscii(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
    QCOMPARE(mChan->targetHandleType(),
        static_cast<uint>(Telepathy::HandleTypeList));
    QCOMPARE(mChan->targetHandle(), mSubscribeHandle);

    delete mChan;
}

void TestChanBasics::cleanup()
{
    cleanupImpl();
}

void TestChanBasics::cleanupTestCase()
{
    QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(
            mConn->Disconnect());
    QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher*))));
    QCOMPARE(mLoop->exec(), 0);
    delete watcher;

    delete mConn;
    mConn = NULL;

    delete mCM;
    mCM = NULL;

    cleanupTestCaseImpl();
}

QTEST_MAIN(TestChanBasics)
#include "_gen/chan-basics.cpp.moc.hpp"
<commit_msg>TestChanBasics: exercise PendingChannel<commit_after>#include <QtCore/QDebug>
#include <QtCore/QTimer>

#include <QtDBus/QtDBus>

#include <TelepathyQt4/Constants>
#include <TelepathyQt4/Client/DBus>
#include <TelepathyQt4/Client/Channel>
#include <TelepathyQt4/Client/Connection>
#include <TelepathyQt4/Client/ConnectionManager>

#include <tests/pinocchio/lib.h>

using namespace Telepathy::Client;

class TestChanBasics : public PinocchioTest
{
    Q_OBJECT

private:
    ConnectionManagerInterface* mCM;

    QString mConnBusName;
    QString mConnObjectPath;
    Connection *mConn;

    uint mSubscribeHandle;
    QString mSubscribeChanObjectPath;
    Channel *mChan;

protected Q_SLOTS:
    // these ought to be private, but if they were, QTest would think they
    // were test cases. So, they're protected instead
    void expectConnReady(uint);
    void expectChanReady(uint);
    void expectSuccessfulCall(QDBusPendingCallWatcher*);
    void expectPendingChannelFinished(Telepathy::Client::PendingChannel*);
    void expectPendingChannelError(Telepathy::Client::PendingChannel*);

private Q_SLOTS:
    void initTestCase();
    void init();

    void testBasics();
    void testPendingChannel();
    void testPendingChannelError();

    void cleanup();
    void cleanupTestCase();
};

void TestChanBasics::expectSuccessfulCall(QDBusPendingCallWatcher* watcher)
{
    QDBusPendingReply<> reply = *watcher;

    if (reply.isError()) {
        qWarning().nospace() << reply.error().name()
            << ": " << reply.error().message();
        mLoop->exit(1);
        return;
    }

    mLoop->exit(0);
}

void TestChanBasics::expectConnReady(uint newReadiness)
{
    switch (newReadiness) {
    case Connection::ReadinessJustCreated:
        qWarning() << "Changing from NYC to JustCreated is silly";
        mLoop->exit(1);
        break;
    case Connection::ReadinessNotYetConnected:
        qWarning() << "Changing from NYC to NYC is silly";
        mLoop->exit(2);
        break;
    case Connection::ReadinessConnecting:
        /* do nothing */
        break;
    case Connection::ReadinessFull:
        qDebug() << "Ready";
        mLoop->exit(0);
        break;
    case Connection::ReadinessDead:
        qWarning() << "Dead!";
        mLoop->exit(3);
        break;
    default:
        qWarning().nospace() << "What sort of readiness is "
            << newReadiness << "?!";
        mLoop->exit(4);
        break;
    }
}

void TestChanBasics::initTestCase()
{
    initTestCaseImpl();

    // Wait for the CM to start up
    QVERIFY(waitForPinocchio(5000));

    // Escape to the low-level API to make a Connection; this uses
    // pseudo-blocking calls for simplicity. Do not do this in production code

    mCM = new Telepathy::Client::ConnectionManagerInterface(
        pinocchioBusName(), pinocchioObjectPath());

    QDBusPendingReply<QString, QDBusObjectPath> reply;
    QVariantMap parameters;
    parameters.insert(QLatin1String("account"),
        QVariant::fromValue(QString::fromAscii("empty")));
    parameters.insert(QLatin1String("password"),
        QVariant::fromValue(QString::fromAscii("s3kr1t")));

    reply = mCM->RequestConnection("dummy", parameters);
    reply.waitForFinished();
    if (!reply.isValid()) {
        qWarning().nospace() << reply.error().name()
            << ": " << reply.error().message();
        QVERIFY(reply.isValid());
    }
    mConnBusName = reply.argumentAt<0>();
    mConnObjectPath = reply.argumentAt<1>().path();

    // Get a connected Connection
    mConn = new Connection(mConnBusName, mConnObjectPath);

    QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(
            mConn->Connect());
    QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher*))));
    QCOMPARE(mLoop->exec(), 0);
    delete watcher;

    QVERIFY(connect(mConn, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectConnReady(uint))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(mConn, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectConnReady(uint))));

    // Using direct access to low-level stuff here, so we can test the
    // Channel constructor directly
    QDBusPendingReply<Telepathy::UIntList> requestHandlesReply =
        mConn->RequestHandles(Telepathy::HandleTypeList,
            QStringList() << "subscribe");
    requestHandlesReply.waitForFinished();
    mSubscribeHandle = requestHandlesReply.value().at(0);

    QDBusPendingReply<QDBusObjectPath> requestChannelReply =
        mConn->RequestChannel(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST,
            Telepathy::HandleTypeList, mSubscribeHandle, true);
    requestChannelReply.waitForFinished();
    mSubscribeChanObjectPath = requestChannelReply.value().path();
}

void TestChanBasics::init()
{
    initImpl();
}

void TestChanBasics::expectChanReady(uint newReadiness)
{
    switch (newReadiness) {
    case Channel::ReadinessJustCreated:
        qWarning() << "Changing from JustCreated to JustCreated is silly";
        mLoop->exit(1);
        break;
    case Channel::ReadinessFull:
        qDebug() << "Ready";
        mLoop->exit(0);
        break;
    case Channel::ReadinessClosed:
        // fall through
    case Channel::ReadinessDead:
        qWarning() << "Dead or closed!";
        mLoop->exit(3);
        break;
    default:
        qWarning().nospace() << "What sort of readiness is "
            << newReadiness << "?!";
        mLoop->exit(4);
        break;
    }
}

void TestChanBasics::testBasics()
{
    mChan = new Channel(mConn, mSubscribeChanObjectPath);

    QCOMPARE(mChan->readiness(), Channel::ReadinessJustCreated);
    QEXPECT_FAIL("", "Doesn't seem to work", Continue);
    QCOMPARE(mChan->connection(), mConn);

    // Wait for readiness to reach Full

    qDebug() << "waiting for Full readiness";
    QVERIFY(connect(mChan, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectChanReady(uint))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(mChan, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectChanReady(uint))));

    QCOMPARE(mChan->readiness(), Channel::ReadinessFull);

    QCOMPARE(mChan->channelType(),
        QString::fromAscii(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
    QCOMPARE(mChan->targetHandleType(),
        static_cast<uint>(Telepathy::HandleTypeList));
    QCOMPARE(mChan->targetHandle(), mSubscribeHandle);

    delete mChan;
}

void TestChanBasics::expectPendingChannelFinished(PendingChannel* pc)
{
    if (!pc->isFinished()) {
        qWarning() << "unfinished";
        mLoop->exit(1);
        return;
    }

    if (pc->isError()) {
        qWarning().nospace() << pc->error().name()
            << ": " << pc->error().message();
        mLoop->exit(2);
        return;
    }

    mLoop->exit(0);
}

void TestChanBasics::testPendingChannel()
{
    PendingChannel *pc = mConn->requestChannel(
        QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST),
        Telepathy::HandleTypeList,
        mSubscribeHandle);

    QVERIFY(connect(pc, SIGNAL(finished(Telepathy::Client::PendingChannel*)),
          this, SLOT(expectPendingChannelFinished(Telepathy::Client::PendingChannel*))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(pc, SIGNAL(finished(Telepathy::Client::PendingChannel*)),
          this, SLOT(expectPendingChannelFinished(Telepathy::Client::PendingChannel*))));

    mChan = pc->channel();
    QVERIFY(mChan);

    QCOMPARE(mChan->readiness(), Channel::ReadinessJustCreated);
    QEXPECT_FAIL("", "Doesn't seem to work", Continue);
    QCOMPARE(mChan->connection(), mConn);

    // Wait for readiness to reach Full
    // FIXME: eventually, this should be encapsulated in the PendingChannel

    qDebug() << "waiting for Full readiness";
    QVERIFY(connect(mChan, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectChanReady(uint))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(mChan, SIGNAL(readinessChanged(uint)),
          this, SLOT(expectChanReady(uint))));

    QCOMPARE(mChan->readiness(), Channel::ReadinessFull);

    QCOMPARE(mChan->channelType(),
        QString::fromAscii(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST));
    QCOMPARE(mChan->targetHandleType(),
        static_cast<uint>(Telepathy::HandleTypeList));
    QCOMPARE(mChan->targetHandle(), mSubscribeHandle);

    delete mChan;
}

void TestChanBasics::expectPendingChannelError(PendingChannel* pc)
{
    if (!pc->isFinished()) {
        qWarning() << "unfinished";
        mLoop->exit(1);
        return;
    }

    if (!pc->isError()) {
        qWarning() << "no error";
        mLoop->exit(2);
        return;
    }

    qDebug().nospace() << pc->error().name()
        << ": " << pc->error().message();
    mLoop->exit(0);
}

void TestChanBasics::testPendingChannelError()
{
    PendingChannel *pc = mConn->requestChannel(
        QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST),
        Telepathy::HandleTypeList,
        31337);

    QVERIFY(connect(pc, SIGNAL(finished(Telepathy::Client::PendingChannel*)),
          this, SLOT(expectPendingChannelError(Telepathy::Client::PendingChannel*))));
    QCOMPARE(mLoop->exec(), 0);
    QVERIFY(disconnect(pc, SIGNAL(finished(Telepathy::Client::PendingChannel*)),
          this, SLOT(expectPendingChannelError(Telepathy::Client::PendingChannel*))));
}

void TestChanBasics::cleanup()
{
    cleanupImpl();
}

void TestChanBasics::cleanupTestCase()
{
    QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(
            mConn->Disconnect());
    QVERIFY(connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
            this, SLOT(expectSuccessfulCall(QDBusPendingCallWatcher*))));
    QCOMPARE(mLoop->exec(), 0);
    delete watcher;

    delete mConn;
    mConn = NULL;

    delete mCM;
    mCM = NULL;

    cleanupTestCaseImpl();
}

QTEST_MAIN(TestChanBasics)
#include "_gen/chan-basics.cpp.moc.hpp"
<|endoftext|>