<commit_before>/*************************************************************************
 *   Copyright (C) 2011-2012 by Paul-Louis Ageneau                       *
 *   paul-louis (at) ageneau (dot) org                                   *
 *                                                                       *
 *   This file is part of TeapotNet.                                     *
 *                                                                       *
 *   TeapotNet is free software: you can redistribute it and/or modify   *
 *   it under the terms of the GNU Affero General Public License as      *
 *   published by the Free Software Foundation, either version 3 of      *
 *   the License, or (at your option) any later version.                 *
 *                                                                       *
 *   TeapotNet 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 Affero General Public License for more details.                 *
 *                                                                       *
 *   You should have received a copy of the GNU Affero General Public    *
 *   License along with TeapotNet.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *************************************************************************/

#include "tpn/profile.h"
#include "tpn/user.h"
#include "tpn/config.h"
#include "tpn/file.h"
#include "tpn/directory.h"
#include "tpn/yamlserializer.h"
#include "tpn/jsonserializer.h"

namespace tpn
{

Profile::Profile(User *user, const String &uname, const String &tracker):
	mUser(user),
	mName(uname)
{
	Assert(mUser);
	if(mName.empty()) mName = mUser->name();
	if(mTracker.empty()) mTracker = Config::Get("tracker");

	Assert(!mName.empty());
	Assert(!mTracker.empty());

	// Status
	mFields["status"]      	= new TypedField<String>("status", &mStatus, "Status");

	// Avatar
	mFields["avatar"]      	= new TypedField<ByteString>("avatar", &mAvatar, "Avatar");

	// Basic Info
	mFields["realname"]	= new TypedField<String>("realname", &mRealName, "Name", "What's your name ?");
	mFields["birthday"]     = new TypedField<Time>("birthday", &mBirthday, "Birthday", "What's your birthday ?", Time(0));
	mFields["gender"]    	= new TypedField<String>("gender", &mGender, "Gender", "What's your gender ?");
	mFields["relationship"]	= new TypedField<String>("relationship", &mRelationship, "Relationship", "What's your relationship status ?");

	// Contact
	mFields["location"]    	= new TypedField<String>("location", &mLocation, "Address", "What's your address ?");
	mFields["email"]	= new TypedField<String>("email", &mEmail, "E-mail", "What's your email address ?");
	mFields["phone"]	= new TypedField<String>("phone", &mPhone, "Phone", "What's your phone number ?");

	// Settings
	mFields["tracker"]	= new TypedField<String>("tracker", &mTracker, "Tracker", "The teapotnet tracker you use", mTracker);

	mFileName = infoPath() + mName;
	load();

	Interface::Instance->add(urlPrefix(), this);
}

Profile::~Profile()
{
	for(    Map<String, Field*>::iterator it = mFields.begin();
                it != mFields.end();
                ++it)
        {
		delete it->second;
        }

	mFields.clear();

	Interface::Instance->remove(urlPrefix());
}

String Profile::name(void) const
{
	Synchronize(this);
	return mName;
}

String Profile::tracker(void) const
{
	Synchronize(this);
	return mTracker;
}

String Profile::urlPrefix(void) const
{
	Synchronize(this);

	// TODO: redirect /[user]/myself/profile
	if(mName == mUser->name()) return "/"+mUser->name()+"/profile";
	else return "/"+mUser->name()+"/contacts/"+mName+"/profile";
}

String Profile::infoPath(void) const
{
	Synchronize(this);

        String path = mUser->profilePath() + "infos";
        if(!Directory::Exist(path)) Directory::Create(path);
        return path + Directory::Separator;
}

void Profile::load()
{
	Synchronize(this);

	if(!File::Exist(mFileName)) return;
	File profileFile(mFileName, File::Read);
	YamlSerializer serializer(&profileFile);
	deserialize(serializer);
}

void Profile::save()
{
	Synchronize(this);

	String tmp;
	YamlSerializer serializer(&tmp);
	serialize(serializer);

	SafeWriteFile file(mFileName);
	file.write(tmp);
	file.close();
}

void Profile::send(const Identifier &identifier)
{
	Synchronize(this);

	String tmp;
	YamlSerializer serializer(&tmp);
	serialize(serializer);

	Notification notification(tmp);
	notification.setParameter("type", "profile");
	if(identifier != Identifier::Null) notification.send(identifier);
	else mUser->addressBook()->send(notification);
}

void Profile::clear()
{
	Synchronize(this);

	for(	Map<String, Field*>::iterator it = mFields.begin();
		it != mFields.end();
		++it)
	{
		it->second->clear();
	}

	// Do not call save() here
}

void Profile::serialize(Serializer &s) const
{
	Synchronize(this);

	Serializer::ConstObjectMapping fields;

	for(	Map<String, Field*>::const_iterator it = mFields.begin();
		it != mFields.end();
		++it)
	{
		if(!it->second->empty())
			fields[it->first] = it->second;
	}

	s.outputObject(fields);
}

bool Profile::deserialize(Serializer &s)
{
	Synchronize(this);

	Serializer::ObjectMapping fields;

	for(	Map<String, Field*>::const_iterator it = mFields.begin();
		it != mFields.end();
		++it)
	{
		fields[it->first] = it->second;
	}

	return s.inputObject(fields);
}

void Profile::http(const String &prefix, Http::Request &request)
{
	try {
		String url = request.url;
		if(url.empty() || url[0] != '/') throw 404;

		if(request.method == "POST")
		{
			if(request.post.contains("clear"))	// Should be removed
			{
				clear();
				save();
			}
			else {
				String field = request.post["field"];
				String value = request.post["value"];

				updateField(field, value);
			}

			Http::Response response(request, 303);
			response.headers["Location"] = prefix + "/";
			response.send();
			return;
		}

		if(url == "/")
		{
			Http::Response response(request,200);
			response.send();

			Html page(response.sock);
			page.header(APPNAME, true);

			try {
				page.header(name()+"'s profile");

				page.open("div","profile.box");

				page.open("h2");
				page.text("My personal information");
				page.close("h2");

				// button() should not be used outside of a form
				//page.button("clearprofilebutton", "Clear profile");

				page.open("div", "profileheader");

					page.open("div","personalstatus");
						page.span("“", ".statusquotemark");
						if(mStatus.empty())
						{
							page.open("span","status.empty");
							page.text("(click here to post a status)");
							page.close("span");
						}
						else {
							page.open("span","status.editable");
							page.text(mStatus);
							page.close("span");
						}
						page.span("”", ".statusquotemark");
					page.close("div");

					page.open("div", "profilephoto");

					// TODO : photo de profil

					page.close("div");

				page.close("div");

				page.open("h2");
				page.text("Personal Information");
				page.close("h2");

				displayField(page, "realname");
				displayField(page, "birthday");
				displayField(page, "gender");
				displayField(page, "relationship");
				displayField(page, "address");
				displayField(page, "email");
				displayField(page, "phone");

				page.javascript("function postField(field, value)\n\
					{\n\
						var request = $.post('"+prefix+"/profile"+"',\n\
							{ 'field': field , 'value': value });\n\
						request.fail(function(jqXHR, textStatus) {\n\
							alert('The profile update could not be made.');\n\
						});\n\
						setTimeout(function(){location.reload();},100);\n\
					}\n\
					var blocked = false;\n\
					$('.editable,.empty').click(function() {\n\
						if(blocked) return;\n\
						blocked = true;\n\
						var currentId = $(this).attr('id');\n\
						var currentText = $(this).html();\n\
						var value = (!$(this).hasClass('empty') ? currentText : '');\n\
						$(this).html('<input type=\"text\" value=\"'+value+'\" class=\"inputprofile\">');\n\
						$('input').focus();\n\
						$('input').keypress(function(e) {\n\
							if (e.keyCode == 13 && !e.shiftKey) {\n\
								e.preventDefault();\n\
								var field = currentId;\n\
								value = $('input[type=text]').attr('value');\n\
								postField(field, value);\n\
							}\n\
						});\n\
						$('input').blur(function() {\n\
							setTimeout(function(){location.reload();},100);\n\
						});\n\
					});\n\
					$('.clearprofilebutton').click(function() {\n\
						var request = $.post('"+prefix+"/profile"+"',\n\
							{ 'clear': 1 });\n\
						request.fail(function(jqXHR, textStatus) {\n\
							alert('Profile clear could not be made.');\n\
						});\n\
						setTimeout(function(){location.reload();},100);\n\
					});");
			}
			catch(const IOException &e)
			{
				LogWarn("Profile::http", e.what());
				return;
			}

			page.footer();
			return;
		}
	}
	catch(const Exception &e)
	{
		LogWarn("Profile::http", e.what());
		throw 404;	// Httpd handles integer exceptions
	}

	throw 404;
}

void Profile::displayField(Html &page, const String &name) const
{
	Synchronize(this);

	Field *field;
	if(mFields.get(name, field))
		displayField(page, field);
}

void Profile::displayField(Html &page, const Field *field) const
{
	Synchronize(this);

	if(!field->empty())
        {
                page.open("div", ".profileinfo");
                page.text(field->displayName() + ": ");
                page.open("span", field->name()+".editable");
                page.text(field->value());
                page.close("span");
                //page.br();
		page.close("div");
        }
        else {
		page.open("div", ".profileinfo");
                page.open("span", field->name()+".empty");
		page.text(field->comment());
                page.close("span");
                //page.br();
		page.close("div");
        }
}

void Profile::updateField(const String &name, const String &value)
{
	Synchronize(this);

	Field *field;
	if(mFields.get(name, field))
	{
		field->fromString(value);
		save();

		StringMap map;
		map[name] = value;

		String tmp;
		YamlSerializer serializer(&tmp);
		map.serialize(serializer);

		Notification notification(tmp);
		notification.setParameter("type", "profilediff");
		mUser->addressBook()->send(notification);
	}
}

}
<commit_msg>Better profile diff formatting<commit_after>/*************************************************************************
 *   Copyright (C) 2011-2012 by Paul-Louis Ageneau                       *
 *   paul-louis (at) ageneau (dot) org                                   *
 *                                                                       *
 *   This file is part of TeapotNet.                                     *
 *                                                                       *
 *   TeapotNet is free software: you can redistribute it and/or modify   *
 *   it under the terms of the GNU Affero General Public License as      *
 *   published by the Free Software Foundation, either version 3 of      *
 *   the License, or (at your option) any later version.                 *
 *                                                                       *
 *   TeapotNet 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 Affero General Public License for more details.                 *
 *                                                                       *
 *   You should have received a copy of the GNU Affero General Public    *
 *   License along with TeapotNet.                                       *
 *   If not, see <http://www.gnu.org/licenses/>.                         *
 *************************************************************************/

#include "tpn/profile.h"
#include "tpn/user.h"
#include "tpn/config.h"
#include "tpn/file.h"
#include "tpn/directory.h"
#include "tpn/yamlserializer.h"
#include "tpn/jsonserializer.h"

