<commit_before>#include "ExampleGame/Components/GameScripts/Units/Assassin.h"
#include "ExampleGame/Components/Grid/GameGrid.h"
#include "ExampleGame/Components/Grid/GridCell.h"
#include "ExampleGame/Components/Grid/GridManager.h"
#include "ExampleGame/Components/Grid/GridNavigator.h"
#include "ExampleGame/GameConstants/GameConstants.h"
#include "ExampleGame/GameSingletons/GameSingletons.h"
#include "ExampleGame/Messages/Declarations.h"

#include "Vajra/Common/Messages/CustomMessageDatas/MessageData1S1I1F.h"
#include "Vajra/Engine/Components/DerivedComponents/Renderer/SpriteRenderer.h"
#include "Vajra/Engine/Components/DerivedComponents/Transform/Transform.h"
#include "Vajra/Engine/Core/Engine.h"
#include "Vajra/Engine/Input/Input.h"
#include "Vajra/Engine/MessageHub/MessageHub.h"
#include "Vajra/Engine/SceneGraph/SceneGraph3D.h"
#include "Vajra/Engine/Tween/Tween.h"
#include "Vajra/Framework/DeviceUtils/FileSystemUtils/FileSystemUtils.h"

// Tween callbacks
void assassinTweenCallback(ObjectIdType gameObjectId , std::string /* tweenClipName */) {
	GameObject* go = ENGINE->GetSceneGraph3D()->GetGameObjectById(gameObjectId);
	ASSERT(go != nullptr, "Game object id passed into playerUnitNuumberTweenCallback is not valid");
	if(go != nullptr) {
		Assassin* pUnit = go->GetComponent<Assassin>();
		ASSERT(pUnit != nullptr, "Game object passed into playerUnitNuumberTweenCallback doesn't have a player unit");
		if(pUnit != nullptr) {
			pUnit->specialUpdate(); // make sure we hit everything we got in the last frame
			pUnit->onSpecialEnd();
		}
	}

}

void assassinNumberTweenCallback(float /* fromNumber */, float /* toNumber */, float /*currentNumber*/, std::string /*tweenClipName*/, MessageData1S1I1F* userParams) {
	GameObject* go = ENGINE->GetSceneGraph3D()->GetGameObjectById(userParams->i);
	ASSERT(go != nullptr, "Game object id passed into playerUnitNuumberTweenCallback is not valid");
	if(go != nullptr) {
		Assassin* pUnit = go->GetComponent<Assassin>();
		ASSERT(pUnit != nullptr, "Game object passed into playerUnitNuumberTweenCallback doesn't have a player unit");
		if(pUnit != nullptr) {
			pUnit->specialUpdate();
		}
	}
}

Assassin::Assassin() : PlayerUnit() {
	this->init();
}

Assassin::Assassin(Object* object_) : PlayerUnit(object_) {
	this->init();
}

Assassin::~Assassin() {
	this->destroy();
}

void Assassin::init() {
	this->unitType = UnitType::UNIT_TYPE_ASSASSIN;

	{
		// create the arrow tail
		this->arrowTail = new GameObject(ENGINE->GetSceneGraph3D());
		SpriteRenderer* spriteRenderer = this->arrowTail->AddComponent<SpriteRenderer>();
		std::vector<std::string> pathsToTextures;
		pathsToTextures.push_back(FRAMEWORK->GetFileSystemUtils()->GetDevicePictureResourcesFolderName() + "SD_UIEffect_Assassin_ArrowStem_magenta_02.png");
		spriteRenderer->initPlane(1.0f, 1.0f, "sptshdr", pathsToTextures, PlaneOrigin::Center);
		this->arrowTail->GetTransform()->Rotate(90.0f inRadians, XAXIS);
		this->arrowTail->SetVisible(false);
	}
	{
		// create the arrow head
		this->arrowHead = new GameObject(ENGINE->GetSceneGraph3D());
		SpriteRenderer* spriteRenderer = this->arrowHead->AddComponent<SpriteRenderer>();
		std::vector<std::string> pathsToTextures;
		pathsToTextures.push_back(FRAMEWORK->GetFileSystemUtils()->GetDevicePictureResourcesFolderName() + "SD_UIEffect_Assassin_Arrow_magenta_03.png");
		spriteRenderer->initPlane(1.0f, 1.0f, "sptshdr", pathsToTextures, PlaneOrigin::Center);
		this->arrowHead->GetTransform()->Rotate(90.0f inRadians, XAXIS);
		this->arrowHead->SetVisible(false);
	}

	this->addSubscriptionToMessageType(MESSAGE_TYPE_GRID_CELL_CHANGED, this->GetTypeId(), false);
}

void Assassin::destroy() {
}

void Assassin::HandleMessage(MessageChunk messageChunk) {
	PlayerUnit::HandleMessage(messageChunk);
	switch (messageChunk->GetMessageType()) {
		case MESSAGE_TYPE_GRID_CELL_CHANGED:
			this->onGridCellChanged(messageChunk->GetSenderId(), messageChunk->messageData.iv1.x, messageChunk->messageData.iv1.z);
			break;

		default:
			break;
	}
}

bool Assassin::isSpecialTouch(int touchId) {
	if(this->getTouchNearUnit()) {
		Touch touch = ENGINE->GetInput()->GetTouch(touchId);
		if(touch.timeDown <= GetFloatGameConstant(GAME_CONSTANT_swipe_duration_in_seconds) && glm::distance(touch.pos, this->touchStartPos) > GetFloatGameConstant(GAME_CONSTANT_assassin_swipe_dist_in_pixels)) {
			this->swipeDirectionScreen = this->touchStartPos - touch.pos;
			this->targetedCell = nullptr;
			this->gridNavRef->HaltMovement();
			this->aimSpecial(touchId);
			return true;
		}
	}
	return false;
}

void Assassin::onSpecialTouch(int touchId) {
	Touch touch = ENGINE->GetInput()->GetTouch(touchId);
	if(touch.phase == TouchPhase::Ended) {
		this->trySpecial(touchId);
	} else if(touch.phase == TouchPhase::Cancelled) {
		this->cancelSpecial();
	} else if(touch.phase == TouchPhase::Moved) {
		this->aimSpecial(touchId);
	}
}

void Assassin::trySpecial(int /*touchId*/) {
	if(this->targetedCell != nullptr && this->targetedCell != this->gridNavRef->GetCurrentCell()) {
		this->startSpecial();
	} else {
		this->cancelSpecial();
	}
}

void Assassin::startSpecial() {
	PlayerUnit::startSpecial();

	//this->gridNavRef->SetMovementSpeed(GetFloatGameConstant(GAME_CONSTANT_assassin_attack_speed));
	//this->gridNavRef->SetDestination(this->targetedCell, true);
	float tweenTime = glm::distance(this->gameObjectRef->GetTransform()->GetPositionWorld(), this->targetLoc) / GetFloatGameConstant(GAME_CONSTANT_assassin_attack_speed);
	ASSERT(tweenTime > 0, "tweenTime is greater than zero");
	this->lastHitCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld());
	this->specialStartPos = this->gameObjectRef->GetTransform()->GetPositionWorld();
	ENGINE->GetTween()->TweenPosition(this->gameObjectRef->GetId(),
									  this->gameObjectRef->GetTransform()->GetPositionWorld(),
									  this->targetLoc,
									  tweenTime,
									  false,
									  TWEEN_TRANSLATION_CURVE_TYPE_LINEAR,
									  false,
									  assassinTweenCallback);
	MessageData1S1I1F* userParams = new MessageData1S1I1F();
 	userParams->i = this->GetObject()->GetId();
	ENGINE->GetTween()->TweenToNumber(0.0f, 1.0f, tweenTime, true, false, true, "dash", NUMBER_TWEEN_AFFILIATION_SCENEGRAPH_3D, userParams, assassinNumberTweenCallback);

	this->startTouchIndicatorPulse();
	this->arrowHead->SetVisible(false);
	this->arrowTail->SetVisible(false);
}

void Assassin::onSpecialEnd() {
	PlayerUnit::onSpecialEnd();
	this->arrowHead->SetVisible(false);
	this->arrowTail->SetVisible(false);
	this->gridNavRef->SetCurrentCell(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
	//this->gridNavRef->SetMovementSpeed(MOVE_SPEED);
	//this->gridNavRef->SetGridPosition(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
	//(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
	//this->gameObjectRef->GetTransform()->SetPosition(this->targetLoc);
}

void Assassin::cancelSpecial() {
	PlayerUnit::cancelSpecial();
	this->arrowHead->SetVisible(false);
	this->arrowTail->SetVisible(false);
	ENGINE->GetTween()->CancelNumberTween("dash");
	ENGINE->GetTween()->CancelPostitionTween(this->gameObjectRef->GetId());
	this->specialUpdate();
	this->gridNavRef->SetCurrentCell(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
}

void Assassin::onGridCellChanged(ObjectIdType id, int gridX, int gridZ) {
	// If this object is the one that sent the message, broadcast an attack message as well
	if (id == this->GetObject()->GetId()) {
		GridCell* cell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(gridX, gridZ);
		if (cell != nullptr) {
			if (this->GetUnitActionState() == UnitActionState::UNIT_ACTION_STATE_DOING_SPECIAL) {
				// Send an attack message
				MessageChunk attackMessage = ENGINE->GetMessageHub()->GetOneFreeMessage();
				attackMessage->SetMessageType(MESSAGE_TYPE_UNIT_SPECIAL_HIT);
				attackMessage->messageData.iv1.x = cell->x;
				attackMessage->messageData.iv1.y = cell->y;
				attackMessage->messageData.iv1.z = cell->z;
				attackMessage->messageData.fv1 = this->specialStartPos;
				ENGINE->GetMessageHub()->SendMulticastMessage(attackMessage, this->GetObject()->GetId());
			}
		}
	}
}

void Assassin::aimSpecial(int touchId){
	this->targetLoc = SINGLETONS->GetGridManager()->TouchPositionToGridPositionAtElevation(ENGINE->GetInput()->GetTouch(touchId).pos, this->gridNavRef->GetCurrentCell()->y);
	glm::vec3 sinPos = this->gameObjectRef->GetTransform()->GetPositionWorld();

	std::list<GridCell*> touchedCells;
	GridCell* touchedCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->targetLoc);
	ASSERT(touchedCell != nullptr, "the target cell is not null");

	SINGLETONS->GetGridManager()->GetGrid()->TouchedCells(sinPos, this->targetLoc, touchedCells);
	int elevation = SINGLETONS->GetGridManager()->GetGrid()->GetElevationFromWorldY(this->gridNavRef->GetCurrentCell()->center.y);
	int cellIndex = 0;
	for( GridCell* c : touchedCells) {
		if(SINGLETONS->GetGridManager()->GetGrid()->IsCellPassableAtElevation(c->x, c->z, elevation)) {
			this->targetedCell = c;
		} else {
			break;
		}
		cellIndex++;
	}

	// if there is a legal target
	if(this->targetedCell != nullptr && touchedCell != nullptr && this->targetedCell != this->gridNavRef->GetCurrentCell()) {
		glm::vec3 attackDir = glm::normalize(this->targetLoc - sinPos);
		if(touchedCell != this->targetedCell) {
			// if the touched cell is obstructed move back along the path until the target is reachable
			float dis = glm::distance(sinPos, this->targetedCell->center);
			while(touchedCell != this->targetedCell && glm::distance(sinPos, this->targetLoc) > dis) {
				this->targetLoc -= attackDir * .1f;
				touchedCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->targetLoc);
			}
		}

		float dist = glm::distance(sinPos, this->targetLoc);
		if(dist > GetFloatGameConstant(GAME_CONSTANT_dash_distance_in_units)) {
			dist = GetFloatGameConstant(GAME_CONSTANT_dash_distance_in_units);
			this->targetLoc = sinPos + (attackDir *  dist);
			touchedCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->targetLoc);
		}

		dist -= 1.5f; // shorten dist so arrow heads are in the right spot
		if(dist < 0.5f) {
			this->arrowTail->SetVisible(false);
		}
		// make the sin look at the target
		this->gridNavRef->SetLookTarget(this->targetLoc);

		// the yOffset the indicators should have
		float yOffset = touchedCell->y * 0.05 + .01f;

		// touch indicator
		this->SetTouchIndicatorVisible(true);
		this->SetTouchIndicatorLocation(this->targetLoc + yOffset);

		// Arrow Tail
		this->arrowTail->SetVisible(true);
		this->GridPlaneLookAt(this->arrowTail, this->targetLoc);
		this->arrowTail->GetTransform()->SetScale(1.0f, dist, 1.0f);
		this->arrowTail->GetComponent<Transform>()->SetPosition(sinPos + glm::vec3(0.0f, yOffset, 0.0f));
		this->arrowTail->GetComponent<Transform>()->Translate(dist * .5f, this->arrowTail->GetComponent<Transform>()->GetUp());

		// Arrow Head
		this->arrowHead->SetVisible(true);
		this->GridPlaneLookAt(this->arrowHead, this->targetLoc);
		this->arrowHead->GetTransform()->SetPosition(sinPos + glm::vec3(0.0f, yOffset, 0.0f));
		this->arrowHead->GetTransform()->Translate(dist + .5f, this->arrowHead->GetComponent<Transform>()->GetUp());
	} else {
		this->SetTouchIndicatorVisible(false);
		this->arrowHead->SetVisible(false);
		this->arrowTail->SetVisible(false);
	}
}