namespace tpn
{

Profile::Profile(User *user, const String &uname, const String &tracker):
	mUser(user),
	mName(uname)
{
	Assert(mUser);
	if(mName.empty()) mName = mUser->name();
	if(mTracker.empty()) mTracker = Config::Get("tracker");

	Assert(!mName.empty());
	Assert(!mTracker.empty());

	// Status
	mFields["status"]      	= new TypedField<String>("status", &mStatus, "Status");

	// Avatar
	mFields["avatar"]      	= new TypedField<ByteString>("avatar", &mAvatar, "Avatar");

	// Basic Info
	mFields["realname"]	= new TypedField<String>("realname", &mRealName, "Name", "What's your name ?");
	mFields["birthday"]     = new TypedField<Time>("birthday", &mBirthday, "Birthday", "What's your birthday ?", Time(0));
	mFields["gender"]    	= new TypedField<String>("gender", &mGender, "Gender", "What's your gender ?");
	mFields["relationship"]	= new TypedField<String>("relationship", &mRelationship, "Relationship", "What's your relationship status ?");

	// Contact
	mFields["location"]    	= new TypedField<String>("location", &mLocation, "Address", "What's your address ?");
	mFields["email"]	= new TypedField<String>("email", &mEmail, "E-mail", "What's your email address ?");
	mFields["phone"]	= new TypedField<String>("phone", &mPhone, "Phone", "What's your phone number ?");

	// Settings
	mFields["tracker"]	= new TypedField<String>("tracker", &mTracker, "Tracker", "The teapotnet tracker you use", mTracker);

	mFileName = infoPath() + mName;
	load();

	Interface::Instance->add(urlPrefix(), this);
}

Profile::~Profile()
{
	for(    Map<String, Field*>::iterator it = mFields.begin();
                it != mFields.end();
                ++it)
        {
		delete it->second;
        }

	mFields.clear();

	Interface::Instance->remove(urlPrefix());
}

String Profile::name(void) const
{
	Synchronize(this);
	return mName;
}

String Profile::tracker(void) const
{
	Synchronize(this);
	return mTracker;
}

String Profile::urlPrefix(void) const
{
	Synchronize(this);

	// TODO: redirect /[user]/myself/profile
	if(mName == mUser->name()) return "/"+mUser->name()+"/profile";
	else return "/"+mUser->name()+"/contacts/"+mName+"/profile";
}

String Profile::infoPath(void) const
{
	Synchronize(this);

        String path = mUser->profilePath() + "infos";
        if(!Directory::Exist(path)) Directory::Create(path);
        return path + Directory::Separator;
}

void Profile::load()
{
	Synchronize(this);

	if(!File::Exist(mFileName)) return;
	File profileFile(mFileName, File::Read);
	YamlSerializer serializer(&profileFile);
	deserialize(serializer);
}

void Profile::save()
{
	Synchronize(this);

	String tmp;
	YamlSerializer serializer(&tmp);
	serialize(serializer);

	SafeWriteFile file(mFileName);
	file.write(tmp);
	file.close();
}

void Profile::send(const Identifier &identifier)
{
	Synchronize(this);

	String tmp;
	YamlSerializer serializer(&tmp);
	serialize(serializer);

	Notification notification(tmp);
	notification.setParameter("type", "profile");
	if(identifier != Identifier::Null) notification.send(identifier);
	else mUser->addressBook()->send(notification);
}

void Profile::clear()
{
	Synchronize(this);

	for(	Map<String, Field*>::iterator it = mFields.begin();
		it != mFields.end();
		++it)
	{
		it->second->clear();
	}

	// Do not call save() here
}

void Profile::serialize(Serializer &s) const
{
	Synchronize(this);

	Serializer::ConstObjectMapping fields;

	for(	Map<String, Field*>::const_iterator it = mFields.begin();
		it != mFields.end();
		++it)
	{
		if(!it->second->empty())
			fields[it->first] = it->second;
	}

	s.outputObject(fields);
}

bool Profile::deserialize(Serializer &s)
{
	Synchronize(this);

	Serializer::ObjectMapping fields;

	for(	Map<String, Field*>::const_iterator it = mFields.begin();
		it != mFields.end();
		++it)
	{
		fields[it->first] = it->second;
	}

	return s.inputObject(fields);
}

void Profile::http(const String &prefix, Http::Request &request)
{
	try {
		String url = request.url;
		if(url.empty() || url[0] != '/') throw 404;

		if(request.method == "POST")
		{
			if(request.post.contains("clear"))	// Should be removed
			{
				clear();
				save();
			}
			else {
				String field = request.post["field"];
				String value = request.post["value"];

				updateField(field, value);
			}

			Http::Response response(request, 303);
			response.headers["Location"] = prefix + "/";
			response.send();
			return;
		}

		if(url == "/")
		{
			Http::Response response(request,200);
			response.send();

			Html page(response.sock);
			page.header(APPNAME, true);

			try {
				page.header(name()+"'s profile");

				page.open("div","profile.box");

				page.open("h2");
				page.text("My personal information");
				page.close("h2");

				// button() should not be used outside of a form
				//page.button("clearprofilebutton", "Clear profile");

				page.open("div", "profileheader");

					page.open("div","personalstatus");
						page.span("“", ".statusquotemark");
						if(mStatus.empty())
						{
							page.open("span","status.empty");
							page.text("(click here to post a status)");
							page.close("span");
						}
						else {
							page.open("span","status.editable");
							page.text(mStatus);
							page.close("span");
						}
						page.span("”", ".statusquotemark");
					page.close("div");

					page.open("div", "profilephoto");

					// TODO : photo de profil

					page.close("div");

				page.close("div");

				page.open("h2");
				page.text("Personal Information");
				page.close("h2");

				displayField(page, "realname");
				displayField(page, "birthday");
				displayField(page, "gender");
				displayField(page, "relationship");
				displayField(page, "address");
				displayField(page, "email");
				displayField(page, "phone");

				page.javascript("function postField(field, value)\n\
					{\n\
						var request = $.post('"+prefix+"/profile"+"',\n\
							{ 'field': field , 'value': value });\n\
						request.fail(function(jqXHR, textStatus) {\n\
							alert('The profile update could not be made.');\n\
						});\n\
						setTimeout(function(){location.reload();},100);\n\
					}\n\
					var blocked = false;\n\
					$('.editable,.empty').click(function() {\n\
						if(blocked) return;\n\
						blocked = true;\n\
						var currentId = $(this).attr('id');\n\
						var currentText = $(this).html();\n\
						var value = (!$(this).hasClass('empty') ? currentText : '');\n\
						$(this).html('<input type=\"text\" value=\"'+value+'\" class=\"inputprofile\">');\n\
						$('input').focus();\n\
						$('input').keypress(function(e) {\n\
							if (e.keyCode == 13 && !e.shiftKey) {\n\
								e.preventDefault();\n\
								var field = currentId;\n\
								value = $('input[type=text]').attr('value');\n\
								postField(field, value);\n\
							}\n\
						});\n\
						$('input').blur(function() {\n\
							setTimeout(function(){location.reload();},100);\n\
						});\n\
					});\n\
					$('.clearprofilebutton').click(function() {\n\
						var request = $.post('"+prefix+"/profile"+"',\n\
							{ 'clear': 1 });\n\
						request.fail(function(jqXHR, textStatus) {\n\
							alert('Profile clear could not be made.');\n\
						});\n\
						setTimeout(function(){location.reload();},100);\n\
					});");
			}
			catch(const IOException &e)
			{
				LogWarn("Profile::http", e.what());
				return;
			}

			page.footer();
			return;
		}
	}
	catch(const Exception &e)
	{
		LogWarn("Profile::http", e.what());
		throw 404;	// Httpd handles integer exceptions
	}

	throw 404;
}

void Profile::displayField(Html &page, const String &name) const
{
	Synchronize(this);

	Field *field;
	if(mFields.get(name, field))
		displayField(page, field);
}

void Profile::displayField(Html &page, const Field *field) const
{
	Synchronize(this);

	if(!field->empty())
        {
                page.open("div", ".profileinfo");
                page.text(field->displayName() + ": ");
                page.open("span", field->name()+".editable");
                page.text(field->value());
                page.close("span");
                //page.br();
		page.close("div");
        }
        else {
		page.open("div", ".profileinfo");
                page.open("span", field->name()+".empty");
		page.text(field->comment());
                page.close("span");
                //page.br();
		page.close("div");
        }
}

void Profile::updateField(const String &name, const String &value)
{
	Synchronize(this);

	Field *field;
	if(mFields.get(name, field))
	{
		field->fromString(value);
		save();

		SerializableMap<String, Field*> map;
		map[name] = field;

		String tmp;
		YamlSerializer serializer(&tmp);
		map.serialize(serializer);

		Notification notification(tmp);
		notification.setParameter("type", "profilediff");
		mUser->addressBook()->send(notification);
	}
}

}
<|endoftext|>


<commit_before>
#include <list>
#include <ctime>
#include <future>
#include <cassert>
#include <thread>
#include <chrono>
#include <iostream>
#include <unistd.h>

#include "global.h"
#include "state.h"

#include "util/hash.h"
#include "server/server_internal.h"
#include "partition/partition_table.h"

#include <edisense_comms.h>
#include <member.h>

#include "edisense_types.h"

#include "ble_client_internal.h"

static const std::chrono::milliseconds kPutRequestTimeOut(5000);

bool Put(edisense_comms::Member *member, device_t device_id, time_t timestamp, time_t expiration, void *data, size_t data_len)
{
	int num_replicas = g_cached_partition_table->getNumReplicas();
	int target_partition = g_cached_partition_table->getPartition(hash_integer(device_id));

	transaction_t tid = g_current_node_state->getTransactionID();

	g_cached_partition_table->lock.acquireRDLock(); // 1
	g_current_node_state->cluster_members_lock.acquireRDLock(); // 3

	node_t *partition_owners = g_cached_partition_table->getPartitionOwners(target_partition);
	bool success;
	std::list<std::string> partition_owners_hostnames;
	for (int i = 0; i < num_replicas; i++)
	{
		if (partition_owners[i] != g_current_node_id)
		{
			partition_owners_hostnames.push_back(g_current_node_state->cluster_members[partition_owners[i]]);
		}
		else // current node owns this partition too
		{
			g_current_node_state->partitions_owned_map_lock.acquireRDLock(); // 2
			if (g_current_node_state->partitions_owned_map[target_partition].db)
			{
				success = g_current_node_state->partitions_owned_map[target_partition]
					.db->put(device_id, timestamp, expiration, data, data_len);
			}
			else
			{
				success = false;
			}
			g_current_node_state->partitions_owned_map_lock.releaseRDLock(); // 2
		}
	}
	g_current_node_state->cluster_members_lock.releaseRDLock(); // 3

	if (!partition_owners_hostnames.empty())
	{
	unsigned char *charBuf = (unsigned char*)data;
	blob dataBlob(charBuf, charBuf + data_len);

	std::future<std::list<std::pair<std::string, PutResult>>> future_result
		= member->put(tid, partition_owners_hostnames, device_id, timestamp, expiration, dataBlob);
	g_cached_partition_table->lock.releaseRDLock(); // 1

	// wait on the future
	if (future_result.wait_for(kPutRequestTimeOut) != std::future_status::ready)
	{
		return false;
	}
	std::list<std::pair<std::string, PutResult>> results = future_result.get();

	// check results of put
	success &= results.size() == num_replicas;
	for (auto &kv: results)
	{
		success &= (kv.second.status == SUCCESS);
		assert(kv.second.status != DATA_NOT_OWNED); // this would mean we are fundamentally inconsistent
		if (kv.second.status == DATA_MOVED) // update location in partition table
		{
			g_cached_partition_table->lock.acquireWRLock(); // 4
			assert(g_cached_partition_table->updatePartitionOwner(hostToNodeId(kv.first), kv.second.moved_to, target_partition));
			g_cached_partition_table->lock.releaseWRLock(); // 4
		}
	}

	return success;
}

static const int kSecondsInDay = 60 * 60 * 24;
void SimulatePutDaemon(edisense_comms::Member *member, unsigned int freq, device_t device_id)
{
	assert(freq != 0);
	unsigned long long counter = 0; // we will use this as our data
	time_t timestamp;
	time(&timestamp);
	time_t expiration = timestamp + kSecondsInDay; // 1 day later
	while (true)
	{
		sleep(freq);
		if (Put(member, device_id, timestamp, expiration, &counter, sizeof(unsigned long long)))
		{
			std::cout << "Put successful: " << device_id << " (device) " << timestamp
				<< " (timestamp) " << expiration << " (expiry) " << counter << " (value)"
				<< std::endl;
			counter++;
			time_t new_timestamp;
			time(&new_timestamp);
			do
			{
				time(&new_timestamp);
			}
			while (new_timestamp <= timestamp); // make sure timestamps do not conflict
			timestamp = new_timestamp;
			expiration = timestamp + kSecondsInDay;
		}
		else
		{
			std::cout << "Put failed: " << device_id << " (device) " << timestamp
				<< " (timestamp) " << expiration << " (expiry) " << counter << " (value)"
				<< std::endl;
		}
	}
}
<commit_msg>Didn't save format changes<commit_after>
#include <list>
#include <ctime>
#include <future>
#include <cassert>
#include <thread>
#include <chrono>
#include <iostream>
#include <unistd.h>

#include "global.h"
#include "state.h"

#include "util/hash.h"
#include "server/server_internal.h"
#include "partition/partition_table.h"

#include <edisense_comms.h>
#include <member.h>

#include "edisense_types.h"

#include "ble_client_internal.h"

static const std::chrono::milliseconds kPutRequestTimeOut(5000);

bool Put(edisense_comms::Member *member, device_t device_id, time_t timestamp, time_t expiration, void *data, size_t data_len)
{
	int num_replicas = g_cached_partition_table->getNumReplicas();
	int target_partition = g_cached_partition_table->getPartition(hash_integer(device_id));

	transaction_t tid = g_current_node_state->getTransactionID();

	g_cached_partition_table->lock.acquireRDLock(); // 1
	g_current_node_state->cluster_members_lock.acquireRDLock(); // 3

	node_t *partition_owners = g_cached_partition_table->getPartitionOwners(target_partition);
	bool success;
	std::list<std::string> partition_owners_hostnames;
	for (int i = 0; i < num_replicas; i++)
	{
		if (partition_owners[i] != g_current_node_id)
		{
			partition_owners_hostnames.push_back(g_current_node_state->cluster_members[partition_owners[i]]);
		}
		else // current node owns this partition too
		{
			g_current_node_state->partitions_owned_map_lock.acquireRDLock(); // 2
			if (g_current_node_state->partitions_owned_map[target_partition].db)
			{
				success = g_current_node_state->partitions_owned_map[target_partition]
					.db->put(device_id, timestamp, expiration, data, data_len);
			}
			else
			{
				success = false;
			}
			g_current_node_state->partitions_owned_map_lock.releaseRDLock(); // 2
		}
	}
	g_current_node_state->cluster_members_lock.releaseRDLock(); // 3

	if (!partition_owners_hostnames.empty())
	{
		unsigned char *charBuf = (unsigned char*)data;
		blob dataBlob(charBuf, charBuf + data_len);

		std::future<std::list<std::pair<std::string, PutResult>>> future_result
			= member->put(tid, partition_owners_hostnames, device_id, timestamp, expiration, dataBlob);
		g_cached_partition_table->lock.releaseRDLock(); // 1

		// wait on the future
		if (future_result.wait_for(kPutRequestTimeOut) != std::future_status::ready)
		{
			return false;
		}
		std::list<std::pair<std::string, PutResult>> results = future_result.get();

		// check results of put
		success &= results.size() == num_replicas;
		for (auto &kv: results)
		{
			success &= (kv.second.status == SUCCESS);
			assert(kv.second.status != DATA_NOT_OWNED); // this would mean we are fundamentally inconsistent
			if (kv.second.status == DATA_MOVED) // update location in partition table
			{
				g_cached_partition_table->lock.acquireWRLock(); // 4
				assert(g_cached_partition_table->updatePartitionOwner(hostToNodeId(kv.first), kv.second.moved_to, target_partition));
				g_cached_partition_table->lock.releaseWRLock(); // 4
			}
		}
		return success;
	}
}

static const int kSecondsInDay = 60 * 60 * 24;
void SimulatePutDaemon(edisense_comms::Member *member, unsigned int freq, device_t device_id)
{
	assert(freq != 0);
	unsigned long long counter = 0; // we will use this as our data
	time_t timestamp;
	time(&timestamp);
	time_t expiration = timestamp + kSecondsInDay; // 1 day later
	while (true)
	{
		sleep(freq);
		if (Put(member, device_id, timestamp, expiration, &counter, sizeof(unsigned long long)))
		{
			std::cout << "Put successful: " << device_id << " (device) " << timestamp
				<< " (timestamp) " << expiration << " (expiry) " << counter << " (value)"
				<< std::endl;
			counter++;
			time_t new_timestamp;
			time(&new_timestamp);
			do
			{
				time(&new_timestamp);
			}
			while (new_timestamp <= timestamp); // make sure timestamps do not conflict
			timestamp = new_timestamp;
			expiration = timestamp + kSecondsInDay;
		}
		else
		{
			std::cout << "Put failed: " << device_id << " (device) " << timestamp
				<< " (timestamp) " << expiration << " (expiry) " << counter << " (value)"
				<< std::endl;
		}
	}
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2016-2019 The Falco Authors

This file is part of falco.

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

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

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

#include <iostream>

#ifdef GRPC_INCLUDE_IS_GRPCPP
#include <grpcpp/grpcpp.h>
#else
#include <grpc++/grpc++.h>
#endif

#include "grpc_server.h"
#include "grpc_context.h"

template<>
void request_stream_context<falco_output_request, falco_output_response>::start(falco_grpc_server* srv)
{
	m_state = request_context_base::REQUEST;
	m_srv_ctx.reset(new grpc::ServerContext);
	auto srvctx = m_srv_ctx.get();
	m_res_writer.reset(new grpc::ServerAsyncWriter<falco_output_response>(srvctx));
	m_stream_ctx.reset();
	m_req.Clear();

	auto cq = srv->m_completion_queue.get();
	(srv->m_svc.*m_request_func)(srvctx, &m_req, m_res_writer.get(), cq, cq, this);
}

template<>
void request_stream_context<falco_output_request, falco_output_response>::process(falco_grpc_server* srv)
{
}

template<>
void request_stream_context<falco_output_request, falco_output_response>::end(falco_grpc_server* srv, bool isError)
{
}

bool falco_grpc_server_impl::is_running()
{
	// TODO: this must act as a switch to shut down the server
	return true;
}

void falco_grpc_server_impl::subscribe(const stream_context& ctx, const falco_output_request& req, falco_output_response& res)
{
	if(ctx.m_status == stream_context::SUCCESS || ctx.m_status == stream_context::ERROR)
	{
		// todo > logic

		ctx.m_stream = nullptr;
	}
	else
	{
		// Start (or continue) streaming
		// ctx.m_status == stream_context::STREAMING
	}

	// todo > print/store statistics
}

void falco_grpc_server::thread_process(int thread_index)
{
	// TODO: is this right? That's what we want?
	// Tell pthread to not handle termination signals in the current thread
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGHUP);
	sigaddset(&set, SIGINT);
	pthread_sigmask(SIG_BLOCK, &set, nullptr);

	void* tag = nullptr;
	bool event_read_success = false;
	while(m_completion_queue->Next(&tag, &event_read_success))
	{
		if(tag == nullptr)
		{
			// TODO: empty tag returned, log, what to do?
			continue;
		}
	}
}

//
// Create array of contexts and start processing streaming RPC request.
//
#define PROCESS_STREAM(REQ, RESP, RPC, IMPL, CONTEXT_COUNT)                             \
	std::vector<request_stream_context<REQ, RESP>> RPC##_contexts(CONTEXT_COUNT);   \
	for(request_stream_context<REQ, RESP> & ctx : RPC##_contexts)                   \
	{                                                                               \
		ctx.m_process_func = &falco_grpc_server::IMPL;                          \
		ctx.m_request_func = &falco_output_service::AsyncService::Request##RPC; \
		ctx.start(this);                                                        \
	}

void falco_grpc_server::run()
{
	grpc::ServerBuilder builder;
	// Listen on the given address without any authentication mechanism.
	builder.AddListeningPort(m_server_addr, grpc::InsecureServerCredentials());
	// builder.RegisterService(&falco_output_svc); // TODO: enable this when we do the impl

	m_completion_queue = builder.AddCompletionQueue();
	m_server = builder.BuildAndStart();
	std::cout << "Server listening on " << m_server_addr << std::endl;

	int context_count = m_threadiness * 10;
	PROCESS_STREAM(falco_output_request, falco_output_response, subscribe, subscribe, context_count)

	m_threads.resize(m_threadiness);
	int thread_idx = 0;
	for(std::thread& thread : m_threads)
	{
		thread = std::thread(&falco_grpc_server::thread_process, this, thread_idx++);
	}

	while(is_running())
	{
	}
}

void start_grpc_server(std::string server_address, int threadiness)
{
	falco_grpc_server srv(server_address, threadiness);
	srv.run();
}<commit_msg>new(userspace/falco): request stream context process and end handling<commit_after>/*
Copyright (C) 2016-2019 The Falco Authors

This file is part of falco.

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

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

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

#include <iostream>

#ifdef GRPC_INCLUDE_IS_GRPCPP
#include <grpcpp/grpcpp.h>
#else
#include <grpc++/grpc++.h>
#endif

#include "grpc_server.h"
#include "grpc_context.h"

template<>
void request_stream_context<falco_output_request, falco_output_response>::start(falco_grpc_server* srv)
{
	m_state = request_context_base::REQUEST;
	m_srv_ctx.reset(new grpc::ServerContext);
	auto srvctx = m_srv_ctx.get();
	m_res_writer.reset(new grpc::ServerAsyncWriter<falco_output_response>(srvctx));
	m_stream_ctx.reset();
	m_req.Clear();

	auto cq = srv->m_completion_queue.get();
	(srv->m_svc.*m_request_func)(srvctx, &m_req, m_res_writer.get(), cq, cq, this);
}

template<>
void request_stream_context<falco_output_request, falco_output_response>::process(falco_grpc_server* srv)
{
	// When it is the 1st process call
	if(m_state == request_context_base::REQUEST)
	{
		m_state = request_context_base::WRITE;
		m_stream_ctx.reset(new stream_context(m_srv_ctx.get()));
	}

	// Processing
	falco_output_response res;
	(srv->*m_process_func)(*m_stream_ctx, m_req, res);

	// When there still are more responses to stream
	if(m_stream_ctx->m_has_more)
	{
		m_res_writer->Write(res, this);
	}
	// No more responses to stream
	else
	{
		// Communicate to the gRPC runtime that we have finished.
		// The memory address of `this` instance uniquely identifies the event.
		m_state = request_context_base::FINISH;
		m_res_writer->Finish(grpc::Status::OK, this);
	}
}

template<>
void request_stream_context<falco_output_request, falco_output_response>::end(falco_grpc_server* srv, bool isError)
{
	if(m_stream_ctx)
	{
		m_stream_ctx->m_status = stream_context::SUCCESS;
		if(isError)
		{
			m_stream_ctx->m_status = stream_context::ERROR;
			// todo > log error
		}

		// Complete the processing
		falco_output_response res;
		(srv->*m_process_func)(*m_stream_ctx, m_req, res);
	}
	else
	{
		// Handle the edge case when `m_request_func` event failed
		// which means `m_stream_ctx` was not set
		// todo > log error
	}

	start(srv);
}

bool falco_grpc_server_impl::is_running()
{
	// TODO: this must act as a switch to shut down the server
	return true;
}

void falco_grpc_server_impl::subscribe(const stream_context& ctx, const falco_output_request& req, falco_output_response& res)
{
	if(ctx.m_status == stream_context::SUCCESS || ctx.m_status == stream_context::ERROR)
	{
		// todo > logic

		ctx.m_stream = nullptr;
	}
	else
	{
		// Start (or continue) streaming
		// ctx.m_status == stream_context::STREAMING
	}

	// todo > print/store statistics
}

void falco_grpc_server::thread_process(int thread_index)
{
	// TODO: is this right? That's what we want?
	// Tell pthread to not handle termination signals in the current thread
	sigset_t set;
	sigemptyset(&set);
	sigaddset(&set, SIGHUP);
	sigaddset(&set, SIGINT);
	pthread_sigmask(SIG_BLOCK, &set, nullptr);

	void* tag = nullptr;
	bool event_read_success = false;
	while(m_completion_queue->Next(&tag, &event_read_success))
	{
		if(tag == nullptr)
		{
			// TODO: empty tag returned, log, what to do?
			continue;
		}
	}
}

//
// Create array of contexts and start processing streaming RPC request.
//
#define PROCESS_STREAM(REQ, RESP, RPC, IMPL, CONTEXT_COUNT)                             \
	std::vector<request_stream_context<REQ, RESP>> RPC##_contexts(CONTEXT_COUNT);   \
	for(request_stream_context<REQ, RESP> & ctx : RPC##_contexts)                   \
	{                                                                               \
		ctx.m_process_func = &falco_grpc_server::IMPL;                          \
		ctx.m_request_func = &falco_output_service::AsyncService::Request##RPC; \
		ctx.start(this);                                                        \
	}

void falco_grpc_server::run()
{
	grpc::ServerBuilder builder;
	// Listen on the given address without any authentication mechanism.
	builder.AddListeningPort(m_server_addr, grpc::InsecureServerCredentials());
	// builder.RegisterService(&falco_output_svc); // TODO: enable this when we do the impl

	m_completion_queue = builder.AddCompletionQueue();
	m_server = builder.BuildAndStart();
	std::cout << "Server listening on " << m_server_addr << std::endl;

	int context_count = m_threadiness * 10;
	PROCESS_STREAM(falco_output_request, falco_output_response, subscribe, subscribe, context_count)

	m_threads.resize(m_threadiness);
	int thread_idx = 0;
	for(std::thread& thread : m_threads)
	{
		thread = std::thread(&falco_grpc_server::thread_process, this, thread_idx++);
	}

	while(is_running())
	{
	}
}

void start_grpc_server(std::string server_address, int threadiness)
{
	falco_grpc_server srv(server_address, threadiness);
	srv.run();
}<|endoftext|>


<commit_before>/****************************************************************************
 *   Copyright (C) 2013 by Savoir-Faire Linux                               *
 *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 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      *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU General Public License      *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 ***************************************************************************/
#include "categorizedaccountmodel.h"

#include <accountlistmodel.h>

CategorizedAccountModel* CategorizedAccountModel::m_spInstance = nullptr;

CategorizedAccountModel* CategorizedAccountModel::instance()
{
   if (!m_spInstance)
      m_spInstance = new CategorizedAccountModel();
   return m_spInstance;
}

CategorizedAccountModel::CategorizedAccountModel(QObject* parent) : QAbstractItemModel(parent)
{
   connect(AccountListModel::instance(),SIGNAL(dataChanged(QModelIndex,QModelIndex)),this,SLOT(slotDataChanged(QModelIndex,QModelIndex)));
   connect(AccountListModel::instance(),SIGNAL(layoutChanged()),this,SLOT(slotLayoutchanged()));
}

CategorizedAccountModel::~CategorizedAccountModel()
{

}

QModelIndex CategorizedAccountModel::mapToSource(const QModelIndex& idx) const
{
   if (!idx.isValid() || !idx.parent().isValid())
      return QModelIndex();
   switch (idx.parent().row()) {
      case Categories::IP2IP:
         return AccountListModel::instance()->index(0,0);
         break;
      case Categories::SERVER:
         return AccountListModel::instance()->index(idx.row()+1,0);
         break;
   };
   return QModelIndex();
}

QVariant CategorizedAccountModel::data(const QModelIndex& index, int role ) const
{
   if (!index.isValid())
      return QVariant();
   else if (index.parent().isValid()) {
      return mapToSource(index).data(role);
   }
   else {
      switch (role) {
         case Qt::DisplayRole:
            if (index.row() == Categories::IP2IP)
               return tr("Peer to peer");
            else
               return tr("Server");
      };
   }
   return QVariant();
}

int CategorizedAccountModel::rowCount(const QModelIndex& parent ) const
{
   if (parent.parent().isValid())
      return 0;
   else if (parent.isValid()) {
      if (parent.row() == 1)
         return 1;
      return AccountListModel::instance()->size()-1;
   }
   return 2;
}

int CategorizedAccountModel::columnCount(const QModelIndex& parent ) const
{
   Q_UNUSED(parent)
   return 1;
}

QModelIndex CategorizedAccountModel::parent(const QModelIndex& idx) const
{
   switch (idx.internalId()) {
      case 0:
         return QModelIndex();
      case 1:
         return createIndex((int)Categories::SERVER,0,0);
      case 2:
         return createIndex((int)Categories::IP2IP,0,0);
   };
   return QModelIndex();
}

QModelIndex CategorizedAccountModel::index( int row, int column, const QModelIndex& parent ) const
{
   if (parent.isValid() && parent.internalId() == 0) {
      if (row >= rowCount(parent))
         return QModelIndex();
      switch (parent.row()) {
         case Categories::SERVER:
            return createIndex(row,column,1);
            break;
         case Categories::IP2IP:
            return createIndex(row,column,2);
            break;
      };
   }
   else if (parent.isValid())
      return QModelIndex();
   return createIndex(row,column,0);
}

Qt::ItemFlags CategorizedAccountModel::flags(const QModelIndex& index ) const
{
   if (index.parent().isValid())
      return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
   return Qt::ItemIsEnabled;
}

bool CategorizedAccountModel::setData(const QModelIndex& index, const QVariant &value, int role )
{
   if (!index.isValid())
      return false;
   else if (index.parent().isValid()) {
      return AccountListModel::instance()->setData(mapToSource(index),value,role);
   }
   return false;
}

QVariant CategorizedAccountModel::headerData(int section, Qt::Orientation orientation, int role ) const
{
   Q_UNUSED(section)
   Q_UNUSED(orientation)
   if (role == Qt::DisplayRole) return tr("Accounts");
   return QVariant();
}

void CategorizedAccountModel::slotDataChanged(const QModelIndex& tl,const QModelIndex& br)
{
   Q_UNUSED(tl)
   Q_UNUSED(br)
   emit layoutChanged();
}

void CategorizedAccountModel::slotLayoutchanged()
{
   emit layoutChanged();
}
<commit_msg>Fix build.kde.org build<commit_after>/****************************************************************************
 *   Copyright (C) 2013 by Savoir-Faire Linux                               *
 *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> *
 *                                                                          *
 *   This library is free software; you can redistribute it and/or          *
 *   modify it under the terms of the GNU Lesser General Public             *
 *   License as published by the Free Software Foundation; either           *
 *   version 2.1 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      *
 *   Lesser General Public License for more details.                        *
 *                                                                          *
 *   You should have received a copy of the GNU General Public License      *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.  *
 ***************************************************************************/
#include "categorizedaccountmodel.h"

#include "accountlistmodel.h"

CategorizedAccountModel* CategorizedAccountModel::m_spInstance = nullptr;

CategorizedAccountModel* CategorizedAccountModel::instance()
{
   if (!m_spInstance)
      m_spInstance = new CategorizedAccountModel();
   return m_spInstance;
}

CategorizedAccountModel::CategorizedAccountModel(QObject* parent) : QAbstractItemModel(parent)
{
   connect(AccountListModel::instance(),SIGNAL(dataChanged(QModelIndex,QModelIndex)),this,SLOT(slotDataChanged(QModelIndex,QModelIndex)));
   connect(AccountListModel::instance(),SIGNAL(layoutChanged()),this,SLOT(slotLayoutchanged()));
}

CategorizedAccountModel::~CategorizedAccountModel()
{

}

QModelIndex CategorizedAccountModel::mapToSource(const QModelIndex& idx) const
{
   if (!idx.isValid() || !idx.parent().isValid())
      return QModelIndex();
   switch (idx.parent().row()) {
      case Categories::IP2IP:
         return AccountListModel::instance()->index(0,0);
         break;
      case Categories::SERVER:
         return AccountListModel::instance()->index(idx.row()+1,0);
         break;
   };
   return QModelIndex();
}

QVariant CategorizedAccountModel::data(const QModelIndex& index, int role ) const
{
   if (!index.isValid())
      return QVariant();
   else if (index.parent().isValid()) {
      return mapToSource(index).data(role);
   }
   else {
      switch (role) {
         case Qt::DisplayRole:
            if (index.row() == Categories::IP2IP)
               return tr("Peer to peer");
            else
               return tr("Server");
      };
   }
   return QVariant();
}

int CategorizedAccountModel::rowCount(const QModelIndex& parent ) const
{
   if (parent.parent().isValid())
      return 0;
   else if (parent.isValid()) {
      if (parent.row() == 1)
         return 1;
      return AccountListModel::instance()->size()-1;
   }
   return 2;
}

int CategorizedAccountModel::columnCount(const QModelIndex& parent ) const
{
   Q_UNUSED(parent)
   return 1;
}

QModelIndex CategorizedAccountModel::parent(const QModelIndex& idx) const
{
   switch (idx.internalId()) {
      case 0:
         return QModelIndex();
      case 1:
         return createIndex((int)Categories::SERVER,0,0);
      case 2:
         return createIndex((int)Categories::IP2IP,0,0);
   };
   return QModelIndex();
}

QModelIndex CategorizedAccountModel::index( int row, int column, const QModelIndex& parent ) const
{
   if (parent.isValid() && parent.internalId() == 0) {
      if (row >= rowCount(parent))
         return QModelIndex();
      switch (parent.row()) {
         case Categories::SERVER:
            return createIndex(row,column,1);
            break;
         case Categories::IP2IP:
            return createIndex(row,column,2);
            break;
      };
   }
   else if (parent.isValid())
      return QModelIndex();
   return createIndex(row,column,0);
}

Qt::ItemFlags CategorizedAccountModel::flags(const QModelIndex& index ) const
{
   if (index.parent().isValid())
      return QAbstractItemModel::flags(index) | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
   return Qt::ItemIsEnabled;
}

bool CategorizedAccountModel::setData(const QModelIndex& index, const QVariant &value, int role )
{
   if (!index.isValid())
      return false;
   else if (index.parent().isValid()) {
      return AccountListModel::instance()->setData(mapToSource(index),value,role);
   }
   return false;
}

QVariant CategorizedAccountModel::headerData(int section, Qt::Orientation orientation, int role ) const
{
   Q_UNUSED(section)
   Q_UNUSED(orientation)
   if (role == Qt::DisplayRole) return tr("Accounts");
   return QVariant();
}

void CategorizedAccountModel::slotDataChanged(const QModelIndex& tl,const QModelIndex& br)
{
   Q_UNUSED(tl)
   Q_UNUSED(br)
   emit layoutChanged();
}

void CategorizedAccountModel::slotLayoutchanged()
{
   emit layoutChanged();
}
<|endoftext|>


<commit_before>/*
 *  Game.cpp
 *  OpenLieroX
 *
 *  Created by Albert Zeyer on 09.12.09.
 *  code under LGPL
 *
 */

#include "Game.h"
#include "AuxLib.h"
#include "Debug.h"
#include "LieroX.h"
#include "GfxPrimitives.h"
#include "Entity.h"
#include "EventQueue.h"
#include "InputEvents.h"
#include "DedicatedControl.h"
#include "CrashHandler.h"
#include "Timer.h"
#include "NewNetEngine.h"
#include "OLXCommand.h"
#include "IRC.h"
#include "CClient.h"
#include "CServer.h"
#include "Physics.h"
#include "DeprecatedGUI/Menu.h"
#include "Cache.h"
#include "gusanos/gusanos.h"
#include "gusanos/gusgame.h"
#include "gusanos/ninjarope.h"
#include "game/WormInputHandler.h"
#include "CWormHuman.h"
#include "gusanos/glua.h"
#include "gusanos/lua51/luaapi/context.h"
#include "sound/sfx.h"
#include "gusanos/network.h"
#include "OLXConsole.h"

#include <boost/shared_ptr.hpp>

Game game;

static bool inMainGameLoop = false;
static std::string quitEngineFlagReason;

void Game::prepareGameloop() {
	// Pre-game initialization
	if(!bDedicated) FillSurface(VideoPostProcessor::videoSurface(), tLX->clBlack);

	while(cClient->getStatus() != NET_CONNECTED) {
		notes << "client not connected yet - waiting" << endl;
		SDL_Delay(10);
		SyncServerAndClient();
	}

	if(cServer->getState() == SVS_LOBBY && tLX->iGameType != GME_JOIN) {
		notes << "prepareGameloop: starting game" << endl;
		std::string errMsg;
		if(!cServer->StartGame(&errMsg)) {
			errors << "starting game in local game failed for reason: " << errMsg << endl;
			DeprecatedGUI::Menu_MessageBox("Error", "Error while starting game: " + errMsg);
			if (tLX->iGameType == GME_LOCAL)
				GotoLocalMenu();
			else
				GotoNetMenu();
			return;
		}
	}
	else
		warnings << "prepareGameloop: server was not in lobby" << endl;

	// we need the gamescript in physics init
	while(gameScript() == NULL) {
		notes << "gamescript not loaded yet - waiting" << endl;
		SDL_Delay(10);
		SyncServerAndClient();
	}

	// always also load Gusanos engine
	if( /*gameScript()->gusEngineUsed() && */ !gusGame.level().gusIsLoaded() ) {
		// WARNING: This may be temporary
		// Right now, we load the gus mod in the map loader (gusGame.changeLevel).
		// Thus, when we don't load a gus level, we must load the mod manually.

		if(!gameScript()->gusEngineUsed())
			gusGame.setMod(gusGame.getDefaultPath());
		gusGame.loadModWithoutMap();
	}

	PhysicsEngine::Init();

	ClearEntities();

	ProcessEvents();
	notes << "MaxFPS is " << tLXOptions->nMaxFPS << endl;

	//cCache.ClearExtraEntries(); // Do not clear anything before game started, it may be slow

	notes << "GameLoopStart" << endl;
	inMainGameLoop = true;
	if( DedicatedControl::Get() )
		DedicatedControl::Get()->GameLoopStart_Signal();

	CrashHandler::recoverAfterCrash = tLXOptions->bRecoverAfterCrash && GetGameVersion().releasetype == Version::RT_NORMAL;

	ResetQuitEngineFlag();
	oldtime = GetTime();
}

void Game::frameOuter() {
	tLX->currentTime = GetTime();
	SetCrashHandlerReturnPoint("main game loop");

	// Timing
	tLX->fDeltaTime = tLX->currentTime - oldtime;
	tLX->fRealDeltaTime = tLX->fDeltaTime;
	oldtime = tLX->currentTime;

	// cap the delta
	if(tLX->fDeltaTime.seconds() > 0.5f) {
		warnings << "deltatime " << tLX->fDeltaTime.seconds() << " is too high" << endl;
		// only if not in new net mode because it would screw up the gamestate there
		if(!NewNet::Active())
			tLX->fDeltaTime = 0.5f; // don't simulate more than 500ms, it could crash the game
	}

	ProcessEvents();

	// Main frame
	frameInner();

	doVideoFrameInMainThread();
	CapFPS();
}

///////////////////
// Game loop
void Game::frameInner()
{
	HandlePendingCommands();

	if(bDedicated)
		DedicatedControl::Get()->GameLoop_Frame();

    if(tLX->bQuitEngine)
        return;

	// Check if user pressed screenshot key
	if (tLX->cTakeScreenshot.isDownOnce())  {
		PushScreenshot("scrshots", "");
	}

	// Switch between window and fullscreen mode
	// Switch only if delta time is low enough. This is because when the game does not
	// respond for >30secs and the user presses cSwitchMode in the meantime, the mainlock-detector
	// would switch to window and here we would switch again to fullscreen which is stupid.
	if( tLX->cSwitchMode.isUp() && tLX && tLX->fRealDeltaTime < 1.0f )  {
		// Set to fullscreen
		tLXOptions->bFullscreen = !tLXOptions->bFullscreen;

		// Set the new video mode
		doSetVideoModeInMainThread();

		tLX->cSwitchMode.reset();
	}

#ifdef WITH_G15
	if (OLXG15)
		OLXG15->gameFrame();
#endif //WITH_G15

	if(tLXOptions->bEnableChat)
		ProcessIRC();

	//if(gusGame.isEngineNeeded()) {
	// do lua frames in all cases
	{
		// convert speed to lua if needed
		boost::shared_ptr<CGameObject::ScopedGusCompatibleSpeed> scopedSpeeds[MAX_WORMS];
		for(int i = 0; i < MAX_WORMS; ++i)
			if(cClient->getRemoteWorms()[i].isUsed())
				scopedSpeeds[i].reset( new CGameObject::ScopedGusCompatibleSpeed(cClient->getRemoteWorms()[i]) );

		gusLogicFrame();

		if(gameScript()->gusEngineUsed()) {
			// copy worm states to CServer worms because they are never updated in this mode
			for(int i = 0; i < MAX_WORMS; ++i) {
				if(cServer->getWorms()[i].isUsed() && cClient->getRemoteWorms()[i].isUsed()) {
					cServer->getWorms()[i].health = cClient->getRemoteWorms()[i].health;
					cServer->getWorms()[i].vPos = cClient->getRemoteWorms()[i].vPos;
					cServer->getWorms()[i].vVelocity = cClient->getRemoteWorms()[i].vVelocity;
					cServer->getWorms()[i].bAlive = cClient->getRemoteWorms()[i].bAlive;
				}
			}
		}
	}
	/*} else {
		// do stuff here which we took from Gusanos, which is done in gusLogicFrame and should be done in any case
		sfx.think();
	}*/

	// Local
	switch (tLX->iGameType)  {
		case GME_LOCAL:
			cClient->Frame();
			cServer->Frame();

			if(tLX && !tLX->bQuitEngine)
				cClient->Draw(VideoPostProcessor::videoSurface());
			break;

			// Hosting
		case GME_HOST:
			cClient->Frame();
			cServer->Frame();

			if(tLX && !tLX->bQuitEngine)
				cClient->Draw(VideoPostProcessor::videoSurface());
			break;

			// Joined
		case GME_JOIN:
			cClient->Frame();
			if(tLX && !tLX->bQuitEngine)
				cClient->Draw(VideoPostProcessor::videoSurface());
			break;

	} // SWITCH

	cClient->resetDebugStr();

	EnableSystemMouseCursor(false);
}

void Game::cleanupAfterGameloopEnd() {
	CrashHandler::recoverAfterCrash = false;

	gusGame.reset(GusGame::ServerQuit);

	PhysicsEngine::UnInit();

	notes << "GameLoopEnd: " << quitEngineFlagReason << endl;
	inMainGameLoop = false;
	if( DedicatedControl::Get() )
		DedicatedControl::Get()->GameLoopEnd_Signal();

	cCache.ClearExtraEntries(); // Game ended - clear cache
}

void ResetQuitEngineFlag() {
	tLX->bQuitEngine = false;
}

void SetQuitEngineFlag(const std::string& reason) {
	Warning_QuitEngineFlagSet("SetQuitEngineFlag(" + reason + "): ");
	quitEngineFlagReason = reason;
	tLX->bQuitEngine = true;
	// If we call this from within the menu, the menu should shutdown.
	// It will be restarted then in the next frame.
	// If we are not in the menu (i.e. in maingameloop), this has no
	// effect as we set it to true in Menu_Start().
	if(DeprecatedGUI::tMenu)
		DeprecatedGUI::tMenu->bMenuRunning = false;
	// If we were in menu, because we forced the menu restart above,
	// we must set this, otherwise OLX would quit (because of current maingamelogic).
	if(DeprecatedGUI::bGame)
		*DeprecatedGUI::bGame = true;
}

bool Warning_QuitEngineFlagSet(const std::string& preText) {
	if(tLX->bQuitEngine) {
		hints << preText << endl;
		warnings << "bQuitEngine is set because: " << quitEngineFlagReason << endl;
		return true;
	}
	return false;
}

void Game::onNewWorm(CWorm* w) {
	//if(!game.gameScript()->gusEngineUsed()) return;

#ifdef USE_GRID
	objects.insertImmediately(w, Grid::WormColLayer, Grid::WormRenderLayer);
	if(w->getNinjaRopeObj()) objects.insertImmediately(w->getNinjaRopeObj(), 1, 1);
#else
	objects.insert(WORMS_COLLISION_LAYER,WORMS_RENDER_LAYER, w);
	if(w->getNinjaRopeObj()) objects.insert( 1,1, (CGameObject*)w->getNinjaRopeObj() );
#endif
}

void Game::onRemoveWorm(CWorm* w) {
/*#ifdef USE_GRID
	objects.(w, Grid::WormColLayer, Grid::WormRenderLayer);
	if(w->getNinjaRopeObj()) objects.insertImmediately(w->getNinjaRopeObj(), 1, 1);
#else
	objects.insert(WORMS_COLLISION_LAYER,WORMS_RENDER_LAYER, w);
	if(w->getNinjaRopeObj()) objects.insert( 1,1, (CGameObject*)w->getNinjaRopeObj() );
#endif	*/
}

void Game::onNewPlayer(CWormInputHandler* player) {
	players.push_back( player );
}

void Game::onNewPlayer_Lua(CWormInputHandler* p) {
	if(game.gameScript()->gusEngineUsed()) {
		EACH_CALLBACK(i, playerInit)
		{
			(lua.call(*i), p->getLuaReference())();
		}
	}
}

void Game::onNewHumanPlayer(CWormHumanInputHandler* player) {
	localPlayers.push_back( player );
	player->local = true;
}

void Game::onNewHumanPlayer_Lua(CWormHumanInputHandler* player) {
	if(game.gameScript()->gusEngineUsed()) {
		EACH_CALLBACK(i, localplayerInit)
		{
			(lua.call(*i), player->getLuaReference())();
		}
	}
}

void Game::reset() {
	// Delete all players
	for ( std::list<CWormInputHandler*>::iterator iter = players.begin(); iter != players.end(); ++iter)
	{
		// handled by CClient for now
		//(*iter)->deleteThis();
	}
	players.clear();
	localPlayers.clear();

	// Delete all objects
#ifdef USE_GRID
	objects.clear();
#else
	for ( ObjectsList::Iterator iter = game.objects.begin(); (bool)iter; ++iter)
	{
		(*iter)->deleteThis();
	}
	objects.clear();
#endif
}

CGameScript* Game::gameScript() {
	if(tLX) {
		if(tLX->iGameType == GME_JOIN) return cClient->getGameScript().get();
		return cServer->getGameScript().get();
	}
	return NULL;
}

CGameMode* Game::gameMode() {
	if(tLX) {
		if(tLX->iGameType != GME_JOIN) return cServer->getGameMode();
		return cClient->getGameLobby()->gameMode;
	}
	return NULL;
}

bool Game::isServer() {
	return tLX->iGameType != GME_JOIN;
}

bool Game::needProxyWormInputHandler() {
	return isServer();
}

bool Game::needToCreateOwnWormInputHandlers() {
	return isServer() || (cClient->getServerVersion() < OLXBetaVersion(0,59,1));
}

bool Game::isTeamPlay() {
	return cClient->isTeamGame();
}

bool CClient::getGamePaused() {
	return (tLX->iGameType == GME_LOCAL) && (bGameOver || bViewportMgr || bGameMenu || Con_IsVisible());
}

bool Game::isGamePaused() {
	// If we're in a menu & a local game, don't do simulation
	return cClient->getGamePaused();
}

bool Game::shouldDoPhysicsFrame() {
	return !isGamePaused() && cClient->canSimulate() &&
    // We stop a few seconds after the actual game over
	!(cClient->bGameOver && (tLX->currentTime - cClient->fGameOverTime).seconds() > GAMEOVER_WAIT);
}

<commit_msg>fix: dont sync gus worms in client<commit_after>/*
 *  Game.cpp
 *  OpenLieroX
 *
 *  Created by Albert Zeyer on 09.12.09.
 *  code under LGPL
 *
 */

#include "Game.h"
#include "AuxLib.h"
#include "Debug.h"
#include "LieroX.h"
#include "GfxPrimitives.h"
#include "Entity.h"
#include "EventQueue.h"
#include "InputEvents.h"
#include "DedicatedControl.h"
#include "CrashHandler.h"
#include "Timer.h"
#include "NewNetEngine.h"
#include "OLXCommand.h"
#include "IRC.h"
#include "CClient.h"
#include "CServer.h"
#include "Physics.h"
#include "DeprecatedGUI/Menu.h"
#include "Cache.h"
#include "gusanos/gusanos.h"
#include "gusanos/gusgame.h"
#include "gusanos/ninjarope.h"
#include "game/WormInputHandler.h"
#include "CWormHuman.h"
#include "gusanos/glua.h"
#include "gusanos/lua51/luaapi/context.h"
#include "sound/sfx.h"
#include "gusanos/network.h"
#include "OLXConsole.h"

#include <boost/shared_ptr.hpp>

Game game;

static bool inMainGameLoop = false;
static std::string quitEngineFlagReason;

void Game::prepareGameloop() {
	// Pre-game initialization
	if(!bDedicated) FillSurface(VideoPostProcessor::videoSurface(), tLX->clBlack);

	while(cClient->getStatus() != NET_CONNECTED) {
		notes << "client not connected yet - waiting" << endl;
		SDL_Delay(10);
		SyncServerAndClient();
	}

	if(cServer->getState() == SVS_LOBBY && tLX->iGameType != GME_JOIN) {
		notes << "prepareGameloop: starting game" << endl;
		std::string errMsg;
		if(!cServer->StartGame(&errMsg)) {
			errors << "starting game in local game failed for reason: " << errMsg << endl;
			DeprecatedGUI::Menu_MessageBox("Error", "Error while starting game: " + errMsg);
			if (tLX->iGameType == GME_LOCAL)
				GotoLocalMenu();
			else
				GotoNetMenu();
			return;
		}
	}
	else
		warnings << "prepareGameloop: server was not in lobby" << endl;

	// we need the gamescript in physics init
	while(gameScript() == NULL) {
		notes << "gamescript not loaded yet - waiting" << endl;
		SDL_Delay(10);
		SyncServerAndClient();
	}

	// always also load Gusanos engine
	if( /*gameScript()->gusEngineUsed() && */ !gusGame.level().gusIsLoaded() ) {
		// WARNING: This may be temporary
		// Right now, we load the gus mod in the map loader (gusGame.changeLevel).
		// Thus, when we don't load a gus level, we must load the mod manually.

		if(!gameScript()->gusEngineUsed())
			gusGame.setMod(gusGame.getDefaultPath());
		gusGame.loadModWithoutMap();
	}

	PhysicsEngine::Init();

	ClearEntities();

	ProcessEvents();
	notes << "MaxFPS is " << tLXOptions->nMaxFPS << endl;

	//cCache.ClearExtraEntries(); // Do not clear anything before game started, it may be slow

	notes << "GameLoopStart" << endl;
	inMainGameLoop = true;
	if( DedicatedControl::Get() )
		DedicatedControl::Get()->GameLoopStart_Signal();

	CrashHandler::recoverAfterCrash = tLXOptions->bRecoverAfterCrash && GetGameVersion().releasetype == Version::RT_NORMAL;

	ResetQuitEngineFlag();
	oldtime = GetTime();
}

void Game::frameOuter() {
	tLX->currentTime = GetTime();
	SetCrashHandlerReturnPoint("main game loop");

	// Timing
	tLX->fDeltaTime = tLX->currentTime - oldtime;
	tLX->fRealDeltaTime = tLX->fDeltaTime;
	oldtime = tLX->currentTime;

	// cap the delta
	if(tLX->fDeltaTime.seconds() > 0.5f) {
		warnings << "deltatime " << tLX->fDeltaTime.seconds() << " is too high" << endl;
		// only if not in new net mode because it would screw up the gamestate there
		if(!NewNet::Active())
			tLX->fDeltaTime = 0.5f; // don't simulate more than 500ms, it could crash the game
	}

	ProcessEvents();

	// Main frame
	frameInner();

	doVideoFrameInMainThread();
	CapFPS();
}

///////////////////
// Game loop
void Game::frameInner()
{
	HandlePendingCommands();

	if(bDedicated)
		DedicatedControl::Get()->GameLoop_Frame();

    if(tLX->bQuitEngine)
        return;

	// Check if user pressed screenshot key
	if (tLX->cTakeScreenshot.isDownOnce())  {
		PushScreenshot("scrshots", "");
	}

	// Switch between window and fullscreen mode
	// Switch only if delta time is low enough. This is because when the game does not
	// respond for >30secs and the user presses cSwitchMode in the meantime, the mainlock-detector
	// would switch to window and here we would switch again to fullscreen which is stupid.
	if( tLX->cSwitchMode.isUp() && tLX && tLX->fRealDeltaTime < 1.0f )  {
		// Set to fullscreen
		tLXOptions->bFullscreen = !tLXOptions->bFullscreen;

		// Set the new video mode
		doSetVideoModeInMainThread();

		tLX->cSwitchMode.reset();
	}

#ifdef WITH_G15
	if (OLXG15)
		OLXG15->gameFrame();
#endif //WITH_G15

	if(tLXOptions->bEnableChat)
		ProcessIRC();

	//if(gusGame.isEngineNeeded()) {
	// do lua frames in all cases
	{
		// convert speed to lua if needed
		boost::shared_ptr<CGameObject::ScopedGusCompatibleSpeed> scopedSpeeds[MAX_WORMS];
		for(int i = 0; i < MAX_WORMS; ++i)
			if(cClient->getRemoteWorms()[i].isUsed())
				scopedSpeeds[i].reset( new CGameObject::ScopedGusCompatibleSpeed(cClient->getRemoteWorms()[i]) );

		gusLogicFrame();

		if(gameScript()->gusEngineUsed() && game.isServer()) {
			// copy worm states to CServer worms because they are never updated in this mode
			for(int i = 0; i < MAX_WORMS; ++i) {
				if(cServer->getWorms()[i].isUsed() && cClient->getRemoteWorms()[i].isUsed()) {
					cServer->getWorms()[i].health = cClient->getRemoteWorms()[i].health;
					cServer->getWorms()[i].vPos = cClient->getRemoteWorms()[i].vPos;
					cServer->getWorms()[i].vVelocity = cClient->getRemoteWorms()[i].vVelocity;
					cServer->getWorms()[i].bAlive = cClient->getRemoteWorms()[i].bAlive;
				}
			}
		}
	}
	/*} else {
		// do stuff here which we took from Gusanos, which is done in gusLogicFrame and should be done in any case
		sfx.think();
	}*/

	// Local
	switch (tLX->iGameType)  {
		case GME_LOCAL:
			cClient->Frame();
			cServer->Frame();

			if(tLX && !tLX->bQuitEngine)
				cClient->Draw(VideoPostProcessor::videoSurface());
			break;

			// Hosting
		case GME_HOST:
			cClient->Frame();
			cServer->Frame();

			if(tLX && !tLX->bQuitEngine)
				cClient->Draw(VideoPostProcessor::videoSurface());
			break;

			// Joined
		case GME_JOIN:
			cClient->Frame();
			if(tLX && !tLX->bQuitEngine)
				cClient->Draw(VideoPostProcessor::videoSurface());
			break;

	} // SWITCH

	cClient->resetDebugStr();

	EnableSystemMouseCursor(false);
}

void Game::cleanupAfterGameloopEnd() {
	CrashHandler::recoverAfterCrash = false;

	gusGame.reset(GusGame::ServerQuit);

	PhysicsEngine::UnInit();

	notes << "GameLoopEnd: " << quitEngineFlagReason << endl;
	inMainGameLoop = false;
	if( DedicatedControl::Get() )
		DedicatedControl::Get()->GameLoopEnd_Signal();

	cCache.ClearExtraEntries(); // Game ended - clear cache
}

void ResetQuitEngineFlag() {
	tLX->bQuitEngine = false;
}

void SetQuitEngineFlag(const std::string& reason) {
	Warning_QuitEngineFlagSet("SetQuitEngineFlag(" + reason + "): ");
	quitEngineFlagReason = reason;
	tLX->bQuitEngine = true;
	// If we call this from within the menu, the menu should shutdown.
	// It will be restarted then in the next frame.
	// If we are not in the menu (i.e. in maingameloop), this has no
	// effect as we set it to true in Menu_Start().
	if(DeprecatedGUI::tMenu)
		DeprecatedGUI::tMenu->bMenuRunning = false;
	// If we were in menu, because we forced the menu restart above,
	// we must set this, otherwise OLX would quit (because of current maingamelogic).
	if(DeprecatedGUI::bGame)
		*DeprecatedGUI::bGame = true;
}

bool Warning_QuitEngineFlagSet(const std::string& preText) {
	if(tLX->bQuitEngine) {
		hints << preText << endl;
		warnings << "bQuitEngine is set because: " << quitEngineFlagReason << endl;
		return true;
	}
	return false;
}

void Game::onNewWorm(CWorm* w) {
	//if(!game.gameScript()->gusEngineUsed()) return;

#ifdef USE_GRID
	objects.insertImmediately(w, Grid::WormColLayer, Grid::WormRenderLayer);
	if(w->getNinjaRopeObj()) objects.insertImmediately(w->getNinjaRopeObj(), 1, 1);
#else
	objects.insert(WORMS_COLLISION_LAYER,WORMS_RENDER_LAYER, w);
	if(w->getNinjaRopeObj()) objects.insert( 1,1, (CGameObject*)w->getNinjaRopeObj() );
#endif
}

void Game::onRemoveWorm(CWorm* w) {
/*#ifdef USE_GRID
	objects.(w, Grid::WormColLayer, Grid::WormRenderLayer);
	if(w->getNinjaRopeObj()) objects.insertImmediately(w->getNinjaRopeObj(), 1, 1);
#else
	objects.insert(WORMS_COLLISION_LAYER,WORMS_RENDER_LAYER, w);
	if(w->getNinjaRopeObj()) objects.insert( 1,1, (CGameObject*)w->getNinjaRopeObj() );
#endif	*/
}

void Game::onNewPlayer(CWormInputHandler* player) {
	players.push_back( player );
}

void Game::onNewPlayer_Lua(CWormInputHandler* p) {
	if(game.gameScript()->gusEngineUsed()) {
		EACH_CALLBACK(i, playerInit)
		{
			(lua.call(*i), p->getLuaReference())();
		}
	}
}

void Game::onNewHumanPlayer(CWormHumanInputHandler* player) {
	localPlayers.push_back( player );
	player->local = true;
}

void Game::onNewHumanPlayer_Lua(CWormHumanInputHandler* player) {
	if(game.gameScript()->gusEngineUsed()) {
		EACH_CALLBACK(i, localplayerInit)
		{
			(lua.call(*i), player->getLuaReference())();
		}
	}
}

void Game::reset() {
	// Delete all players
	for ( std::list<CWormInputHandler*>::iterator iter = players.begin(); iter != players.end(); ++iter)
	{
		// handled by CClient for now
		//(*iter)->deleteThis();
	}
	players.clear();
	localPlayers.clear();

	// Delete all objects
#ifdef USE_GRID
	objects.clear();
#else
	for ( ObjectsList::Iterator iter = game.objects.begin(); (bool)iter; ++iter)
	{
		(*iter)->deleteThis();
	}
	objects.clear();
#endif
}

CGameScript* Game::gameScript() {
	if(tLX) {
		if(tLX->iGameType == GME_JOIN) return cClient->getGameScript().get();
		return cServer->getGameScript().get();
	}
	return NULL;
}

CGameMode* Game::gameMode() {
	if(tLX) {
		if(tLX->iGameType != GME_JOIN) return cServer->getGameMode();
		return cClient->getGameLobby()->gameMode;
	}
	return NULL;
}

bool Game::isServer() {
	return tLX->iGameType != GME_JOIN;
}

bool Game::needProxyWormInputHandler() {
	return isServer();
}

bool Game::needToCreateOwnWormInputHandlers() {
	return isServer() || (cClient->getServerVersion() < OLXBetaVersion(0,59,1));
}

bool Game::isTeamPlay() {
	return cClient->isTeamGame();
}

bool CClient::getGamePaused() {
	return (tLX->iGameType == GME_LOCAL) && (bGameOver || bViewportMgr || bGameMenu || Con_IsVisible());
}

bool Game::isGamePaused() {
	// If we're in a menu & a local game, don't do simulation
	return cClient->getGamePaused();
}

bool Game::shouldDoPhysicsFrame() {
	return !isGamePaused() && cClient->canSimulate() &&
    // We stop a few seconds after the actual game over
	!(cClient->bGameOver && (tLX->currentTime - cClient->fGameOverTime).seconds() > GAMEOVER_WAIT);
}

<|endoftext|>


<commit_before>#include "NetworkWorker.h"
#include <QVector>
#include <QTime>
#include <QMutexLocker>
#include "src/common/GameTimer.h"
#include "src/common/net/Deserializer.h"
#include "easylogging++.h"
#include <cassert>

using common::net::Deserializer;

namespace net {

NetworkWorker::NetworkWorker(quint8 client_id, QHostAddress address, quint16 server_port, quint16 local_port, std::shared_ptr<GameTimer> game_timer)
  : last_event_id_(0), // the first class to GetNextEventId will return 1 (intended)
    last_packet_id_(0), // idem with GetNextPacketId
    client_id_(client_id),
    timer_(this),
    ping_timer_(this),
    clean_ping_data_timer_(this),
    game_timer_(game_timer) {

  socket_.connectToHost(address, server_port);

  connect(&timer_, SIGNAL(timeout()), this, SLOT(SendPendingEvents()));
  connect(&ping_timer_, SIGNAL(timeout()), this, SLOT(SendPingPacket()));
  connect(&clean_ping_data_timer_, SIGNAL(timeout()), this, SLOT(CleanPingData()));
  timer_.start(100);
  ping_timer_.start(200);
  clean_ping_data_timer_.start(5000);

  connect(&receive_socket_, SIGNAL(readyRead()), this, SLOT(ReadPendingDatagrams()));
  connect(&receive_socket_, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(SocketError(QAbstractSocket::SocketError)));
  receive_socket_.bind(local_port);

  LOG(INFO) << "Initialized the client network worker: listening on " << local_port << ", connected to " << address.toString().toStdWString() << ":" << server_port;
}

void NetworkWorker::SocketError(QAbstractSocket::SocketError) {
  LOG(ERROR) << "Socket error: " << socket_.errorString().toStdWString();
}

quint32 NetworkWorker::GetNextEventId() {
  last_event_id_ += 1;
  return last_event_id_;
}

quint32 NetworkWorker::GetNextPacketId() {
  last_packet_id_ += 1;
  return last_packet_id_;
}

void NetworkWorker::ReadPendingDatagrams() {
  VLOG(9) << "Reading pending datagrams...";
  while(receive_socket_.hasPendingDatagrams()) {
    QByteArray datagram;
    datagram.resize(receive_socket_.pendingDatagramSize());
    QHostAddress sender;
    quint16 sender_port;
    receive_socket_.readDatagram(datagram.data(), datagram.size(), &sender, &sender_port);
    ProcessDatagram(datagram);
  }

}

void NetworkWorker::ProcessDatagram(const QByteArray& datagram) {
  VLOG(LOG_PACKET_LEVEL) << "Received a datagram";
  QDataStream stream(datagram);
  if(!CheckProtocolAndVersion(stream)) {
    LOG(WARNING) << "Protocol or version ID mismatch, dropping datagram.";
    return;
  }
  quint32 packet_id = GetPacketId(stream);
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the packet ID";
    return;
  }
  VLOG(LOG_PACKET_LEVEL) << "Received datagram packet id: " << packet_id;
  quint8 packet_type = GetPacketType(stream);
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the packet type.";
    return;
  }
  switch(packet_type) {
    case kPingPacketId:
      VLOG(LOG_PACKET_LEVEL) << "The datagram is a ping packet.";
      ProcessPingPacket(stream);
      break;
    case kEntitiesPacketId:
      VLOG(LOG_PACKET_LEVEL) << "The datagram is an entities packet.";
      ProcessEntitiesPacket(stream);
      break;
    default:
      LOG(WARNING) << "Received unknown packet type " << packet_type << ", dropping datagram.";
      break;
  }
}

bool NetworkWorker::CheckProtocolAndVersion(QDataStream &stream) {
  assert(stream.status() == QDataStream::Ok);
  quint8 protocol_id;
  quint8 server_version;
  stream >> protocol_id;
  stream >> server_version;
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the protocol ID or version.";
    return false;
  }
  quint8 accepted_version = NetworkWorker::kClientVersion;
  quint8 accepted_protocol_id = NetworkWorker::kProtocolId;
  if(protocol_id != accepted_protocol_id) {
    LOG(WARNING) << "Received unexpected protocol ID: got " << protocol_id << ", expected " << accepted_protocol_id;
    return false;
  }
  if(server_version != accepted_version) {
    LOG(WARNING) << "Received unsupported server version: got " << server_version << ", but only version " << accepted_version << " is supported by this client.";
    return false;
  }
  return true;
}