void Assassin::specialUpdate() {
	GridCell* currentCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld());
	if(currentCell != this->lastHitCell) {
		std::list<GridCell*> touchedCells;
		SINGLETONS->GetGridManager()->CheckZoneCollisions(this->GetObject()->GetId(), this->lastHitCell, currentCell);
		for( GridCell* c : touchedCells) {
			if(this->lastHitCell != c) {
				// Attack the cell
				MessageChunk attackMessage = ENGINE->GetMessageHub()->GetOneFreeMessage();
				attackMessage->SetMessageType(MESSAGE_TYPE_UNIT_SPECIAL_HIT);
				attackMessage->messageData.iv1.x = c->x;
				attackMessage->messageData.iv1.y = c->y;
				attackMessage->messageData.iv1.z = c->z;
				attackMessage->messageData.fv1 = this->specialStartPos;
				ENGINE->GetMessageHub()->SendMulticastMessage(attackMessage, this->GetObject()->GetId());
			}
		}
		this->lastHitCell = currentCell;
	}
}
<commit_msg>Fixed sin bug<commit_after>#include "ExampleGame/Components/GameScripts/Units/Assassin.h"
#include "ExampleGame/Components/Grid/GameGrid.h"
#include "ExampleGame/Components/Grid/GridCell.h"
#include "ExampleGame/Components/Grid/GridManager.h"
#include "ExampleGame/Components/Grid/GridNavigator.h"
#include "ExampleGame/GameConstants/GameConstants.h"
#include "ExampleGame/GameSingletons/GameSingletons.h"
#include "ExampleGame/Messages/Declarations.h"

#include "Vajra/Common/Messages/CustomMessageDatas/MessageData1S1I1F.h"
#include "Vajra/Engine/Components/DerivedComponents/Renderer/SpriteRenderer.h"
#include "Vajra/Engine/Components/DerivedComponents/Transform/Transform.h"
#include "Vajra/Engine/Core/Engine.h"
#include "Vajra/Engine/Input/Input.h"
#include "Vajra/Engine/MessageHub/MessageHub.h"
#include "Vajra/Engine/SceneGraph/SceneGraph3D.h"
#include "Vajra/Engine/Tween/Tween.h"
#include "Vajra/Framework/DeviceUtils/FileSystemUtils/FileSystemUtils.h"

// Tween callbacks
void assassinTweenCallback(ObjectIdType gameObjectId , std::string /* tweenClipName */) {
	GameObject* go = ENGINE->GetSceneGraph3D()->GetGameObjectById(gameObjectId);
	ASSERT(go != nullptr, "Game object id passed into playerUnitNuumberTweenCallback is not valid");
	if(go != nullptr) {
		Assassin* pUnit = go->GetComponent<Assassin>();
		ASSERT(pUnit != nullptr, "Game object passed into playerUnitNuumberTweenCallback doesn't have a player unit");
		if(pUnit != nullptr) {
			pUnit->specialUpdate(); // make sure we hit everything we got in the last frame
			pUnit->onSpecialEnd();
		}
	}

}

void assassinNumberTweenCallback(float /* fromNumber */, float /* toNumber */, float /*currentNumber*/, std::string /*tweenClipName*/, MessageData1S1I1F* userParams) {
	GameObject* go = ENGINE->GetSceneGraph3D()->GetGameObjectById(userParams->i);
	ASSERT(go != nullptr, "Game object id passed into playerUnitNuumberTweenCallback is not valid");
	if(go != nullptr) {
		Assassin* pUnit = go->GetComponent<Assassin>();
		ASSERT(pUnit != nullptr, "Game object passed into playerUnitNuumberTweenCallback doesn't have a player unit");
		if(pUnit != nullptr) {
			pUnit->specialUpdate();
		}
	}
}

Assassin::Assassin() : PlayerUnit() {
	this->init();
}

Assassin::Assassin(Object* object_) : PlayerUnit(object_) {
	this->init();
}

Assassin::~Assassin() {
	this->destroy();
}

void Assassin::init() {
	this->unitType = UnitType::UNIT_TYPE_ASSASSIN;

	{
		// create the arrow tail
		this->arrowTail = new GameObject(ENGINE->GetSceneGraph3D());
		SpriteRenderer* spriteRenderer = this->arrowTail->AddComponent<SpriteRenderer>();
		std::vector<std::string> pathsToTextures;
		pathsToTextures.push_back(FRAMEWORK->GetFileSystemUtils()->GetDevicePictureResourcesFolderName() + "SD_UIEffect_Assassin_ArrowStem_magenta_02.png");
		spriteRenderer->initPlane(1.0f, 1.0f, "sptshdr", pathsToTextures, PlaneOrigin::Center);
		this->arrowTail->GetTransform()->Rotate(90.0f inRadians, XAXIS);
		this->arrowTail->SetVisible(false);
	}
	{
		// create the arrow head
		this->arrowHead = new GameObject(ENGINE->GetSceneGraph3D());
		SpriteRenderer* spriteRenderer = this->arrowHead->AddComponent<SpriteRenderer>();
		std::vector<std::string> pathsToTextures;
		pathsToTextures.push_back(FRAMEWORK->GetFileSystemUtils()->GetDevicePictureResourcesFolderName() + "SD_UIEffect_Assassin_Arrow_magenta_03.png");
		spriteRenderer->initPlane(1.0f, 1.0f, "sptshdr", pathsToTextures, PlaneOrigin::Center);
		this->arrowHead->GetTransform()->Rotate(90.0f inRadians, XAXIS);
		this->arrowHead->SetVisible(false);
	}

	this->addSubscriptionToMessageType(MESSAGE_TYPE_GRID_CELL_CHANGED, this->GetTypeId(), false);
}

void Assassin::destroy() {
}

void Assassin::HandleMessage(MessageChunk messageChunk) {
	PlayerUnit::HandleMessage(messageChunk);
	switch (messageChunk->GetMessageType()) {
		case MESSAGE_TYPE_GRID_CELL_CHANGED:
			this->onGridCellChanged(messageChunk->GetSenderId(), messageChunk->messageData.iv1.x, messageChunk->messageData.iv1.z);
			break;

		default:
			break;
	}
}

bool Assassin::isSpecialTouch(int touchId) {
	if(this->getTouchNearUnit()) {
		Touch touch = ENGINE->GetInput()->GetTouch(touchId);
		if(touch.timeDown <= GetFloatGameConstant(GAME_CONSTANT_swipe_duration_in_seconds) && glm::distance(touch.pos, this->touchStartPos) > GetFloatGameConstant(GAME_CONSTANT_assassin_swipe_dist_in_pixels)) {
			this->swipeDirectionScreen = this->touchStartPos - touch.pos;
			this->targetedCell = nullptr;
			this->gridNavRef->HaltMovement();
			this->aimSpecial(touchId);
			return true;
		}
	}
	return false;
}

void Assassin::onSpecialTouch(int touchId) {
	Touch touch = ENGINE->GetInput()->GetTouch(touchId);
	if(touch.phase == TouchPhase::Ended) {
		this->trySpecial(touchId);
	} else if(touch.phase == TouchPhase::Cancelled) {
		this->cancelSpecial();
	} else if(touch.phase == TouchPhase::Moved) {
		this->aimSpecial(touchId);
	}
}

void Assassin::trySpecial(int /*touchId*/) {
	if(this->targetedCell != nullptr && this->targetedCell != this->gridNavRef->GetCurrentCell()) {
		this->startSpecial();
	} else {
		this->cancelSpecial();
	}
}

void Assassin::startSpecial() {
	PlayerUnit::startSpecial();

	//this->gridNavRef->SetMovementSpeed(GetFloatGameConstant(GAME_CONSTANT_assassin_attack_speed));
	//this->gridNavRef->SetDestination(this->targetedCell, true);
	float tweenTime = glm::distance(this->gameObjectRef->GetTransform()->GetPositionWorld(), this->targetLoc) / GetFloatGameConstant(GAME_CONSTANT_assassin_attack_speed);
	ASSERT(tweenTime > 0, "tweenTime is greater than zero");
	this->lastHitCell = this->gridNavRef->GetCurrentCell();
	this->specialStartPos = this->gameObjectRef->GetTransform()->GetPositionWorld();
	ENGINE->GetTween()->TweenPosition(this->gameObjectRef->GetId(),
									  this->gameObjectRef->GetTransform()->GetPositionWorld(),
									  this->targetLoc,
									  tweenTime,
									  false,
									  TWEEN_TRANSLATION_CURVE_TYPE_LINEAR,
									  false,
									  assassinTweenCallback);
	MessageData1S1I1F* userParams = new MessageData1S1I1F();
 	userParams->i = this->GetObject()->GetId();
	ENGINE->GetTween()->TweenToNumber(0.0f, 1.0f, tweenTime, true, false, true, "dash", NUMBER_TWEEN_AFFILIATION_SCENEGRAPH_3D, userParams, assassinNumberTweenCallback);

	this->startTouchIndicatorPulse();
	this->arrowHead->SetVisible(false);
	this->arrowTail->SetVisible(false);
}

void Assassin::onSpecialEnd() {
	PlayerUnit::onSpecialEnd();
	this->arrowHead->SetVisible(false);
	this->arrowTail->SetVisible(false);
	this->gridNavRef->SetCurrentCell(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
	//this->gridNavRef->SetMovementSpeed(MOVE_SPEED);
	//this->gridNavRef->SetGridPosition(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
	//(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
	//this->gameObjectRef->GetTransform()->SetPosition(this->targetLoc);
}

void Assassin::cancelSpecial() {
	PlayerUnit::cancelSpecial();
	this->arrowHead->SetVisible(false);
	this->arrowTail->SetVisible(false);
	ENGINE->GetTween()->CancelNumberTween("dash");
	ENGINE->GetTween()->CancelPostitionTween(this->gameObjectRef->GetId());
	this->specialUpdate();
	this->gridNavRef->SetCurrentCell(SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld()));
}

void Assassin::onGridCellChanged(ObjectIdType id, int gridX, int gridZ) {
	// If this object is the one that sent the message, broadcast an attack message as well
	if (id == this->GetObject()->GetId()) {
		GridCell* cell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(gridX, gridZ);
		if (cell != nullptr) {
			if (this->GetUnitActionState() == UnitActionState::UNIT_ACTION_STATE_DOING_SPECIAL) {
				// Send an attack message
				MessageChunk attackMessage = ENGINE->GetMessageHub()->GetOneFreeMessage();
				attackMessage->SetMessageType(MESSAGE_TYPE_UNIT_SPECIAL_HIT);
				attackMessage->messageData.iv1.x = cell->x;
				attackMessage->messageData.iv1.y = cell->y;
				attackMessage->messageData.iv1.z = cell->z;
				attackMessage->messageData.fv1 = this->specialStartPos;
				ENGINE->GetMessageHub()->SendMulticastMessage(attackMessage, this->GetObject()->GetId());
			}
		}
	}
}