void NetworkWorker::ProcessPingPacket(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint32 packet_id;
  quint32 server_timestamp;
  stream >> packet_id;
  stream >> server_timestamp; // unused for now
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the ping packet contents.";
    return;
  }
  auto send_timestamp = ping_timestamps_.find(packet_id);
  if(send_timestamp == ping_timestamps_.end()) {
    LOG(WARNING) << "Received a ping response, but could not find when the question was sent.";
  } else {
    UpdateRoundTripTime(send_timestamp->second);
    // even if the line below is not called (exception), the CleanPingData
    // method will prevent the memory leak
    ping_timestamps_.erase(send_timestamp);
  }
}

void NetworkWorker::ProcessEntitiesPacket(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint32 server_timestamp;
  quint32 entities_nb;
  stream >> server_timestamp;
  stream >> entities_nb;
  for(quint32 i = 0; i < entities_nb && stream.status() == QDataStream::Ok ; i++) {
    DeserializeEntity(stream, server_timestamp);
  }
  if(stream.status() != QDataStream::Ok) {
    LOG(WARNING) << "Received invalid entity datagram, dropping remaining data.";
  }
}

void NetworkWorker::DeserializeEntity(QDataStream& stream, quint32 timestamp) {
  std::unique_ptr<Entity> entity = Deserializer::DeserializeEntity(stream);
  if(entity.get() == nullptr || stream.status() != QDataStream::Ok) {
    LOG(WARNING) << "Could not deserialize an entity";
    return;
  }
  ServerEntity server_entity(timestamp, std::move(entity));
  VLOG(9) << "Received an entity / id = " << server_entity.GetEntity()->GetId() << " / server timestamp = " << server_entity.GetTimestamp();
  emit EntityReceived(server_entity);
}

void NetworkWorker::UpdateRoundTripTime(quint32 send_timestamp) {
  quint32 now = game_timer_->GetTimestamp();
  if(now < send_timestamp) {
    LOG(ERROR) << "The timestamp of a sent ping packet is in the future. This probably means the client clock settings changed after the packet was sent.";
    return;
  }
  round_trip_time_ = (double)round_trip_time_ * 0.9 + ((double)now - (double)send_timestamp) * 0.1;
  emit Latency(round_trip_time_ / 2);
}

quint8 NetworkWorker::GetPacketType(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint8 packet_id;
  stream >> packet_id;
  return packet_id;
}

quint32 NetworkWorker::GetPacketId(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint32 packet_id;
  stream >> packet_id;
  return packet_id;
}

void NetworkWorker::SendPingPacket() {
  QByteArray buffer;
  QDataStream stream(&buffer, QIODevice::OpenModeFlag::WriteOnly);
  quint32 id = PrepareHeader(stream, kPingPacketId);
  ping_timestamps_[id] = game_timer_->GetTimestamp();
  stream << ping_timestamps_[id];
  assert(stream.status() == QDataStream::Status::Ok);
  socket_.write(buffer);
  VLOG(9) << "Ping packet sent / timestamp = " << ping_timestamps_[id] << " / packet id = " << id;
}