void Assassin::aimSpecial(int touchId){
	this->targetLoc = SINGLETONS->GetGridManager()->TouchPositionToGridPositionAtElevation(ENGINE->GetInput()->GetTouch(touchId).pos, this->gridNavRef->GetCurrentCell()->y);
	glm::vec3 sinPos = this->gameObjectRef->GetTransform()->GetPositionWorld();

	std::list<GridCell*> touchedCells;
	GridCell* touchedCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->targetLoc);
	ASSERT(touchedCell != nullptr, "the target cell is not null");

	SINGLETONS->GetGridManager()->GetGrid()->TouchedCells(sinPos, this->targetLoc, touchedCells);
	int elevation = SINGLETONS->GetGridManager()->GetGrid()->GetElevationFromWorldY(this->gridNavRef->GetCurrentCell()->center.y);
	int cellIndex = 0;
	for( GridCell* c : touchedCells) {
		if(SINGLETONS->GetGridManager()->GetGrid()->IsCellPassableAtElevation(c->x, c->z, elevation)) {
			this->targetedCell = c;
		} else {
			break;
		}
		cellIndex++;
	}

	// if there is a legal target
	if(this->targetedCell != nullptr && touchedCell != nullptr && this->targetedCell != this->gridNavRef->GetCurrentCell()) {
		glm::vec3 attackDir = glm::normalize(this->targetLoc - sinPos);
		if(touchedCell != this->targetedCell) {
			// if the touched cell is obstructed move back along the path until the target is reachable
			float dis = glm::distance(sinPos, this->targetedCell->center);
			while(touchedCell != this->targetedCell && glm::distance(sinPos, this->targetLoc) > dis) {
				this->targetLoc -= attackDir * .1f;
				touchedCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->targetLoc);
			}
		}

		float dist = glm::distance(sinPos, this->targetLoc);
		if(dist > GetFloatGameConstant(GAME_CONSTANT_dash_distance_in_units)) {
			dist = GetFloatGameConstant(GAME_CONSTANT_dash_distance_in_units);
			this->targetLoc = sinPos + (attackDir *  dist);
			touchedCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->targetLoc);
		}

		dist -= 1.5f; // shorten dist so arrow heads are in the right spot
		if(dist < 0.5f) {
			this->arrowTail->SetVisible(false);
		}
		// make the sin look at the target
		this->gridNavRef->SetLookTarget(this->targetLoc);

		// the yOffset the indicators should have
		float yOffset = touchedCell->y * 0.05 + .01f;

		// touch indicator
		this->SetTouchIndicatorVisible(true);
		this->SetTouchIndicatorLocation(this->targetLoc + yOffset);

		// Arrow Tail
		this->arrowTail->SetVisible(true);
		this->GridPlaneLookAt(this->arrowTail, this->targetLoc);
		this->arrowTail->GetTransform()->SetScale(1.0f, dist, 1.0f);
		this->arrowTail->GetComponent<Transform>()->SetPosition(sinPos + glm::vec3(0.0f, yOffset, 0.0f));
		this->arrowTail->GetComponent<Transform>()->Translate(dist * .5f, this->arrowTail->GetComponent<Transform>()->GetUp());

		// Arrow Head
		this->arrowHead->SetVisible(true);
		this->GridPlaneLookAt(this->arrowHead, this->targetLoc);
		this->arrowHead->GetTransform()->SetPosition(sinPos + glm::vec3(0.0f, yOffset, 0.0f));
		this->arrowHead->GetTransform()->Translate(dist + .5f, this->arrowHead->GetComponent<Transform>()->GetUp());
	} else {
		this->SetTouchIndicatorVisible(false);
		this->arrowHead->SetVisible(false);
		this->arrowTail->SetVisible(false);
	}
}

void Assassin::specialUpdate() {
	GridCell* currentCell = SINGLETONS->GetGridManager()->GetGrid()->GetCell(this->gameObjectRef->GetTransform()->GetPositionWorld());
	if(currentCell != this->lastHitCell) {
		std::list<GridCell*> touchedCells;
		SINGLETONS->GetGridManager()->GetGrid()->TouchedCells(this->lastHitCell, currentCell, touchedCells);
		SINGLETONS->GetGridManager()->CheckZoneCollisions(this->GetObject()->GetId(), this->lastHitCell, currentCell);
		for( GridCell* c : touchedCells) {
			if(this->lastHitCell != c) {
				// Attack the cell
				MessageChunk attackMessage = ENGINE->GetMessageHub()->GetOneFreeMessage();
				attackMessage->SetMessageType(MESSAGE_TYPE_UNIT_SPECIAL_HIT);
				attackMessage->messageData.iv1.x = c->x;
				attackMessage->messageData.iv1.y = c->y;
				attackMessage->messageData.iv1.z = c->z;
				attackMessage->messageData.fv1 = this->specialStartPos;
				ENGINE->GetMessageHub()->SendMulticastMessage(attackMessage, this->GetObject()->GetId());
			}
		}
		this->lastHitCell = currentCell;
	}
}
<|endoftext|>


<commit_before>/**
 *   Copyright 2016, Quickstep Research Group, Computer Sciences Department,
 *     University of Wisconsin—Madison.
 *
 *   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.
 **/

#ifndef QUICKSTEP_TRANSACTION_DIRECTED_GRAPH_HPP_
#define QUICKSTEP_TRANSACTION_DIRECTED_GRAPH_HPP_

#include <algorithm>
#include <memory>
#include <stack>
#include <unordered_set>
#include <vector>

#include "transaction/Transaction.hpp"
#include "utility/Macros.hpp"

#include "glog/logging.h"

namespace quickstep {

namespace transaction {

/** \addtogroup Transaction
 *  @{
 */

/**
 * @brief Class for representing a directed graph. Vertices are transaction
 *        ids, edges are wait-for relations.
 **/
class DirectedGraph {
 public:
  typedef std::uint64_t node_id;

  /**
   * @brief Default constructor
   **/
  DirectedGraph() {}

  /**
   * @brief Adds a new node to the graph with the given transaction id.
   *        It does not check whether the transaction id is valid or not.
   * @warning Pointer ownership will pass to the graph, therefore it
   *          should not be deleted.
   *
   * @param data Pointer to the transaction id that will be contained
   *        in the node.
   * @return Id of the newly created node.
   **/
  inline node_id addNodeUnchecked(transaction_id *data) {
    nodes_.emplace_back(data);
    return nodes_.size() - 1;
  }

  /**
   * @brief Adds a new node to the graph with the given transaction id.
   *        It checks whether the transaction id is valid or not.
   * @warning Pointer ownership will pass to the graph, therefore it
   *          should not be deleted.
   *
   * @param data Pointer to the transaction id that will be contained
   *        in the node.
   * @return Id of the newly created node.
   **/
  inline node_id addNodeCheckExists(transaction_id *data) {
    for (std::vector<DirectedGraphNode>::const_iterator
           it = nodes_.cbegin(); it != nodes_.cend(); ++it) {
      CHECK(*data != it->getData());
    }
    nodes_.emplace_back(data);
    return nodes_.size() - 1;
  }

  /**
   * @brief Adds an edge between nodes. It does not check whether the
   *        parameters are valid node ids.
   * @warning Does not check arguments are legit. It may cause
   *          out of range errors.
   *
   * @param fromNode The node that edge is orginated.
   * @param toNode The node that edge is ended.
   **/
  inline void addEdgeUnchecked(node_id from_node, node_id to_node) {
    nodes_[from_node].addOutgoingEdge(to_node);
  }

  /**
   * @brief Adds an edge between nodes. It checks whether the
   *        parameters are valid node ids.
   *
   * @param fromNode The node that edge is orginated.
   * @param toNode The node that edge is ended.
   **/
  inline void addEdgeCheckExists(node_id from_node, node_id to_node) {
    CHECK(from_node < getNumNodes() && to_node < getNumNodes());
    nodes_[from_node].addOutgoingEdge(to_node);
  }

  /**
   * @brief Check whether there is a directed edge.
   * @warning When parameters are not valid ids, it may cause
   *          out of range errors.
   *
   * @param fromNode Id of the node that edge is originated from.
   * @param toNode Id of the node that edge is ended.
   * @return True if there is an edge, false otherwise.
   **/
  inline bool hasEdge(node_id from_node, node_id to_node) const {
    DCHECK(from_node < getNumNodes() && to_node < getNumNodes());
    return nodes_[from_node].hasOutgoingEdge(to_node);
  }

  /**
   * @brief Get data (transaction id) contained in the node.
   * @warning Does not check index validity.
   *
   * @param node Id of the node that the data is got from.
   * @return Id of the transaction that this node contains.
   **/
  inline transaction_id getDataFromNode(node_id node) const {
    return nodes_[node].getData();
  }

  /**
   * @brief Calculate how many nodes the graph has.
   *
   * @return The number of nodes the graph has.
   **/
  inline std::size_t getNumNodes() const {
    return nodes_.size();
  }

  /**
   * @brief Gives the node ids that this node has edges to.
   *
   * @param id Id of the corresponding node.
   * @return Vector of node ids that id has edges to.
   **/
  inline std::vector<node_id> getAdjacentNodes(node_id id) const {
    return nodes_[id].getOutgoingEdges();
  }

 private:
  // Class for representing a graph node.
  class DirectedGraphNode {
   public:
    explicit DirectedGraphNode(transaction_id *data)
      : data_(data) {}

    inline void addOutgoingEdge(node_id to_node) {
      outgoing_edges_.insert(to_node);
    }

    inline bool hasOutgoingEdge(node_id to_node) const {
      return outgoing_edges_.count(to_node) == 1;
    }

    inline std::vector<node_id> getOutgoingEdges() const {
      std::vector<node_id> result;
      std::copy(outgoing_edges_.begin(), outgoing_edges_.end(),
                std::back_inserter(result));
      return result;
    }

    inline transaction_id getData() const {
      return *(data_.get());
    }

   private:
    // Owner pointer to transaction id.
    std::unique_ptr<transaction_id> data_;

    // Endpoint nodes of outgoing edges originated from this node.
    std::unordered_set<node_id> outgoing_edges_;
  };

  // The list of nodes that are created. NodeId is the index of that
  // node in this buffer.
  std::vector<DirectedGraphNode> nodes_;

  DISALLOW_COPY_AND_ASSIGN(DirectedGraph);
};

/** @} */

}  // namespace transaction

}  // namespace quickstep

#endif  // QUICKSTEP_TRANSACTION_DIRECTED_GRAPH_HPP_
<commit_msg>Added a todo for considering alternatives.<commit_after>/**
 *   Copyright 2016, Quickstep Research Group, Computer Sciences Department,
 *     University of Wisconsin—Madison.
 *
 *   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.
 **/

#ifndef QUICKSTEP_TRANSACTION_DIRECTED_GRAPH_HPP_
#define QUICKSTEP_TRANSACTION_DIRECTED_GRAPH_HPP_

#include <algorithm>
#include <memory>
#include <stack>
#include <unordered_set>
#include <vector>

#include "transaction/Transaction.hpp"
#include "utility/Macros.hpp"

#include "glog/logging.h"

namespace quickstep {

namespace transaction {

/** \addtogroup Transaction
 *  @{
 */

/**
 * @brief Class for representing a directed graph. Vertices are transaction
 *        ids, edges are wait-for relations.
 **/
class DirectedGraph {
 public:
  typedef std::uint64_t node_id;

  /**
   * @brief Default constructor
   **/
  DirectedGraph() {}

  /**
   * @brief Adds a new node to the graph with the given transaction id.
   *        It does not check whether the transaction id is valid or not.
   * @warning Pointer ownership will pass to the graph, therefore it
   *          should not be deleted.
   *
   * @param data Pointer to the transaction id that will be contained
   *        in the node.
   * @return Id of the newly created node.
   **/
  inline node_id addNodeUnchecked(transaction_id *data) {
    nodes_.emplace_back(data);
    return nodes_.size() - 1;
  }

  /**
   * @brief Adds a new node to the graph with the given transaction id.
   *        It checks whether the transaction id is valid or not.
   * @warning Pointer ownership will pass to the graph, therefore it
   *          should not be deleted.
   *
   * @param data Pointer to the transaction id that will be contained
   *        in the node.
   * @return Id of the newly created node.
   **/
  inline node_id addNodeCheckExists(transaction_id *data) {
    for (std::vector<DirectedGraphNode>::const_iterator
           it = nodes_.cbegin(); it != nodes_.cend(); ++it) {
      CHECK(*data != it->getData());
    }
    nodes_.emplace_back(data);
    return nodes_.size() - 1;
  }

  /**
   * @brief Adds an edge between nodes. It does not check whether the
   *        parameters are valid node ids.
   * @warning Does not check arguments are legit. It may cause
   *          out of range errors.
   *
   * @param fromNode The node that edge is orginated.
   * @param toNode The node that edge is ended.
   **/
  inline void addEdgeUnchecked(node_id from_node, node_id to_node) {
    nodes_[from_node].addOutgoingEdge(to_node);
  }

  /**
   * @brief Adds an edge between nodes. It checks whether the
   *        parameters are valid node ids.
   *
   * @param fromNode The node that edge is orginated.
   * @param toNode The node that edge is ended.
   **/
  inline void addEdgeCheckExists(node_id from_node, node_id to_node) {
    CHECK(from_node < getNumNodes() && to_node < getNumNodes());
    nodes_[from_node].addOutgoingEdge(to_node);
  }

  /**
   * @brief Check whether there is a directed edge.
   * @warning When parameters are not valid ids, it may cause
   *          out of range errors.
   *
   * @param fromNode Id of the node that edge is originated from.
   * @param toNode Id of the node that edge is ended.
   * @return True if there is an edge, false otherwise.
   **/
  inline bool hasEdge(node_id from_node, node_id to_node) const {
    DCHECK(from_node < getNumNodes() && to_node < getNumNodes());
    return nodes_[from_node].hasOutgoingEdge(to_node);
  }

  /**
   * @brief Get data (transaction id) contained in the node.
   * @warning Does not check index validity.
   *
   * @param node Id of the node that the data is got from.
   * @return Id of the transaction that this node contains.
   **/
  inline transaction_id getDataFromNode(node_id node) const {
    return nodes_[node].getData();
  }

  /**
   * @brief Calculate how many nodes the graph has.
   *
   * @return The number of nodes the graph has.
   **/
  inline std::size_t getNumNodes() const {
    return nodes_.size();
  }