void NetworkWorker::SendPendingEvents() {
    // we know the event won't be deleted until we leave this method
    // so using a pointer (instead of a shared_ptr) is fine
    QVector<Event*> to_send;
    QMutexLocker lock(&pending_events_mutex_);
    auto it = pending_.begin();
    int i = 0;
    while(it != pending_.end()) {
      while(i < 6 && it != pending_.end()) {
        // each packet contains at most 5 events in order not ot have the datagram splitted by the underlying network layers
        // TODO: add events until the datagram is 512 bytes (more robust)
        to_send.push_back(it->second.get());
        VLOG(9) << "Preparing to send the event with id " << it->second->GetId();
        i++;
        ++it;
      }
      QByteArray buffer;
      QDataStream stream(&buffer, QIODevice::OpenModeFlag::WriteOnly);
      quint32 packet_id = PrepareHeader(stream, kEventPacketId);
      stream << (quint8) to_send.size();
      for(Event* evt : to_send) {
        stream << *evt;
      }
      assert(stream.status() == QDataStream::Status::Ok);
      socket_.write(buffer);
      to_send.clear();
      i = 0;
      VLOG(9) << "Packet event with id " << packet_id << " sent";
    }
    VLOG(9) << "All pending events sent";
}

void NetworkWorker::AddEvent(std::unique_ptr<Event> event) {
  quint64 id = GetNextEventId();
  event->SetId(id);
  QMutexLocker lock(&pending_events_mutex_);
  pending_[id] = std::move(event);
}