  /**
   * @brief Gives the node ids that this node has edges to.
   *
   * @param id Id of the corresponding node.
   * @return Vector of node ids that id has edges to.
   **/
  inline std::vector<node_id> getAdjacentNodes(node_id id) const {
    return nodes_[id].getOutgoingEdges();
  }

 private:
  // Class for representing a graph node.
  class DirectedGraphNode {
   public:
    explicit DirectedGraphNode(transaction_id *data)
      : data_(data) {}

    inline void addOutgoingEdge(node_id to_node) {
      outgoing_edges_.insert(to_node);
    }

    inline bool hasOutgoingEdge(node_id to_node) const {
      return outgoing_edges_.count(to_node) == 1;
    }

    inline std::vector<node_id> getOutgoingEdges() const {
      // TODO(hakan): Benchmark this version and the alternative which the
      //              function returns const reference and the uniqueness
      //              is imposed in the outgoing_edges_ as a vector.
      std::vector<node_id> result;
      std::copy(outgoing_edges_.begin(), outgoing_edges_.end(),
                std::back_inserter(result));
      return result;
    }

    inline transaction_id getData() const {
      return *(data_.get());
    }

   private:
    // Owner pointer to transaction id.
    std::unique_ptr<transaction_id> data_;

    // Endpoint nodes of outgoing edges originated from this node.
    std::unordered_set<node_id> outgoing_edges_;
  };

  // The list of nodes that are created. NodeId is the index of that
  // node in this buffer.
  std::vector<DirectedGraphNode> nodes_;

  DISALLOW_COPY_AND_ASSIGN(DirectedGraph);
};

/** @} */

}  // namespace transaction

}  // namespace quickstep

#endif  // QUICKSTEP_TRANSACTION_DIRECTED_GRAPH_HPP_
<|endoftext|>


<commit_before>/// @file
/// @author uentity
/// @date 22.06.2018
/// @brief Implementation of BS tree-related serialization
/// @copyright
/// This Source Code Form is subject to the terms of the Mozilla Public License,
/// v. 2.0. If a copy of the MPL was not distributed with this file,
/// You can obtain one at https://mozilla.org/MPL/2.0/

#include <bs/actor_common.h>
#include <bs/atoms.h>
#include <bs/kernel/radio.h>
#include <bs/log.h>
#include <bs/tree/tree.h>
#include <bs/serialize/serialize.h>
#include <bs/serialize/tree.h>
#include <bs/serialize/cafbind.h>

CAF_ALLOW_UNSAFE_MESSAGE_TYPE(blue_sky::tree::on_serialized_f)

NAMESPACE_BEGIN(blue_sky::tree)
using namespace kernel::radio;
/*-----------------------------------------------------------------------------
 *  tree serialization actor impl
 *-----------------------------------------------------------------------------*/
NAMESPACE_BEGIN()
///////////////////////////////////////////////////////////////////////////////
//  Tree FS archive
//
auto unite_errors(const std::vector<error>& errs) -> error {
	std::string reduced_er;
	for(const auto& er : errs) {
		if(er) {
			if(!reduced_er.empty()) reduced_er += '\n';
			reduced_er += er.what();
		}
	}
	return reduced_er.empty() ? success() : error::quiet(reduced_er);
}

auto save_fs(const link& root, const std::string& filename) -> error {
	// collect all errors happened
	auto errs = std::vector<error>{};
	if(auto er = error::eval_safe([&] {
		auto ar = tree_fs_output(filename);
		ar(root);
		//ar.serializeDeferments();
		errs = ar.wait_objects_saved(infinite);
	}))
		errs.push_back(er);

	return unite_errors(errs);
}

auto load_fs(link& root, const std::string& filename) -> error {
	// collect all errors happened
	auto errs = std::vector<error>{};
	if(auto er = error::eval_safe([&] {
		auto ar = tree_fs_input(filename, tree_fs_input::NodeLoad::Normal);
		ar(root);
		//ar.serializeDeferments();
		errs = ar.wait_objects_loaded(infinite);
	}))
		errs.push_back(er);

	return unite_errors(errs);
}

///////////////////////////////////////////////////////////////////////////////
//  generic archives
//
auto save_generic(const link& root, const std::string& filename, TreeArchive ar) -> error {
return error::eval_safe([&]() -> error {
	// open file for writing
	std::ofstream fs(
		filename,
		std::ios::out | std::ios::trunc | (ar == TreeArchive::Binary ? std::ios::binary : std::ios::openmode())
	);
	fs.exceptions(fs.failbit | fs.badbit);

	// dump link to JSON archive
	if(ar == TreeArchive::Binary) {
		cereal::PortableBinaryOutputArchive ja(fs);
		ja(root);
	}
	else {
		cereal::JSONOutputArchive ja(fs);
		ja(root);
	}
	return perfect;
}); }

auto load_generic(link& root, const std::string& filename, TreeArchive ar) -> error {
return error::eval_safe([&]() -> error {
	// open file for reading
	std::ifstream fs(
		filename,
		std::ios::in | (ar == TreeArchive::Binary ? std::ios::binary : std::ios::openmode())
	);
	fs.exceptions(fs.failbit | fs.badbit);

	// load link from JSON archive
	if(ar == TreeArchive::Binary) {
		cereal::PortableBinaryInputArchive ja(fs);
		ja(root);
	}
	else {
		cereal::JSONInputArchive ja(fs);
		ja(root);
	}
	return perfect;
}); }

///////////////////////////////////////////////////////////////////////////////
//  serial ator impl
//
auto save_actor(
	caf::event_based_actor* self, link root, std::string filename, TreeArchive ar,
	on_serialized_f cb
) -> caf::behavior {
	return {
		[ar, r = std::move(root), filename = std::move(filename), cb = std::move(cb)](a_apply) mutable
		-> error::box {
			// launch work
			auto er = ar == TreeArchive::FS ? save_fs(r, filename) : save_generic(r, filename, ar);
			// invoke callback
			if(cb) error::eval_safe([&]{ cb(std::move(r), er); });
			return er;
		}
	};
}

auto load_actor(
	caf::event_based_actor* self, std::string filename, TreeArchive ar,
	on_serialized_f cb
) -> caf::behavior {
	return {
		[ar, filename = std::move(filename), cb = std::move(cb)](a_apply) mutable
		-> link_or_errbox {
			// launch work
			link r;
			auto er = ar == TreeArchive::FS ? load_fs(r, filename) : load_generic(r, filename, ar);
			// invoke callback
			if(cb) error::eval_safe([&]{ cb(r, er); });
			if(er.ok())
				return r;
			else
				return tl::make_unexpected(std::move(er));
		}
	};
}

NAMESPACE_END()

/*-----------------------------------------------------------------------------
 *  tree save impl
 *-----------------------------------------------------------------------------*/
auto save_tree(link root, std::string filename, TreeArchive ar, timespan wait_for) -> error {
	bsout() << "*** save_tree with {} timeout" <<
		(wait_for == infinite ? "infinite" : to_string(wait_for)) << bs_end;
	// launch worker in detached actor
	auto Af = caf::make_function_view(
		system().spawn<caf::spawn_options::detach_flag>(
			save_actor, std::move(root), std::move(filename), ar, on_serialized_f{}
		),
		wait_for == infinite ? caf::infinite : caf::duration{wait_for}
	);

	// wait for result
	return actorf<error>(Af, a_apply());
}

BS_API auto save_tree(
	on_serialized_f cb, link root, std::string filename, TreeArchive ar
) -> void {
	// [NOTE] spawn save in detached actor to prevent starvation
	auto A = system().spawn<caf::spawn_options::detach_flag>(
		save_actor, std::move(root), std::move(filename), ar, std::move(cb)
	);
	caf::anon_send(A, a_apply());
}

/*-----------------------------------------------------------------------------
 *  tree load impl
 *-----------------------------------------------------------------------------*/
auto load_tree(std::string filename, TreeArchive ar) -> link_or_err {
	link r;
	auto er = ar == TreeArchive::FS ? load_fs(r, filename) : load_generic(r, filename, ar);
	if(er.ok())
		return r;
	else
		return tl::make_unexpected(std::move(er));

	//// launch worker in detached actor
	//auto Af = caf::make_function_view(
	//	system().spawn<caf::spawn_options::detach_flag>(
	//		load_actor, std::move(filename), ar, on_serialized_f{}
	//	),
	//	caf::infinite
	//);

	//// wait for result
	//return actorf<link_or_errbox>(Af, a_apply());
}

auto load_tree(
	on_serialized_f cb, std::string filename, TreeArchive ar
) -> void {
	// [NOTE] spawn save in detached actor to prevent starvation
	auto A = system().spawn<caf::spawn_options::detach_flag>(
		load_actor, std::move(filename), ar, std::move(cb)
	);
	caf::anon_send(A, a_apply());
}

NAMESPACE_END(blue_sky::tree)
<commit_msg>serial/tree: fix load from monofile (non tree-fs) archives<commit_after>/// @file
/// @author uentity
/// @date 22.06.2018
/// @brief Implementation of BS tree-related serialization
/// @copyright
/// This Source Code Form is subject to the terms of the Mozilla Public License,
/// v. 2.0. If a copy of the MPL was not distributed with this file,
/// You can obtain one at https://mozilla.org/MPL/2.0/

#include <bs/actor_common.h>
#include <bs/atoms.h>
#include <bs/kernel/radio.h>
#include <bs/log.h>
#include <bs/tree/tree.h>
#include <bs/serialize/serialize.h>
#include <bs/serialize/tree.h>
#include <bs/serialize/cafbind.h>

#include <fstream>

CAF_ALLOW_UNSAFE_MESSAGE_TYPE(blue_sky::tree::on_serialized_f)

NAMESPACE_BEGIN(blue_sky::tree)
using namespace kernel::radio;
/*-----------------------------------------------------------------------------
 *  tree serialization actor impl
 *-----------------------------------------------------------------------------*/
NAMESPACE_BEGIN()
///////////////////////////////////////////////////////////////////////////////
//  Tree FS archive
//
auto unite_errors(const std::vector<error>& errs) -> error {
	std::string reduced_er;
	for(const auto& er : errs) {
		if(er) {
			if(!reduced_er.empty()) reduced_er += '\n';
			reduced_er += er.what();
		}
	}
	return reduced_er.empty() ? success() : error::quiet(reduced_er);
}

auto save_fs(const link& root, const std::string& filename) -> error {
	// collect all errors happened
	auto errs = std::vector<error>{};
	if(auto er = error::eval_safe([&] {
		auto ar = tree_fs_output(filename);
		ar(root);
		//ar.serializeDeferments();
		errs = ar.wait_objects_saved(infinite);
	}))
		errs.push_back(er);

	return unite_errors(errs);
}

auto load_fs(link& root, const std::string& filename) -> error {
	// collect all errors happened
	auto errs = std::vector<error>{};
	if(auto er = error::eval_safe([&] {
		auto ar = tree_fs_input(filename, tree_fs_input::NodeLoad::Normal);
		ar(root);
		//ar.serializeDeferments();
		errs = ar.wait_objects_loaded(infinite);
	}))
		errs.push_back(er);

	return unite_errors(errs);
}

///////////////////////////////////////////////////////////////////////////////
//  generic archives
//
auto save_generic(const link& root, const std::string& filename, TreeArchive ar) -> error {
return error::eval_safe([&]() -> error {
	// open file for writing
	std::ofstream fs(
		filename,
		std::ios::out | std::ios::trunc |
			(ar == TreeArchive::Binary ? std::ios::binary : std::ios::openmode(0))
	);

	// dump link to JSON archive
	if(ar == TreeArchive::Binary) {
		cereal::PortableBinaryOutputArchive ja(fs);
		ja(root);
	}
	else {
		cereal::JSONOutputArchive ja(fs);
		ja(root);
	}
	return perfect;
}); }

auto load_generic(link& root, const std::string& filename, TreeArchive ar) -> error {
return error::eval_safe([&]() -> error {
	// open file for reading
	auto mode = std::ios::in;
	if(ar == TreeArchive::Binary) mode |= std::ios::binary;
	std::ifstream fs(
		filename,
		std::ios::in | (ar == TreeArchive::Binary ? std::ios::binary : std::ios::openmode(0))
	);

	// load link from JSON archive
	if(ar == TreeArchive::Binary) {
		cereal::PortableBinaryInputArchive ja(fs);
		ja(root);
	}
	else {
		cereal::JSONInputArchive ja(fs);
		ja(root);
	}
	return perfect;
}); }

///////////////////////////////////////////////////////////////////////////////
//  serial ator impl
//
auto save_actor(
	caf::event_based_actor* self, link root, std::string filename, TreeArchive ar,
	on_serialized_f cb
) -> caf::behavior {
	return {
		[ar, r = std::move(root), filename = std::move(filename), cb = std::move(cb)](a_apply) mutable
		-> error::box {
			// launch work
			auto er = ar == TreeArchive::FS ? save_fs(r, filename) : save_generic(r, filename, ar);
			// invoke callback
			if(cb) error::eval_safe([&]{ cb(std::move(r), er); });
			return er;
		}
	};
}

auto load_actor(
	caf::event_based_actor* self, std::string filename, TreeArchive ar,
	on_serialized_f cb
) -> caf::behavior {
	return {
		[ar, filename = std::move(filename), cb = std::move(cb)](a_apply) mutable
		-> link_or_errbox {
			// launch work
			link r;
			auto er = ar == TreeArchive::FS ? load_fs(r, filename) : load_generic(r, filename, ar);
			// invoke callback
			if(cb) error::eval_safe([&]{ cb(r, er); });
			if(er.ok())
				return r;
			else
				return tl::make_unexpected(std::move(er));
		}
	};
}

NAMESPACE_END()

/*-----------------------------------------------------------------------------
 *  tree save impl
 *-----------------------------------------------------------------------------*/
auto save_tree(link root, std::string filename, TreeArchive ar, timespan wait_for) -> error {
	//bsout() << "*** save_tree with {} timeout" <<
	//	(wait_for == infinite ? "infinite" : to_string(wait_for)) << bs_end;
	// launch worker in detached actor
	auto Af = caf::make_function_view(
		system().spawn<caf::spawn_options::detach_flag>(
			save_actor, std::move(root), std::move(filename), ar, on_serialized_f{}
		),
		wait_for == infinite ? caf::infinite : caf::duration{wait_for}
	);

	// wait for result
	return actorf<error>(Af, a_apply());
}

BS_API auto save_tree(
	on_serialized_f cb, link root, std::string filename, TreeArchive ar
) -> void {
	// [NOTE] spawn save in detached actor to prevent starvation
	auto A = system().spawn<caf::spawn_options::detach_flag>(
		save_actor, std::move(root), std::move(filename), ar, std::move(cb)
	);
	caf::anon_send(A, a_apply());
}

/*-----------------------------------------------------------------------------
 *  tree load impl
 *-----------------------------------------------------------------------------*/
auto load_tree(std::string filename, TreeArchive ar) -> link_or_err {
	//link r;
	//auto er = ar == TreeArchive::FS ? load_fs(r, filename) : load_generic(r, filename, ar);
	//if(er.ok())
	//	return r;
	//else
	//	return tl::make_unexpected(std::move(er));

	// launch worker in detached actor
	auto Af = caf::make_function_view(
		system().spawn<caf::spawn_options::detach_flag>(
			load_actor, std::move(filename), ar, on_serialized_f{}
		),
		caf::infinite
	);

	// wait for result
	return actorf<link_or_errbox>(Af, a_apply());
}

auto load_tree(
	on_serialized_f cb, std::string filename, TreeArchive ar
) -> void {
	// [NOTE] spawn save in detached actor to prevent starvation
	auto A = system().spawn<caf::spawn_options::detach_flag>(
		load_actor, std::move(filename), ar, std::move(cb)
	);
	caf::anon_send(A, a_apply());
}

NAMESPACE_END(blue_sky::tree)
<|endoftext|>


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

    Copyright (c) 2001,2002,2003 Cornelius Schumacher <schumacher@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include <qstring.h>
#include <qkeycode.h>
#include <qlayout.h>
#include <qtimer.h>
#include <qframe.h>
#include <qlabel.h>

#include <kdebug.h>
#include <klocale.h>
#include <kglobal.h>
#include <kglobalsettings.h>

#include "koglobals.h"
#include "koprefs.h"
#ifndef KORG_NOPLUGINS
#include "kocore.h"
#endif
#include "kodaymatrix.h"

#include <kcalendarsystem.h>

#include "navigatorbar.h"

#include "kdatenavigator.h"

KDateNavigator::KDateNavigator( QWidget *parent, const char *name )
  : QFrame( parent, name )
{
  setFrameStyle( QFrame::NoFrame );

  QGridLayout *topLayout = new QGridLayout( this, 8, 8 );

  mNavigatorBar = new NavigatorBar( this );
  topLayout->addMultiCellWidget( mNavigatorBar, 0, 0, 0, 7 );

  connect( mNavigatorBar, SIGNAL( goPrevYear() ), SIGNAL( goPrevYear() ) );
  connect( mNavigatorBar, SIGNAL( goPrevMonth() ), SIGNAL( goPrevMonth() ) );
  connect( mNavigatorBar, SIGNAL( goNextMonth() ), SIGNAL( goNextMonth() ) );
  connect( mNavigatorBar, SIGNAL( goNextYear() ), SIGNAL( goNextYear() ) );
  connect( mNavigatorBar, SIGNAL( goMonth( int ) ), SIGNAL( goMonth( int ) ) );

  int i;
  QString generalFont = KGlobalSettings::generalFont().family();

  // Set up the heading fields.
  for( i = 0; i < 7; i++ ) {
    headings[i] = new QLabel( this );
    headings[i]->setFont( QFont( generalFont, 10, QFont::Bold ) );
    headings[i]->setAlignment( AlignCenter );

    topLayout->addWidget( headings[i], 1, i + 1 );
  }

  // Create the weeknumber labels
  for( i = 0; i < 6; i++ ) {
    weeknos[i] = new QLabel( this );
    weeknos[i]->setAlignment( AlignCenter );
    weeknos[i]->setFont( QFont( generalFont, 10 ) );
    weeknos[i]->installEventFilter( this );

    topLayout->addWidget( weeknos[i], i + 2, 0 );
  }

  mDayMatrix = new KODayMatrix( this, "KDateNavigator::dayMatrix" );
  mDayMatrix->setFrameStyle( QFrame::Panel | QFrame::Sunken );
  mDayMatrix->setLineWidth( 1 );

  connect( mDayMatrix, SIGNAL( selected( const KCal::DateList & ) ),
           SIGNAL( datesSelected( const KCal::DateList & ) ) );

  connect( mDayMatrix, SIGNAL( incidenceDropped( Incidence * ) ),
           SIGNAL( incidenceDropped( Incidence * ) ) );
  connect( mDayMatrix, SIGNAL( incidenceDroppedMove( Incidence * , Incidence * ) ),
           SIGNAL( incidenceDroppedMove( Incidence *, Incidence * ) ) );

  topLayout->addMultiCellWidget( mDayMatrix, 2, 7, 1, 7 );

  // read settings from configuration file.
  updateConfig();
}

KDateNavigator::~KDateNavigator()
{
}

void KDateNavigator::setCalendar( Calendar *cal )
{
  mDayMatrix->setCalendar( cal );
}

void KDateNavigator::setBaseDate( const QDate &date )
{
  mBaseDate = date;

  updateDates();
  updateView();

  KCal::DateList dates;
  dates.append( date );
  mNavigatorBar->selectDates( dates );

  mDayMatrix->clearSelection();
  mDayMatrix->repaint();
}

QSizePolicy KDateNavigator::sizePolicy () const
{
  return QSizePolicy( QSizePolicy::MinimumExpanding,
                      QSizePolicy::MinimumExpanding );
}

void KDateNavigator::updateToday()
{
  if ( mDayMatrix->isEndOfMonth() ) {
    goNextMonth();
  }
  mDayMatrix->recalculateToday();
  mDayMatrix->repaint();
}

void KDateNavigator::updateDates()
{
  // Find the first day of the week of the current month.
  //int d1 = KOGlobals::self()->calendarSystem()->day( mBaseDate );
  QDate dayone( mBaseDate.year(), mBaseDate.month(), mBaseDate.day() );
  int d2 = KOGlobals::self()->calendarSystem()->day( dayone );
  //int di = d1 - d2 + 1;
  dayone = dayone.addDays( -d2 + 1 );

  int m_fstDayOfWkCalsys = KOGlobals::self()->calendarSystem()->dayOfWeek(
      dayone );

  // If month begins on Monday and Monday is first day of week,
  // month should begin on second line. Sunday doesn't have this problem.
  int nextLine = ( ( m_fstDayOfWkCalsys == 1) &&
                   ( KGlobal::locale()->weekStartDay() == 1 ) ) ? 7 : 0;

  // update the matrix dates
  int index = ( KGlobal::locale()->weekStartDay() == 1 ? 1 : 0 ) -
              m_fstDayOfWkCalsys - nextLine;

  mDayMatrix->updateView( dayone.addDays( index ) );

// each updateDates is followed by an updateView -> repaint is issued there !
//  mDayMatrix->repaint();
}

void KDateNavigator::updateDayMatrix()
{
  mDayMatrix->updateView();
  mDayMatrix->repaint();
}

void KDateNavigator::updateView()
{
//  kdDebug() << "KDateNavigator::updateView()" << endl;

  setUpdatesEnabled( false );

  int i;

//  kdDebug(5850) << "updateView() -> mDayMatrix->updateView()" << endl;
  mDayMatrix->updateView();

  // set the week numbers.
  for( i = 0; i < 6; i++ ) {
    QString weeknum;
    // remember, according to ISO 8601, the first week of the year is the
    // first week that contains a thursday.  Thus we must subtract off 4,
    // not just 1.

    //ET int dayOfYear = buttons[(i + 1) * 7 - 4]->date().dayOfYear();
    int dayOfYear = KOGlobals::self()->calendarSystem()->dayOfYear(
        ( mDayMatrix->getDate( ( i + 1 ) * 7 - 4 ) ) );

    if ( dayOfYear % 7 != 0 )
      weeknum.setNum( dayOfYear / 7 + 1 );
    else
      weeknum.setNum( dayOfYear / 7 );
    weeknos[i]->setText( weeknum );
  }

  setUpdatesEnabled( true );

//  kdDebug(5850) << "updateView() -> repaint()" << endl;
  repaint();
  mDayMatrix->repaint();
}

void KDateNavigator::updateConfig()
{
  int day;
  for( int i = 0; i < 7; i++ ) {
    // take the first letter of the day name to be the abbreviation
    if ( KGlobal::locale()->weekStartDay() == 1 ) {
      day = i + 1;
    } else {
      if ( i == 0 ) day = 7;
      else day = i;
    }
    QString dayName = KOGlobals::self()->calendarSystem()->weekDayName( day,
                                                                        true );
    if ( KOPrefs::instance()->mCompactDialogs ) dayName = dayName.left( 1 );
    headings[i]->setText( dayName );
  }

  // FIXME: Use actual config setting here
//  setShowWeekNums( true );
}

void KDateNavigator::setShowWeekNums( bool enabled )
{
  for( int i = 0; i < 6; i++ ) {
    if( enabled )
      weeknos[i]->show();
    else
      weeknos[i]->hide();
  }
}

void KDateNavigator::selectDates( const DateList &dateList )
{
  if ( dateList.count() > 0 ) {
    mNavigatorBar->selectDates( dateList );

    mSelectedDates = dateList;

    // set our record of the month and year that this datetbl is
    // displaying.
    mBaseDate = mSelectedDates.first();

    updateDates();

    mDayMatrix->setSelectedDaysFrom( *( dateList.begin() ),
                                     *( --dateList.end() ) );

    updateView();
  }
}

void KDateNavigator::wheelEvent ( QWheelEvent *e )
{
  if( e->delta() > 0 ) emit goPrevious();
  else emit goNext();

  e->accept();
}

bool KDateNavigator::eventFilter ( QObject *o, QEvent *e )
{
  if ( e->type() == QEvent::MouseButtonPress ) {
    int i;
    for( i = 0; i < 6; ++i ) {
      if ( o == weeknos[ i ] ) {
        QDate weekstart = mDayMatrix->getDate( i * 7 );
        emit weekClicked( weekstart );
        break;
      }
    }
    return true;
  } else {
    return false;
  }
}