quint32 NetworkWorker::PrepareHeader(QDataStream& stream, quint8 packet_type) {
  assert(stream.status() == QDataStream::Ok);
  quint32 packet_id = GetNextPacketId();
  stream << kProtocolId << kClientVersion << client_id_ << packet_id << packet_type;
  return packet_id;
}

void NetworkWorker::CleanPingData() {
  quint32 now = game_timer_->GetTimestamp();
  auto it = ping_timestamps_.begin();
  while(it != ping_timestamps_.end()) {
    if(it->second + 5000 < now) {
      it = ping_timestamps_.erase(it);
    } else {
      ++it;
    }
  }
}

bool NetworkWorker::CheckStreamStatus(const QDataStream& stream) const {
  if(stream.status() != QDataStream::Ok) {
    LOG(WARNING) << "Deserialization stream status is not OK (code is: " << (int) stream.status() << "), the current message will be lost";
    return false;
  }
  return true;
}

}
<commit_msg>Fix reading too many bytes when reading the number of entities in a packet.<commit_after>#include "NetworkWorker.h"
#include <QVector>
#include <QTime>
#include <QMutexLocker>
#include "src/common/GameTimer.h"
#include "src/common/net/Deserializer.h"
#include "easylogging++.h"
#include <cassert>

using common::net::Deserializer;

namespace net {

NetworkWorker::NetworkWorker(quint8 client_id, QHostAddress address, quint16 server_port, quint16 local_port, std::shared_ptr<GameTimer> game_timer)
  : last_event_id_(0), // the first class to GetNextEventId will return 1 (intended)
    last_packet_id_(0), // idem with GetNextPacketId
    client_id_(client_id),
    timer_(this),
    ping_timer_(this),
    clean_ping_data_timer_(this),
    game_timer_(game_timer) {

  socket_.connectToHost(address, server_port);

  connect(&timer_, SIGNAL(timeout()), this, SLOT(SendPendingEvents()));
  connect(&ping_timer_, SIGNAL(timeout()), this, SLOT(SendPingPacket()));
  connect(&clean_ping_data_timer_, SIGNAL(timeout()), this, SLOT(CleanPingData()));
  timer_.start(100);
  ping_timer_.start(200);
  clean_ping_data_timer_.start(5000);

  connect(&receive_socket_, SIGNAL(readyRead()), this, SLOT(ReadPendingDatagrams()));
  connect(&receive_socket_, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(SocketError(QAbstractSocket::SocketError)));
  receive_socket_.bind(local_port);

  LOG(INFO) << "Initialized the client network worker: listening on " << local_port << ", connected to " << address.toString().toStdWString() << ":" << server_port;
}

void NetworkWorker::SocketError(QAbstractSocket::SocketError) {
  LOG(ERROR) << "Socket error: " << socket_.errorString().toStdWString();
}

quint32 NetworkWorker::GetNextEventId() {
  last_event_id_ += 1;
  return last_event_id_;
}

quint32 NetworkWorker::GetNextPacketId() {
  last_packet_id_ += 1;
  return last_packet_id_;
}

void NetworkWorker::ReadPendingDatagrams() {
  VLOG(9) << "Reading pending datagrams...";
  while(receive_socket_.hasPendingDatagrams()) {
    QByteArray datagram;
    datagram.resize(receive_socket_.pendingDatagramSize());
    QHostAddress sender;
    quint16 sender_port;
    receive_socket_.readDatagram(datagram.data(), datagram.size(), &sender, &sender_port);
    ProcessDatagram(datagram);
  }

}

void NetworkWorker::ProcessDatagram(const QByteArray& datagram) {
  VLOG(LOG_PACKET_LEVEL) << "Received a datagram";
  QDataStream stream(datagram);
  if(!CheckProtocolAndVersion(stream)) {
    LOG(WARNING) << "Protocol or version ID mismatch, dropping datagram.";
    return;
  }
  quint32 packet_id = GetPacketId(stream);
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the packet ID";
    return;
  }
  VLOG(LOG_PACKET_LEVEL) << "Received datagram packet id: " << packet_id;
  quint8 packet_type = GetPacketType(stream);
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the packet type.";
    return;
  }
  switch(packet_type) {
    case kPingPacketId:
      VLOG(LOG_PACKET_LEVEL) << "The datagram is a ping packet.";
      ProcessPingPacket(stream);
      break;
    case kEntitiesPacketId:
      VLOG(LOG_PACKET_LEVEL) << "The datagram is an entities packet.";
      ProcessEntitiesPacket(stream);
      break;
    default:
      LOG(WARNING) << "Received unknown packet type " << packet_type << ", dropping datagram.";
      break;
  }
}

bool NetworkWorker::CheckProtocolAndVersion(QDataStream &stream) {
  assert(stream.status() == QDataStream::Ok);
  quint8 protocol_id;
  quint8 server_version;
  stream >> protocol_id;
  stream >> server_version;
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the protocol ID or version.";
    return false;
  }
  quint8 accepted_version = NetworkWorker::kClientVersion;
  quint8 accepted_protocol_id = NetworkWorker::kProtocolId;
  if(protocol_id != accepted_protocol_id) {
    LOG(WARNING) << "Received unexpected protocol ID: got " << protocol_id << ", expected " << accepted_protocol_id;
    return false;
  }
  if(server_version != accepted_version) {
    LOG(WARNING) << "Received unsupported server version: got " << server_version << ", but only version " << accepted_version << " is supported by this client.";
    return false;
  }
  return true;
}

void NetworkWorker::ProcessPingPacket(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint32 packet_id;
  quint32 server_timestamp;
  stream >> packet_id;
  stream >> server_timestamp; // unused for now
  if(!CheckStreamStatus(stream)) {
    LOG(WARNING) << "Could not deserialize the ping packet contents.";
    return;
  }
  auto send_timestamp = ping_timestamps_.find(packet_id);
  if(send_timestamp == ping_timestamps_.end()) {
    LOG(WARNING) << "Received a ping response, but could not find when the question was sent.";
  } else {
    UpdateRoundTripTime(send_timestamp->second);
    // even if the line below is not called (exception), the CleanPingData
    // method will prevent the memory leak
    ping_timestamps_.erase(send_timestamp);
  }
}

void NetworkWorker::ProcessEntitiesPacket(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint32 server_timestamp;
  quint8 entities_nb;
  stream >> server_timestamp;
  stream >> entities_nb;
  for(quint32 i = 0; i < entities_nb && stream.status() == QDataStream::Ok ; i++) {
    DeserializeEntity(stream, server_timestamp);
  }
  if(stream.status() != QDataStream::Ok) {
    LOG(WARNING) << "Received invalid entity datagram, dropping remaining data.";
  }
}

void NetworkWorker::DeserializeEntity(QDataStream& stream, quint32 timestamp) {
  std::unique_ptr<Entity> entity = Deserializer::DeserializeEntity(stream);
  if(entity.get() == nullptr || stream.status() != QDataStream::Ok) {
    LOG(WARNING) << "Could not deserialize an entity";
    return;
  }
  ServerEntity server_entity(timestamp, std::move(entity));
  VLOG(9) << "Received an entity / id = " << server_entity.GetEntity()->GetId() << " / server timestamp = " << server_entity.GetTimestamp();
  emit EntityReceived(server_entity);
}

void NetworkWorker::UpdateRoundTripTime(quint32 send_timestamp) {
  quint32 now = game_timer_->GetTimestamp();
  if(now < send_timestamp) {
    LOG(ERROR) << "The timestamp of a sent ping packet is in the future. This probably means the client clock settings changed after the packet was sent.";
    return;
  }
  round_trip_time_ = (double)round_trip_time_ * 0.9 + ((double)now - (double)send_timestamp) * 0.1;
  emit Latency(round_trip_time_ / 2);
}

quint8 NetworkWorker::GetPacketType(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint8 packet_id;
  stream >> packet_id;
  return packet_id;
}

quint32 NetworkWorker::GetPacketId(QDataStream& stream) {
  assert(stream.status() == QDataStream::Ok);
  quint32 packet_id;
  stream >> packet_id;
  return packet_id;
}

void NetworkWorker::SendPingPacket() {
  QByteArray buffer;
  QDataStream stream(&buffer, QIODevice::OpenModeFlag::WriteOnly);
  quint32 id = PrepareHeader(stream, kPingPacketId);
  ping_timestamps_[id] = game_timer_->GetTimestamp();
  stream << ping_timestamps_[id];
  assert(stream.status() == QDataStream::Status::Ok);
  socket_.write(buffer);
  VLOG(9) << "Ping packet sent / timestamp = " << ping_timestamps_[id] << " / packet id = " << id;
}

void NetworkWorker::SendPendingEvents() {
    // we know the event won't be deleted until we leave this method
    // so using a pointer (instead of a shared_ptr) is fine
    QVector<Event*> to_send;
    QMutexLocker lock(&pending_events_mutex_);
    auto it = pending_.begin();
    int i = 0;
    while(it != pending_.end()) {
      while(i < 6 && it != pending_.end()) {
        // each packet contains at most 5 events in order not ot have the datagram splitted by the underlying network layers
        // TODO: add events until the datagram is 512 bytes (more robust)
        to_send.push_back(it->second.get());
        VLOG(9) << "Preparing to send the event with id " << it->second->GetId();
        i++;
        ++it;
      }
      QByteArray buffer;
      QDataStream stream(&buffer, QIODevice::OpenModeFlag::WriteOnly);
      quint32 packet_id = PrepareHeader(stream, kEventPacketId);
      stream << (quint8) to_send.size();
      for(Event* evt : to_send) {
        stream << *evt;
      }
      assert(stream.status() == QDataStream::Status::Ok);
      socket_.write(buffer);
      to_send.clear();
      i = 0;
      VLOG(9) << "Packet event with id " << packet_id << " sent";
    }
    VLOG(9) << "All pending events sent";
}

void NetworkWorker::AddEvent(std::unique_ptr<Event> event) {
  quint64 id = GetNextEventId();
  event->SetId(id);
  QMutexLocker lock(&pending_events_mutex_);
  pending_[id] = std::move(event);
}

quint32 NetworkWorker::PrepareHeader(QDataStream& stream, quint8 packet_type) {
  assert(stream.status() == QDataStream::Ok);
  quint32 packet_id = GetNextPacketId();
  stream << kProtocolId << kClientVersion << client_id_ << packet_id << packet_type;
  return packet_id;
}

void NetworkWorker::CleanPingData() {
  quint32 now = game_timer_->GetTimestamp();
  auto it = ping_timestamps_.begin();
  while(it != ping_timestamps_.end()) {
    if(it->second + 5000 < now) {
      it = ping_timestamps_.erase(it);
    } else {
      ++it;
    }
  }
}

bool NetworkWorker::CheckStreamStatus(const QDataStream& stream) const {
  if(stream.status() != QDataStream::Ok) {
    LOG(WARNING) << "Deserialization stream status is not OK (code is: " << (int) stream.status() << "), the current message will be lost";
    return false;
  }
  return true;
}

}
<|endoftext|>


<commit_before>#include "v3dDataFibersWriter.h"

#include "dtkCore/dtkAbstractData.h"
#include "dtkCore/dtkAbstractDataFactory.h"

#include <vtkPolyData.h>
#include <vtkCellArray.h>
#include <vtkXMLFiberDataSetWriter.h>
#include "vtkFiberDataSet.h"

#include <QFile>
#include <QXmlStreamWriter>

class v3dDataFibersWriterPrivate
{
public:
};

v3dDataFibersWriter::v3dDataFibersWriter(): d (new v3dDataFibersWriterPrivate)
{
}

v3dDataFibersWriter::~v3dDataFibersWriter()
{
   delete d;
   d = NULL;
}

QStringList v3dDataFibersWriter::handled(void) const
{
    return QStringList() << "v3dDataFibers";
}

bool v3dDataFibersWriter::canWrite(const QString& path)
{
  return true;
}

bool v3dDataFibersWriter::write(const QString& path)
{
  if (!this->data())
      return false;

  if (this->data()->description()!="v3dDataFibers")
      return false;

  vtkFiberDataSet *dataset = static_cast<vtkFiberDataSet*>(this->data()->data());
  if (!dataset)
      return false;

  dataset->Update();

  vtkXMLFiberDataSetWriter *writer = vtkXMLFiberDataSetWriter::New();
  writer->SetFileName ( path.toAscii().constData() );
  writer->SetInput ( dataset );
  writer->Write();

  writer->Delete();

  return true;
}

QString v3dDataFibersWriter::description(void) const
{
    return "v3dDataFibersWriter";
}

bool v3dDataFibersWriter::registered(void)
{
  return dtkAbstractDataFactory::instance()->registerDataWriterType("v3dDataFibersWriter",
								    QStringList() << "v3dDataFibers",
								    createV3dDataFibersWriter);
}

// /////////////////////////////////////////////////////////////////
// Type instanciation
// /////////////////////////////////////////////////////////////////

dtkAbstractDataWriter *createV3dDataFibersWriter(void)
{
  return new v3dDataFibersWriter;
}
<commit_msg>Set write mode to binary<commit_after>#include "v3dDataFibersWriter.h"

#include "dtkCore/dtkAbstractData.h"
#include "dtkCore/dtkAbstractDataFactory.h"

#include <vtkPolyData.h>
#include <vtkCellArray.h>
#include <vtkXMLFiberDataSetWriter.h>
#include "vtkFiberDataSet.h"

#include <QFile>
#include <QXmlStreamWriter>

class v3dDataFibersWriterPrivate
{
public:
};

v3dDataFibersWriter::v3dDataFibersWriter(): d (new v3dDataFibersWriterPrivate)
{
}

v3dDataFibersWriter::~v3dDataFibersWriter()
{
   delete d;
   d = NULL;
}

QStringList v3dDataFibersWriter::handled(void) const
{
    return QStringList() << "v3dDataFibers";
}

bool v3dDataFibersWriter::canWrite(const QString& path)
{
  return true;
}

bool v3dDataFibersWriter::write(const QString& path)
{
  if (!this->data())
      return false;

  if (this->data()->description()!="v3dDataFibers")
      return false;

  vtkFiberDataSet *dataset = static_cast<vtkFiberDataSet*>(this->data()->data());
  if (!dataset)
      return false;

  dataset->Update();

  vtkXMLFiberDataSetWriter *writer = vtkXMLFiberDataSetWriter::New();
  writer->SetFileName ( path.toAscii().constData() );
  writer->SetInput ( dataset );
  writer->SetDataModeToBinary();
  writer->Write();

  writer->Delete();

  return true;
}

QString v3dDataFibersWriter::description(void) const
{
    return "v3dDataFibersWriter";
}

bool v3dDataFibersWriter::registered(void)
{
  return dtkAbstractDataFactory::instance()->registerDataWriterType("v3dDataFibersWriter",
								    QStringList() << "v3dDataFibers",
								    createV3dDataFibersWriter);
}

// /////////////////////////////////////////////////////////////////
// Type instanciation
// /////////////////////////////////////////////////////////////////

dtkAbstractDataWriter *createV3dDataFibersWriter(void)
{
  return new v3dDataFibersWriter;
}
<|endoftext|>


<commit_before>/* brushlib - The MyPaint Brush Library
 * Copyright (C) 2007-2008 Martin Renold <martinxyz@gmx.ch>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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. See the COPYING file for more details.
 */

#ifndef __MAPPING_H__
#define __MAPPING_H__

// user-defined mappings
// (the curves you can edit in the brush settings)

class Mapping {
private:
  typedef struct {
    // a set of control points (stepwise linear)
    float xvalues[8];
    float yvalues[8];
    int n;
  } ControlPoints;

  int inputs;
  ControlPoints * pointsList; // one for each input
  int inputs_used; // optimization
public:
  float base_value;

  Mapping(int inputs_) {
    inputs = inputs_;
    pointsList = new ControlPoints[inputs];
    for (int i=0; i<inputs; i++) pointsList[i].n = 0;
    inputs_used = 0;
    base_value = 0;
  }
  ~Mapping() {
    delete pointsList;
  }

  void set_n (int input, int n)
  {
    assert (input >= 0 && input < inputs);
    assert (n >= 0 && n <= 8);
    assert (n != 1); // cannot build a linear mapping with only one point
    ControlPoints * p = pointsList + input;

    if (n != 0 && p->n == 0) inputs_used++;
    if (n == 0 && p->n != 0) inputs_used--;
    assert(inputs_used >= 0);
    assert(inputs_used <= inputs);

    p->n = n;
  }

  void set_point (int input, int index, float x, float y)
  {
    assert (input >= 0 && input < inputs);
    assert (index >= 0 && index < 8);
    ControlPoints * p = pointsList + input;
    assert (index < p->n);

    if (index > 0) {
      assert (x > p->xvalues[index-1]);
    }

    p->xvalues[index] = x;
    p->yvalues[index] = y;
  }

  bool is_constant()
  {
    return inputs_used == 0;
  }

  float calculate (float * data)
  {
    int j;
    float result;
    result = base_value;

    // constant mapping (common case)
    if (inputs_used == 0) return result;

    for (j=0; j<inputs; j++) {
      ControlPoints * p = pointsList + j;

      if (p->n) {
        float x, y;
        x = data[j];

        // find the segment with the slope that we need to use
        float x0, y0, x1, y1;
        x0 = p->xvalues[0];
        y0 = p->yvalues[0];
        x1 = p->xvalues[1];
        y1 = p->yvalues[1];

        int i;
        for (i=2; i<p->n && x>x1; i++) {
          x0 = x1;
          y0 = y1;
          x1 = p->xvalues[i];
          y1 = p->yvalues[i];
        }

        //g_print ("%d/%d x0=%f,x1=%f\n", i, p->n, x0, x1);

        // linear interpolation
        float m, q;
        m = (y1-y0)/(x1-x0);
        q = y0 - m*x0;
        y = m*x + q;
        result += y;
      }
    }
    return result;
  }

  // used in python for the global pressure mapping
  float calculate_single_input (float input)
  {
    assert(inputs == 1);
    return calculate(&input);
  }
};

#endif
<commit_msg>[mapping] simplify linear interpolation formula<commit_after>/* brushlib - The MyPaint Brush Library
 * Copyright (C) 2007-2008 Martin Renold <martinxyz@gmx.ch>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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. See the COPYING file for more details.
 */

#ifndef __MAPPING_H__
#define __MAPPING_H__

// user-defined mappings
// (the curves you can edit in the brush settings)

class Mapping {
private:
  typedef struct {
    // a set of control points (stepwise linear)
    float xvalues[8];
    float yvalues[8];
    int n;
  } ControlPoints;

  int inputs;
  ControlPoints * pointsList; // one for each input
  int inputs_used; // optimization
public:
  float base_value;

  Mapping(int inputs_) {
    inputs = inputs_;
    pointsList = new ControlPoints[inputs];
    for (int i=0; i<inputs; i++) pointsList[i].n = 0;
    inputs_used = 0;
    base_value = 0;
  }
  ~Mapping() {
    delete pointsList;
  }

  void set_n (int input, int n)
  {
    assert (input >= 0 && input < inputs);
    assert (n >= 0 && n <= 8);
    assert (n != 1); // cannot build a linear mapping with only one point
    ControlPoints * p = pointsList + input;

    if (n != 0 && p->n == 0) inputs_used++;
    if (n == 0 && p->n != 0) inputs_used--;
    assert(inputs_used >= 0);
    assert(inputs_used <= inputs);

    p->n = n;
  }

  void set_point (int input, int index, float x, float y)
  {
    assert (input >= 0 && input < inputs);
    assert (index >= 0 && index < 8);
    ControlPoints * p = pointsList + input;
    assert (index < p->n);

    if (index > 0) {
      assert (x > p->xvalues[index-1]);
    }

    p->xvalues[index] = x;
    p->yvalues[index] = y;
  }

  bool is_constant()
  {
    return inputs_used == 0;
  }

  float calculate (float * data)
  {
    int j;
    float result;
    result = base_value;

    // constant mapping (common case)
    if (inputs_used == 0) return result;

    for (j=0; j<inputs; j++) {
      ControlPoints * p = pointsList + j;

      if (p->n) {
        float x, y;
        x = data[j];

        // find the segment with the slope that we need to use
        float x0, y0, x1, y1;
        x0 = p->xvalues[0];
        y0 = p->yvalues[0];
        x1 = p->xvalues[1];
        y1 = p->yvalues[1];

        int i;
        for (i=2; i<p->n && x>x1; i++) {
          x0 = x1;
          y0 = y1;
          x1 = p->xvalues[i];
          y1 = p->yvalues[i];
        }

        // linear interpolation
        y = (y1*(x - x0) + y0*(x1 - x)) / (x1 - x0);

        result += y;
      }
    }
    return result;
  }

  // used in python for the global pressure mapping
  float calculate_single_input (float input)
  {
    assert(inputs == 1);
    return calculate(&input);
  }
};

#endif
<|endoftext|>