#include "kdatenavigator.moc"
<commit_msg>Now that cvs head is open for commits again, apply the patch. If week start day is not monday, both week numbers are now shown like (for July 2004):         Sun Mon Tue Wed Thu Fri Sat  26/27   27  28  29  30   1   2   3  27/28    4   5   6   7   8   9  10  28/29   11  12  13  14  15  16  17  29/30   18  19  20  21  22  23  24  30/31   25  26  27  28  29  30  31<commit_after>/*
    This file is part of KOrganizer.

    Copyright (c) 2001,2002,2003 Cornelius Schumacher <schumacher@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include <qstring.h>
#include <qkeycode.h>
#include <qlayout.h>
#include <qtimer.h>
#include <qframe.h>
#include <qlabel.h>

#include <kdebug.h>
#include <klocale.h>
#include <kglobal.h>
#include <kglobalsettings.h>

#include "koglobals.h"
#include "koprefs.h"
#ifndef KORG_NOPLUGINS
#include "kocore.h"
#endif
#include "kodaymatrix.h"

#include <kcalendarsystem.h>

#include "navigatorbar.h"

#include "kdatenavigator.h"

KDateNavigator::KDateNavigator( QWidget *parent, const char *name )
  : QFrame( parent, name )
{
  setFrameStyle( QFrame::NoFrame );

  QGridLayout *topLayout = new QGridLayout( this, 8, 8 );

  mNavigatorBar = new NavigatorBar( this );
  topLayout->addMultiCellWidget( mNavigatorBar, 0, 0, 0, 7 );

  connect( mNavigatorBar, SIGNAL( goPrevYear() ), SIGNAL( goPrevYear() ) );
  connect( mNavigatorBar, SIGNAL( goPrevMonth() ), SIGNAL( goPrevMonth() ) );
  connect( mNavigatorBar, SIGNAL( goNextMonth() ), SIGNAL( goNextMonth() ) );
  connect( mNavigatorBar, SIGNAL( goNextYear() ), SIGNAL( goNextYear() ) );
  connect( mNavigatorBar, SIGNAL( goMonth( int ) ), SIGNAL( goMonth( int ) ) );

  int i;
  QString generalFont = KGlobalSettings::generalFont().family();

  // Set up the heading fields.
  for( i = 0; i < 7; i++ ) {
    headings[i] = new QLabel( this );
    headings[i]->setFont( QFont( generalFont, 10, QFont::Bold ) );
    headings[i]->setAlignment( AlignCenter );

    topLayout->addWidget( headings[i], 1, i + 1 );
  }

  // Create the weeknumber labels
  for( i = 0; i < 6; i++ ) {
    weeknos[i] = new QLabel( this );
    weeknos[i]->setAlignment( AlignCenter );
    weeknos[i]->setFont( QFont( generalFont, 10 ) );
    weeknos[i]->installEventFilter( this );

    topLayout->addWidget( weeknos[i], i + 2, 0 );
  }

  mDayMatrix = new KODayMatrix( this, "KDateNavigator::dayMatrix" );
  mDayMatrix->setFrameStyle( QFrame::Panel | QFrame::Sunken );
  mDayMatrix->setLineWidth( 1 );

  connect( mDayMatrix, SIGNAL( selected( const KCal::DateList & ) ),
           SIGNAL( datesSelected( const KCal::DateList & ) ) );

  connect( mDayMatrix, SIGNAL( incidenceDropped( Incidence * ) ),
           SIGNAL( incidenceDropped( Incidence * ) ) );
  connect( mDayMatrix, SIGNAL( incidenceDroppedMove( Incidence * , Incidence * ) ),
           SIGNAL( incidenceDroppedMove( Incidence *, Incidence * ) ) );

  topLayout->addMultiCellWidget( mDayMatrix, 2, 7, 1, 7 );

  // read settings from configuration file.
  updateConfig();
}

KDateNavigator::~KDateNavigator()
{
}

void KDateNavigator::setCalendar( Calendar *cal )
{
  mDayMatrix->setCalendar( cal );
}

void KDateNavigator::setBaseDate( const QDate &date )
{
  mBaseDate = date;

  updateDates();
  updateView();

  KCal::DateList dates;
  dates.append( date );
  mNavigatorBar->selectDates( dates );

  mDayMatrix->clearSelection();
  mDayMatrix->repaint();
}

QSizePolicy KDateNavigator::sizePolicy () const
{
  return QSizePolicy( QSizePolicy::MinimumExpanding,
                      QSizePolicy::MinimumExpanding );
}

void KDateNavigator::updateToday()
{
  if ( mDayMatrix->isEndOfMonth() ) {
    goNextMonth();
  }
  mDayMatrix->recalculateToday();
  mDayMatrix->repaint();
}

void KDateNavigator::updateDates()
{
  // Find the first day of the week of the current month.
  //int d1 = KOGlobals::self()->calendarSystem()->day( mBaseDate );
  QDate dayone( mBaseDate.year(), mBaseDate.month(), mBaseDate.day() );
  int d2 = KOGlobals::self()->calendarSystem()->day( dayone );
  //int di = d1 - d2 + 1;
  dayone = dayone.addDays( -d2 + 1 );

  int m_fstDayOfWkCalsys = KOGlobals::self()->calendarSystem()->dayOfWeek(
      dayone );

  // If month begins on Monday and Monday is first day of week,
  // month should begin on second line. Sunday doesn't have this problem.
  int nextLine = ( ( m_fstDayOfWkCalsys == 1) &&
                   ( KGlobal::locale()->weekStartDay() == 1 ) ) ? 7 : 0;

  // update the matrix dates
  int index = ( KGlobal::locale()->weekStartDay() == 1 ? 1 : 0 ) -
              m_fstDayOfWkCalsys - nextLine;

  mDayMatrix->updateView( dayone.addDays( index ) );

// each updateDates is followed by an updateView -> repaint is issued there !
//  mDayMatrix->repaint();
}

void KDateNavigator::updateDayMatrix()
{
  mDayMatrix->updateView();
  mDayMatrix->repaint();
}

void KDateNavigator::updateView()
{
//  kdDebug() << "KDateNavigator::updateView()" << endl;

  setUpdatesEnabled( false );

  int i;

//  kdDebug(5850) << "updateView() -> mDayMatrix->updateView()" << endl;
  mDayMatrix->updateView();

  // set the week numbers.
  const KCalendarSystem *calsys = KOGlobals::self()->calendarSystem();
  for( i = 0; i < 6; i++ ) {
    // Use QDate's weekNumber method to determine the week number!
    QDate dtStart = mDayMatrix->getDate( i * 7 );
    QDate dtEnd = mDayMatrix->getDate( ( i + 1 ) * 7 - 1 );
    int weeknumstart = calsys->weekNumber( dtStart );
    int weeknumend = calsys->weekNumber( dtEnd );
    QString weeknum;

    if ( weeknumstart != weeknumend ) {
      weeknum = i18n("start/end week number of line in date picker", "%1/%2")
                .arg( weeknumstart ).arg( weeknumend );
    } else {
      weeknum.setNum( weeknumstart );
    }
    weeknos[i]->setText( weeknum );
  }

  setUpdatesEnabled( true );

//  kdDebug(5850) << "updateView() -> repaint()" << endl;
  repaint();
  mDayMatrix->repaint();
}

void KDateNavigator::updateConfig()
{
  int day;
  for( int i = 0; i < 7; i++ ) {
    // take the first letter of the day name to be the abbreviation
    if ( KGlobal::locale()->weekStartDay() == 1 ) {
      day = i + 1;
    } else {
      if ( i == 0 ) day = 7;
      else day = i;
    }
    QString dayName = KOGlobals::self()->calendarSystem()->weekDayName( day,
                                                                        true );
    if ( KOPrefs::instance()->mCompactDialogs ) dayName = dayName.left( 1 );
    headings[i]->setText( dayName );
  }

  // FIXME: Use actual config setting here
//  setShowWeekNums( true );
}

void KDateNavigator::setShowWeekNums( bool enabled )
{
  for( int i = 0; i < 6; i++ ) {
    if( enabled )
      weeknos[i]->show();
    else
      weeknos[i]->hide();
  }
}

void KDateNavigator::selectDates( const DateList &dateList )
{
  if ( dateList.count() > 0 ) {
    mNavigatorBar->selectDates( dateList );

    mSelectedDates = dateList;

    // set our record of the month and year that this datetbl is
    // displaying.
    mBaseDate = mSelectedDates.first();

    updateDates();

    mDayMatrix->setSelectedDaysFrom( *( dateList.begin() ),
                                     *( --dateList.end() ) );

    updateView();
  }
}

void KDateNavigator::wheelEvent ( QWheelEvent *e )
{
  if( e->delta() > 0 ) emit goPrevious();
  else emit goNext();

  e->accept();
}

bool KDateNavigator::eventFilter ( QObject *o, QEvent *e )
{
  if ( e->type() == QEvent::MouseButtonPress ) {
    int i;
    for( i = 0; i < 6; ++i ) {
      if ( o == weeknos[ i ] ) {
        QDate weekstart = mDayMatrix->getDate( i * 7 );
        emit weekClicked( weekstart );
        break;
      }
    }
    return true;
  } else {
    return false;
  }
}

#include "kdatenavigator.moc"
<|endoftext|>


<commit_before>/* This file is part of the KDE libraries
 *  Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 */
#include <editorchooser.h>
#include <editorchooser.moc>

#include <QtGui/QComboBox>
#include <QtCore/QStringList>
#include <QtGui/QLabel>
#include <QtGui/QLayout>

#include <kmimetypetrader.h>
#include <kconfig.h>
#include <klocale.h>
#include <kglobal.h>

#include "ui_editorchooser_ui.h"
#include <kconfiggroup.h>

using namespace KTextEditor;

namespace KTextEditor
{
  class PrivateEditorChooser
  {
  public:
    PrivateEditorChooser()
    {
    }
    ~PrivateEditorChooser(){}
    // Data Members
    Ui::EditorChooser *chooser;
    QStringList ElementNames;
    QStringList elements;
  };
}

EditorChooser::EditorChooser(QWidget *parent)
  : QWidget(parent)
{
  d = new PrivateEditorChooser ();

  d->chooser = new Ui::EditorChooser();
  d->chooser->setupUi(this);

  KService::List offers = KMimeTypeTrader::self()->query("text/plain", "KTextEditor/Document");
  KConfigGroup config = KSharedConfig::openConfig("default_components")->group("KTextEditor");
  QString editor = config.readPathEntry("embeddedEditor", QString());

  if (editor.isEmpty()) editor = "katepart";

  // search default component
  for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it)
  {
    if ((*it)->desktopEntryName().contains(editor))
    {
      d->chooser->editorCombo->addItem(i18n("System Default (currently: %1)", (*it)->name()));
      break;
    }
  }

  // add list of all available components
  for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it)
  {
    d->chooser->editorCombo->addItem((*it)->name());
    d->elements.append((*it)->desktopEntryName());
  }
  d->chooser->editorCombo->setCurrentIndex(0);

  connect(d->chooser->editorCombo,SIGNAL(activated(int)),this,SIGNAL(changed()));
}

EditorChooser:: ~EditorChooser()
{
  delete d->chooser;
  delete d;
}

void EditorChooser::readAppSetting(const QString& postfix)
{
  KConfigGroup cg(KGlobal::config(), "KTEXTEDITOR:" + postfix);
  QString editor = cg.readPathEntry("editor", QString());
  if (editor.isEmpty())
    d->chooser->editorCombo->setCurrentIndex(0);
  else
  {
    // + 1, because item 0 is the default one.
    int idx = d->elements.indexOf(editor) + 1;
    d->chooser->editorCombo->setCurrentIndex(idx);
  }
}

void EditorChooser::writeAppSetting(const QString& postfix)
{
  KConfigGroup cg(KGlobal::config(), "KTEXTEDITOR:" + postfix);
  cg.writeEntry("DEVELOPER_INFO","NEVER TRY TO USE VALUES FROM THAT GROUP, THEY ARE SUBJECT TO CHANGES");
  cg.writePathEntry("editor", (d->chooser->editorCombo->currentIndex()<=0) ? //< for broken installations, where editor list is empty
  QString() : QString(d->elements.at(d->chooser->editorCombo->currentIndex()-1)));
}

KTextEditor::Editor *EditorChooser::editor(const QString& postfix,
                                           bool fallBackToKatePart)
{
  // try to read the used library from the application's config
  KConfigGroup cg(KGlobal::config(), "KTEXTEDITOR:" + postfix);
  QString editor = cg.readPathEntry("editor", QString());
  if (editor.isEmpty())
  {
    // there is no library set in the application's config,
    // fall back to KDE's system default
    KConfig config("default_components");
    editor = config.group("KTextEditor").readPathEntry("embeddedEditor", "katepart");
  }

  KService::Ptr serv = KService::serviceByDesktopName(editor);
  if (serv)
  {
    KTextEditor::Editor *tmpEd = KTextEditor::editor(serv->library().toLatin1());
    if (tmpEd) return tmpEd;
  }
  if (fallBackToKatePart)
    return KTextEditor::editor("katepart");

  return 0;
}

// kate: space-indent on; indent-width 2; replace-tabs on;
<commit_msg>Enforce minimum size<commit_after>/* This file is part of the KDE libraries
 *  Copyright (C) 2005 Joseph Wenninger <jowenn@kde.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public License
 *  along with this library; see the file COPYING.LIB.  If not, write to
 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 */
#include <editorchooser.h>
#include <editorchooser.moc>

#include <QtGui/QComboBox>
#include <QtCore/QStringList>
#include <QtGui/QLabel>
#include <QtGui/QLayout>

#include <kmimetypetrader.h>
#include <kconfig.h>
#include <klocale.h>
#include <kglobal.h>

#include "ui_editorchooser_ui.h"
#include <kconfiggroup.h>

using namespace KTextEditor;

namespace KTextEditor
{
  class PrivateEditorChooser
  {
  public:
    PrivateEditorChooser()
    {
    }
    ~PrivateEditorChooser(){}
    // Data Members
    Ui::EditorChooser *chooser;
    QStringList ElementNames;
    QStringList elements;
  };
}

EditorChooser::EditorChooser(QWidget *parent)
  : QWidget(parent)
{
  d = new PrivateEditorChooser ();

  d->chooser = new Ui::EditorChooser();
  d->chooser->setupUi(this);

  KService::List offers = KMimeTypeTrader::self()->query("text/plain", "KTextEditor/Document");
  KConfigGroup config = KSharedConfig::openConfig("default_components")->group("KTextEditor");
  QString editor = config.readPathEntry("embeddedEditor", QString());

  if (editor.isEmpty()) editor = "katepart";

  // search default component
  for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it)
  {
    if ((*it)->desktopEntryName().contains(editor))
    {
      d->chooser->editorCombo->addItem(i18n("System Default (currently: %1)", (*it)->name()));
      break;
    }
  }

  // add list of all available components
  for (KService::List::Iterator it = offers.begin(); it != offers.end(); ++it)
  {
    d->chooser->editorCombo->addItem((*it)->name());
    d->elements.append((*it)->desktopEntryName());
  }
  d->chooser->editorCombo->setCurrentIndex(0);

  connect(d->chooser->editorCombo,SIGNAL(activated(int)),this,SIGNAL(changed()));

  setMinimumSize(sizeHint());
}

EditorChooser:: ~EditorChooser()
{
  delete d->chooser;
  delete d;
}

void EditorChooser::readAppSetting(const QString& postfix)
{
  KConfigGroup cg(KGlobal::config(), "KTEXTEDITOR:" + postfix);
  QString editor = cg.readPathEntry("editor", QString());
  if (editor.isEmpty())
    d->chooser->editorCombo->setCurrentIndex(0);
  else
  {
    // + 1, because item 0 is the default one.
    int idx = d->elements.indexOf(editor) + 1;
    d->chooser->editorCombo->setCurrentIndex(idx);
  }
}

void EditorChooser::writeAppSetting(const QString& postfix)
{
  KConfigGroup cg(KGlobal::config(), "KTEXTEDITOR:" + postfix);
  cg.writeEntry("DEVELOPER_INFO","NEVER TRY TO USE VALUES FROM THAT GROUP, THEY ARE SUBJECT TO CHANGES");
  cg.writePathEntry("editor", (d->chooser->editorCombo->currentIndex()<=0) ? //< for broken installations, where editor list is empty
  QString() : QString(d->elements.at(d->chooser->editorCombo->currentIndex()-1)));
}

KTextEditor::Editor *EditorChooser::editor(const QString& postfix,
                                           bool fallBackToKatePart)
{
  // try to read the used library from the application's config
  KConfigGroup cg(KGlobal::config(), "KTEXTEDITOR:" + postfix);
  QString editor = cg.readPathEntry("editor", QString());
  if (editor.isEmpty())
  {
    // there is no library set in the application's config,
    // fall back to KDE's system default
    KConfig config("default_components");
    editor = config.group("KTextEditor").readPathEntry("embeddedEditor", "katepart");
  }

  KService::Ptr serv = KService::serviceByDesktopName(editor);
  if (serv)
  {
    KTextEditor::Editor *tmpEd = KTextEditor::editor(serv->library().toLatin1());
    if (tmpEd) return tmpEd;
  }
  if (fallBackToKatePart)
    return KTextEditor::editor("katepart");

  return 0;
}

// kate: space-indent on; indent-width 2; replace-tabs on;
<|endoftext|>


<commit_before><commit_msg>WaE: keep msvc happy<commit_after><|endoftext|>


<commit_before>/**
 * @file lsh_test.cpp
 *
 * Unit tests for the 'LSHSearch' class.
 */
#include <mlpack/core.hpp>
#include <mlpack/core/metrics/lmetric.hpp>
#include <boost/test/unit_test.hpp>
#include "old_boost_test_definitions.hpp"

#include <mlpack/methods/lsh/lsh_search.hpp>
#include <mlpack/methods/neighbor_search/neighbor_search.hpp>

using namespace std;
using namespace mlpack;
using namespace mlpack::neighbor;

double compute_recall(
    const arma::Mat<size_t>& LSHneighbors,
    const arma::Mat<size_t>& groundTruth)
{
  const size_t queries = LSHneighbors.n_cols;
  const size_t neigh = LSHneighbors.n_rows;

  int same = 0;
  for (size_t q = 0; q < queries; ++q)
  {
    for (size_t n = 0; n < neigh; ++n)
    {
      same += (LSHneighbors(n,q) == groundTruth(n,q));
    }
  }
  return static_cast<double>(same)/
    (static_cast<double>(queries*neigh));
}

BOOST_AUTO_TEST_SUITE(LSHTest);

/**
 * Test: Run LSH with varying number of tables, keeping all other parameters
 * constant. Compute the recall, i.e. the number of reported neighbors that
 * are real neighbors of the query.
 * LSH's property is that (with high probability), increasing the number of
 * tables will increase recall. Epsilon ensures that if noise lightly affects
 * the projections, the test will not fail.
 * This produces false negatives, so we attempt the test numTries times and
 * only declare failure if all of them fail.
 */
BOOST_AUTO_TEST_CASE(numTablesTest)
{

  //math::RandomSeed(time(0));
  //kNN and LSH parameters (use LSH default parameters)
  const int k = 4;
  const int numProj = 10;
  const double hashWidth = 0;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  //test parameters
  const double epsilon = 0.1; //allowed deviation from expected monotonicity
  const int numTries = 5; //tries for each test before declaring failure

  //read iris training and testing data as reference and query
  const string trainSet="iris_train.csv";
  const string testSet="iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  //Run classic knn on reference data
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);

  bool fail;
  for (int t = 0; t < numTries; ++t)
  {
    fail = false;

    const int lSize = 6; //number of runs
    const int lValue[] = {1, 8, 16, 32, 64, 128}; //number of tables
    double lValueRecall[lSize] = {0.0}; //recall of each LSH run

    for (size_t l=0; l < lSize; ++l)
    {
      //run LSH with only numTables varying (other values default)
      LSHSearch<> lshTest(rdata, numProj, lValue[l],
          hashWidth, secondHashSize, bucketSize);
      arma::Mat<size_t> LSHneighbors;
      arma::mat LSHdistances;
      lshTest.Search(qdata, k, LSHneighbors, LSHdistances);

      //compute recall for each query
      lValueRecall[l] = compute_recall(LSHneighbors, groundTruth);

      if (l > 0)
      {
        if(lValueRecall[l] < lValueRecall[l-1] - epsilon)
        {
          fail = true; //if test fails at one point, stop and retry
          break;
        }
      }
    }

    if ( !fail )
      break; //if test passes one time, it is sufficient
  }

  BOOST_REQUIRE(fail == false);
}

/*Test: Run LSH with varying hash width, keeping all other parameters
 * constant. Compute the recall, i.e. the number of reported neighbors that
 * are real neighbors of the query.
 * LSH's property is that (with high probability), increasing the hash width
 * will increase recall. Epsilon ensures that if noise lightly affects the
 * projections, the test will not fail.
 */
BOOST_AUTO_TEST_CASE(hashWidthTest)
{

  //math::RandomSeed(time(0));
  //kNN and LSH parameters (use LSH default parameters)
  const int k = 4;
  const int numTables = 30;
  const int numProj = 10;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  //test parameters
  const double epsilon = 0.1; //allowed deviation from expected monotonicity

  //read iris training and testing data as reference and query
  const string trainSet="iris_train.csv";
  const string testSet="iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  //Run classic knn on reference data
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);
  const int hSize = 7; //number of runs
  const double hValue[] = {0.1, 0.5, 1, 5, 10, 50, 500}; //hash width
  double hValueRecall[hSize] = {0.0}; //recall of each run

  for (size_t h = 0; h < hSize; ++h)
  {
    //run LSH with only hashWidth varying (other values default)
    LSHSearch<> lshTest(
        rdata,
        numProj,
        numTables,
        hValue[h],
        secondHashSize,
        bucketSize);

    arma::Mat<size_t> LSHneighbors;
    arma::mat LSHdistances;
    lshTest.Search(qdata, k, LSHneighbors, LSHdistances);

    //compute recall for each query
    hValueRecall[h] = compute_recall(LSHneighbors, groundTruth);

    if (h > 0)
        BOOST_REQUIRE_GE(hValueRecall[h], hValueRecall[h-1] - epsilon);

  }
}

/**
 * Test: Run LSH with varying number of projections, keeping other parameters
 * constant. Compute the recall, i.e. the number of reported neighbors that
 * are real neighbors of the query.
 * LSH's property is that (with high probability), increasing the number of
 * projections per table will decrease recall. Epsilon ensures that if noise
 * lightly affects the projections, the test will not fail.
 */
BOOST_AUTO_TEST_CASE(numProjTest)
{

  //math::RandomSeed(time(0));
  //kNN and LSH parameters (use LSH default parameters)
  const int k = 4;
  const int numTables = 30;
  const double hashWidth = 0;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  //test parameters
  const double epsilon = 0.1; //allowed deviation from expected monotonicity

  //read iris training and testing data as reference and query
  const string trainSet="iris_train.csv";
  const string testSet="iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  //Run classic knn on reference data
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);

  //LSH test parameters for numProj
  const int pSize = 5; //number of runs
  const int pValue[] = {1, 10, 20, 50, 100}; //number of projections
  double pValueRecall[pSize] = {0.0}; //recall of each run

  for (size_t p = 0; p < pSize; ++p)
  {
    //run LSH with only numProj varying (other values default)
    LSHSearch<> lshTest(
        rdata,
        pValue[p],
        numTables,
        hashWidth,
        secondHashSize,
        bucketSize);

    arma::Mat<size_t> LSHneighbors;
    arma::mat LSHdistances;
    lshTest.Search(qdata, k, LSHneighbors, LSHdistances);

    //compute recall for each query
    pValueRecall[p] = compute_recall(LSHneighbors, groundTruth);

    if (p > 0) //don't check first run, only that increasing P decreases recall
        BOOST_REQUIRE_LE(pValueRecall[p] - epsilon, pValueRecall[p-1]);
  }
}

/**
 * Test: Run two LSH searches:
 * First, a very expensive LSH search, with a large number of hash tables
 * and a large hash width. This run should return an acceptable recall. We set
 * the bar very low (recall >= 50%) to make sure that a test fail means bad
 * implementation.
 * Second, a very cheap LSH search, with parameters that should cause recall
 * to be very low. Set the threshhold very high (recall <= 25%) to make sure
 * that a test fail means bad implementation.
 */
BOOST_AUTO_TEST_CASE(recallTest)
{
  //math::RandomSeed(time(0));
  //kNN and LSH parameters (use LSH default parameters)
  const int k = 4;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  //read iris training and testing data as reference and query
  const string trainSet="iris_train.csv";
  const string testSet="iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  //Run classic knn on reference data
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);

  //Expensive LSH run
  const int hExp = 10000; //first-level hash width
  const int kExp = 1; //projections per table
  const int tExp = 128; //number of tables
  const double recallThreshExp = 0.5;

  LSHSearch<> lshTestExp(
      rdata,
      kExp,
      tExp,
      hExp,
      secondHashSize,
      bucketSize);
  arma::Mat<size_t> LSHneighborsExp;
  arma::mat LSHdistancesExp;
  lshTestExp.Search(qdata, k, LSHneighborsExp, LSHdistancesExp);

  const double recallExp = compute_recall(LSHneighborsExp, groundTruth);

  //This run should have recall higher than the threshold
  BOOST_REQUIRE_GE(recallExp, recallThreshExp);

  //Cheap LSH Run
  const int hChp = 1; //small first-level hash width
  const int kChp = 1000; //large number of projections per table
  const int tChp = 1; //only one table
  const double recallThreshChp = 0.25; //recall threshold

  LSHSearch<> lshTestChp(
      rdata,
      kChp,
      tChp,
      hChp,
      secondHashSize,
      bucketSize);
  arma::Mat<size_t> LSHneighborsChp;
  arma::mat LSHdistancesChp;
  lshTestChp.Search(qdata, k, LSHneighborsChp, LSHdistancesChp);

  const double recallChp = compute_recall(LSHneighborsChp, groundTruth);

  //This run should have recall lower than the threshold
  BOOST_REQUIRE_LE(recallChp, recallThreshChp);
}

BOOST_AUTO_TEST_CASE(LSHTrainTest)
{
  // This is a not very good test that simply checks that the re-trained LSH
  // model operates on the correct dimensionality and returns the correct number
  // of results.
  arma::mat referenceData = arma::randu<arma::mat>(3, 100);
  arma::mat newReferenceData = arma::randu<arma::mat>(10, 400);
  arma::mat queryData = arma::randu<arma::mat>(10, 200);

  LSHSearch<> lsh(referenceData, 3, 2, 2.0, 11, 3);

  lsh.Train(newReferenceData, 4, 3, 3.0, 12, 4);

  arma::Mat<size_t> neighbors;
  arma::mat distances;

  lsh.Search(queryData, 3, neighbors, distances);

  BOOST_REQUIRE_EQUAL(neighbors.n_cols, 200);
  BOOST_REQUIRE_EQUAL(neighbors.n_rows, 3);
  BOOST_REQUIRE_EQUAL(distances.n_cols, 200);
  BOOST_REQUIRE_EQUAL(distances.n_rows, 3);
}

BOOST_AUTO_TEST_CASE(EmptyConstructorTest)
{
  // If we create an empty LSH model and then call Search(), it should throw an
  // exception.
  LSHSearch<> lsh;

  arma::mat dataset = arma::randu<arma::mat>(5, 50);
  arma::mat distances;
  arma::Mat<size_t> neighbors;
  BOOST_REQUIRE_THROW(lsh.Search(dataset, 2, neighbors, distances),
      std::invalid_argument);

  // Now, train.
  lsh.Train(dataset, 4, 3, 3.0, 12, 4);

  lsh.Search(dataset, 3, neighbors, distances);

  BOOST_REQUIRE_EQUAL(neighbors.n_cols, 50);
  BOOST_REQUIRE_EQUAL(neighbors.n_rows, 3);
  BOOST_REQUIRE_EQUAL(distances.n_cols, 50);
  BOOST_REQUIRE_EQUAL(distances.n_rows, 3);
}

BOOST_AUTO_TEST_SUITE_END();
<commit_msg>Style fixes for LSH test.<commit_after>/**
 * @file lsh_test.cpp
 *
 * Unit tests for the 'LSHSearch' class.
 */
#include <mlpack/core.hpp>
#include <mlpack/core/metrics/lmetric.hpp>
#include <boost/test/unit_test.hpp>
#include "old_boost_test_definitions.hpp"

#include <mlpack/methods/lsh/lsh_search.hpp>
#include <mlpack/methods/neighbor_search/neighbor_search.hpp>

using namespace std;
using namespace mlpack;
using namespace mlpack::neighbor;

double ComputeRecall(
    const arma::Mat<size_t>& lshNeighbors,
    const arma::Mat<size_t>& groundTruth)
{
  const size_t queries = lshNeighbors.n_cols;
  const size_t neigh = lshNeighbors.n_rows;

  size_t same = 0;
  for (size_t q = 0; q < queries; ++q)
  {
    for (size_t n = 0; n < neigh; ++n)
    {
      same += (lshNeighbors(n, q) == groundTruth(n, q));
    }
  }
  return static_cast<double>(same) /
    (static_cast<double>(queries * neigh));
}

BOOST_AUTO_TEST_SUITE(LSHTest);

/**
 * Test: Run LSH with varying number of tables, keeping all other parameters
 * constant. Compute the recall, i.e. the number of reported neighbors that
 * are real neighbors of the query.
 * LSH's property is that (with high probability), increasing the number of
 * tables will increase recall. Epsilon ensures that if noise lightly affects
 * the projections, the test will not fail.
 * This produces false negatives, so we attempt the test numTries times and
 * only declare failure if all of them fail.
 */
BOOST_AUTO_TEST_CASE(NumTablesTest)
{
  // kNN and LSH parameters (use LSH default parameters).
  const int k = 4;
  const int numProj = 10;
  const double hashWidth = 0;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  // Test parameters.
  const double epsilon = 0.1; // Allowed deviation from expected monotonicity.
  const int numTries = 5; // Tries for each test before declaring failure.

  // Read iris training and testing data as reference and query sets.
  const string trainSet = "iris_train.csv";
  const string testSet = "iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  // Run classic knn on reference data.
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);

  bool fail;
  for (int t = 0; t < numTries; ++t)
  {
    fail = false;

    const int lSize = 6; // Number of runs.
    const int lValue[] = {1, 8, 16, 32, 64, 128}; // Number of tables.
    double lValueRecall[lSize] = {0.0}; // Recall of each LSH run.

    for (size_t l = 0; l < lSize; ++l)
    {
      // Run LSH with only numTables varying (other values are defaults).
      LSHSearch<> lshTest(rdata, numProj, lValue[l], hashWidth, secondHashSize,
          bucketSize);
      arma::Mat<size_t> lshNeighbors;
      arma::mat lshDistances;
      lshTest.Search(qdata, k, lshNeighbors, lshDistances);

      // Compute recall for each query.
      lValueRecall[l] = ComputeRecall(lshNeighbors, groundTruth);

      if (l > 0)
      {
        if (lValueRecall[l] < lValueRecall[l - 1] - epsilon)
        {
          fail = true; // If test fails at one point, stop and retry.
          break;
        }
      }
    }

    if (!fail)
      break; // If test passes one time, it is sufficient.
  }

  BOOST_REQUIRE(fail == false);
}

/**
 * Test: Run LSH with varying hash width, keeping all other parameters
 * constant. Compute the recall, i.e. the number of reported neighbors that
 * are real neighbors of the query.
 * LSH's property is that (with high probability), increasing the hash width
 * will increase recall. Epsilon ensures that if noise lightly affects the
 * projections, the test will not fail.
 */
BOOST_AUTO_TEST_CASE(HashWidthTest)
{
  // kNN and LSH parameters (use LSH default parameters).
  const int k = 4;
  const int numTables = 30;
  const int numProj = 10;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  // Test parameters.
  const double epsilon = 0.1; // Allowed deviation from expected monotonicity.

  // Read iris training and testing data as reference and query.
  const string trainSet = "iris_train.csv";
  const string testSet = "iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  // Run classic knn on reference data.
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);
  const int hSize = 7; // Number of runs.
  const double hValue[] = {0.1, 0.5, 1, 5, 10, 50, 500}; // Hash width.
  double hValueRecall[hSize] = {0.0}; // Recall of each run.

  for (size_t h = 0; h < hSize; ++h)
  {
    // Run LSH with only hashWidth varying (other values are defaults).
    LSHSearch<> lshTest(
        rdata,
        numProj,
        numTables,
        hValue[h],
        secondHashSize,
        bucketSize);

    arma::Mat<size_t> lshNeighbors;
    arma::mat lshDistances;
    lshTest.Search(qdata, k, lshNeighbors, lshDistances);

    // Compute recall for each query.
    hValueRecall[h] = ComputeRecall(lshNeighbors, groundTruth);

    if (h > 0)
      BOOST_REQUIRE_GE(hValueRecall[h], hValueRecall[h - 1] - epsilon);
  }
}

/**
 * Test: Run LSH with varying number of projections, keeping other parameters
 * constant. Compute the recall, i.e. the number of reported neighbors that
 * are real neighbors of the query.
 * LSH's property is that (with high probability), increasing the number of
 * projections per table will decrease recall. Epsilon ensures that if noise
 * lightly affects the projections, the test will not fail.
 */
BOOST_AUTO_TEST_CASE(NumProjTest)
{
  // kNN and LSH parameters (use LSH default parameters).
  const int k = 4;
  const int numTables = 30;
  const double hashWidth = 0;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  // Test parameters.
  const double epsilon = 0.1; // Allowed deviation from expected monotonicity.

  // Read iris training and testing data as reference and query sets.
  const string trainSet = "iris_train.csv";
  const string testSet = "iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  // Run classic knn on reference data.
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);

  // LSH test parameters for numProj.
  const int pSize = 5; // Number of runs.
  const int pValue[] = {1, 10, 20, 50, 100}; // Number of projections.
  double pValueRecall[pSize] = {0.0}; // Recall of each run.

  for (size_t p = 0; p < pSize; ++p)
  {
    // Run LSH with only numProj varying (other values are defaults).
    LSHSearch<> lshTest(
        rdata,
        pValue[p],
        numTables,
        hashWidth,
        secondHashSize,
        bucketSize);

    arma::Mat<size_t> lshNeighbors;
    arma::mat lshDistances;
    lshTest.Search(qdata, k, lshNeighbors, lshDistances);

    // Compute recall for each query.
    pValueRecall[p] = ComputeRecall(lshNeighbors, groundTruth);

    // Don't check the first run; only check that increasing P decreases recall.
    if (p > 0)
      BOOST_REQUIRE_LE(pValueRecall[p] - epsilon, pValueRecall[p - 1]);
  }
}

/**
 * Test: Run two LSH searches:
 * First, a very expensive LSH search, with a large number of hash tables
 * and a large hash width. This run should return an acceptable recall. We set
 * the bar very low (recall >= 50%) to make sure that a test fail means bad
 * implementation.
 * Second, a very cheap LSH search, with parameters that should cause recall
 * to be very low. Set the threshhold very high (recall <= 25%) to make sure
 * that a test fail means bad implementation.
 */
BOOST_AUTO_TEST_CASE(RecallTest)
{
  // kNN and LSH parameters (use LSH default parameters).
  const int k = 4;
  const int secondHashSize = 99901;
  const int bucketSize = 500;

  // Read iris training and testing data as reference and query sets.
  const string trainSet = "iris_train.csv";
  const string testSet = "iris_test.csv";
  arma::mat rdata;
  arma::mat qdata;
  data::Load(trainSet, rdata, true);
  data::Load(testSet, qdata, true);

  // Run classic knn on reference data.
  AllkNN knn(rdata);
  arma::Mat<size_t> groundTruth;
  arma::mat groundDistances;
  knn.Search(qdata, k, groundTruth, groundDistances);

  // Expensive LSH run.
  const int hExp = 10000; // First-level hash width.
  const int kExp = 1; // Projections per table.
  const int tExp = 128; // Number of tables.
  const double recallThreshExp = 0.5;

  LSHSearch<> lshTestExp(
      rdata,
      kExp,
      tExp,
      hExp,
      secondHashSize,
      bucketSize);
  arma::Mat<size_t> lshNeighborsExp;
  arma::mat lshDistancesExp;
  lshTestExp.Search(qdata, k, lshNeighborsExp, lshDistancesExp);

  const double recallExp = ComputeRecall(lshNeighborsExp, groundTruth);

  // This run should have recall higher than the threshold.
  BOOST_REQUIRE_GE(recallExp, recallThreshExp);

  // Cheap LSH run.
  const int hChp = 1; // Small first-level hash width.
  const int kChp = 1000; // Large number of projections per table.
  const int tChp = 1; // Only one table.
  const double recallThreshChp = 0.25; // Recall threshold.

  LSHSearch<> lshTestChp(
      rdata,
      kChp,
      tChp,
      hChp,
      secondHashSize,
      bucketSize);
  arma::Mat<size_t> lshNeighborsChp;
  arma::mat lshDistancesChp;
  lshTestChp.Search(qdata, k, lshNeighborsChp, lshDistancesChp);

  const double recallChp = ComputeRecall(lshNeighborsChp, groundTruth);

  // This run should have recall lower than the threshold.
  BOOST_REQUIRE_LE(recallChp, recallThreshChp);
}

BOOST_AUTO_TEST_CASE(LSHTrainTest)
{
  // This is a not very good test that simply checks that the re-trained LSH
  // model operates on the correct dimensionality and returns the correct number
  // of results.
  arma::mat referenceData = arma::randu<arma::mat>(3, 100);
  arma::mat newReferenceData = arma::randu<arma::mat>(10, 400);
  arma::mat queryData = arma::randu<arma::mat>(10, 200);

  LSHSearch<> lsh(referenceData, 3, 2, 2.0, 11, 3);

  lsh.Train(newReferenceData, 4, 3, 3.0, 12, 4);

  arma::Mat<size_t> neighbors;
  arma::mat distances;

  lsh.Search(queryData, 3, neighbors, distances);

  BOOST_REQUIRE_EQUAL(neighbors.n_cols, 200);
  BOOST_REQUIRE_EQUAL(neighbors.n_rows, 3);
  BOOST_REQUIRE_EQUAL(distances.n_cols, 200);
  BOOST_REQUIRE_EQUAL(distances.n_rows, 3);
}

BOOST_AUTO_TEST_CASE(EmptyConstructorTest)
{
  // If we create an empty LSH model and then call Search(), it should throw an
  // exception.
  LSHSearch<> lsh;

  arma::mat dataset = arma::randu<arma::mat>(5, 50);
  arma::mat distances;
  arma::Mat<size_t> neighbors;
  BOOST_REQUIRE_THROW(lsh.Search(dataset, 2, neighbors, distances),
      std::invalid_argument);

  // Now, train.
  lsh.Train(dataset, 4, 3, 3.0, 12, 4);

  lsh.Search(dataset, 3, neighbors, distances);

  BOOST_REQUIRE_EQUAL(neighbors.n_cols, 50);
  BOOST_REQUIRE_EQUAL(neighbors.n_rows, 3);
  BOOST_REQUIRE_EQUAL(distances.n_cols, 50);
  BOOST_REQUIRE_EQUAL(distances.n_rows, 3);
}

BOOST_AUTO_TEST_SUITE_END();
<|endoftext|>